IoTBASIC bzw. TinyBASIC (Stefans's BASIC) auf div. Plattformen

  • auf dem Thread "RunCPM Speedvergleich" will ich das Thema mal anfangen hier auszulagern, da es doch einge mehr Texte zum Thema werden, die im RunCPM Speedvergleich nicht ganz reinpassen ;)


    Hier geht es um das IoTBASIC / TinyBASIC von slenz


    ARDUINOMQTT und USEPICOSERIAL habe ich auf #undef - aber die automatische Speichererkennung war es wohl ;)
    Mit #define MEMSIZE 32000 klappte es auf Anhieb.

    Spannenderweise klappt laut Anzeige auch 65535 - aber wenn man irrwitzigerweise mal 128000 gibt, dann

    kommt die Speicheranzeige nur auf 62464



    So kann ich mir vorstellen,, dass die 65535 nicht wirklich OK sind und uns da fuer sauberen Betrieb knapp 70-100 Bytes fehlen :)
    Also hier fuer den PicoW dannn eher 60000-62000 nehmen? (59x1024 waeren 60416)

    #define MEMSIZE 60416 in der IoTBasic.ino


    slenz BTW: Kennst Du die SdFat-Library von "greiman"? Die nutzt das RunCPM-Projekt mit mehreren Plattformen und auch der RP2040 arduino-pico-Core hat eine eigene (aeltere) Version der SdFat in Nutzung.
    Die SdFat-Library kann AT16/FAT32 und exFAT - fuer die normae SD.h muesste ich meine Karte ja mit FDISK vom IoTBASIC erst formatieren zur Nutzung.

  • Vielleicht ganz kurz zwei Worte zu dem Speichermechanismus. Das BASIC ist so gemacht, dass der komplette Hauptspeicher auf einmal allokiert wird und einen zusammenhängenden Speicherbereich bildet. Wenn MEMSIZE nicht gesetzt ist, wird der Speicher auf dem C Heap allokiert indem einmal malloc() aufgerufen wird. Eine eine Heuristik versucht zu erraten, wieviel möglich ist abhängig von den Hardware-Subsystemen. All das passiert in freememorysize() und freeRam(). Darin finden sich folgende verfängliche Zeilen


    Code
    #if defined(ARDUINO_NANO_RP2040_CONNECT) || defined(ARDUINO_RASPBERRY_PI_PICO)
    return 65536;
    #endif


    Ich habe bisher keinen Mechanismus gefunden, für die Mbed Cores die maximale Heapgröße zu messen. Für fast alle anderen Plattformen geht das. Bisher habe ich da auch keine Zeit reingesteckt, weil ich davon ausgegangen bin, dass die RP2040 so viel Speicher haben, das 64kB sicher ist.


    Wenn Du MEMSIZE von Hand setzt, wird ein anderer Mechanismus verwendet. In dem Fall wird der Speicher im Bereich der statischen Variablen allokiert. Also ein anderer Speicherbereich. Deswegen kann es durchaus sein, dass 64kB hier problemlos geht.


    Wenn Du 128000 setzt, passiert noch ein anderer Effekt. In basic.h ist der Typ address_t definiert. Das ist normalerweise


    Code
    typedef unsigned short address_t; /* this type addresses memory */


    Unsigned short ist auf den meisten Plattformen ein uint16_t also unsigned 16 bit integer. Deswegen geht nur maximal 65536 als Adressgröße. MEMSIZE darf nicht größer sein, als diese Zahl. Wenn Du wirklich ausloten willst, wieviel Du statisch allokieren kannst, dann könntest du folgendes tun

    Code
    typedef unsigned int address_t; /* this type addresses memory */


    dann hast du einen 32 bit Adressraum. Dann kannst Du MEMSIZE hochschrauben. BASIC läuft damit problemlos (ich habe es aber eine Weile nicht mehr getestet).


    Ich nehme an, dass der Core den Du benutzt eine Beschränkung für den Heap hat. Vielleicht erlaubt er nur 96 kB oder etwas ähnliches für den Heap. Es kann sein, dass das beim PicoW zu einem anderen Speicherlayout als beim Pico führt, weil zum Bespiel der Core selber Speicher für seine Subsysteme (USB, Wireless etc) vorbelegt. Ich schaue mir diese Wochenende den verwendeten Core mal an. Wie gesagt, die RP2040 waren bisher für mich auf der Low Level Ebene rätselhaft.


  • mit dem int type kann ich den Speicher hochdrehen bis auf #define MEMSIZE 184320


    Code
    Stefan's Basic v1.4 on RPi PicoW - Memory 184320 0


    allerdings ist dann laut Arduino-IDE damit fast das Ende erreicht, wenn man mehr als 97% ausnutzt dann compiliert die Arduino-IDE es nicht fertig- so ist dies wohl das Maximum:


    Code
    Bei 184320:
    
    Global variables use 256080 bytes (97%) of dynamic memory, 
    leaving 6064 bytes for local variables. Maximum is 262144 bytes.
    Low memory available, stability problems may occur.


    Ob das BASIC mit den 180KB (180*1024 = 184320) stabil laeuft?
    Bei 128KB hat man eine Auslastung von 77% bekommt aber auch schon die Speicher./Stabilitaetswarnung.


    Bei 96KB (98304) scheint die Welt noch in Ordnung zu sein :) weil keine Low-Memory Warnung:

    Code
    Global variables use 170064 bytes (64%) of dynamic memory, 
    leaving 92080 bytes for local variables. Maximum is 262144 bytes.


    Fuer den arduino-pico Core gibt es eine echt tolle Dokumentaionsseite und der Core scheint auch eine Helper-Class fuer den Heap zu haben:

    Code
    RP2040 Helper Class
    int rp2040.getFreeHeap()
    ... Returns the number of bytes free for heap allocation (i.e. malloc, new). ...
    int rp2040.getUsedHeap()
    ... Returns the number of bytes allocated out of the heap. ...
    int rp2040.getTotalHeap()
    ... Returns the total heap that was available to this program at compile time (i.e. the Pico RAM size minus ...
  • Da ich mein PyBoard v1.1 mit STM32F405RGT6 schon mal mit RunCPM am laufen hatte, habe ich heute mal das IoTBASIC darauf zum laufen bekommen ;)


    Musste die Librarys STM32SD, STM32RTC und STM32LowPower installieren
    (die letzten beiden fand ich nicht ueber den Library Manager, sondern nur auf Github)


    Dann das define von ARDUINOSD auf STM32SD umgestellt und die SDCard-SDIO-SPI-Pins auf die des PyBoard angepasst.


    Und schon beim ersten Compile kein Fehler ;)


  • Und schon beim ersten Compile kein Fehler ;)


    Oben das war ein Compile mit der Standard-Optimierungsoption -Os


    Jetzt nochmal eine optimierte Kompilierung mit der Option -O3 (fastest) und LTO (Link Time Optimization).

    Das gibt nochmal ordentlich Zusatzspeed (468.77 zu 326.86 Sekunden) - allerdings bekomme ich da nie die "Einschaltmeldung" :(


  • Hier mal die Performance Zahlen von BASIC auf den verschiedenen Plattformen:


    Uno Integer - Simple

    Loop time 43

    Token time 18

    Assignment time 68

    Multiplication time 34


    LGT8 Integer - Simple

    Loop time 15

    Token time 6

    Assignment time 19

    Multiplication time 10


    Uno Float - Simple with float

    Loop time 69.9

    Token time 21.1

    Assignment time 83.5

    Multiplication time 52.2


    STM32V4VE

    Loop time 5.3

    Token time 1.7

    Assignment time 10.3

    Multiplication time 3.7


    XMC

    Loop time 43

    Token time 10.3

    Assignment time 56

    Multiplication time 34.2


    ESP8266

    Loop time 29.89999

    Token time 21.5

    Assignment time 36.9

    Multiplication time 9.6


    ESP32

    Loop time 11.3

    Token time 9.89999

    Assignment time 14

    Multiplication time 2.59999


    ESP32-C3

    Loop time 12.3

    Token time 9.39999

    Assignment time 14

    Multiplication time 3.59999


    RP2040 (Pi Pico)

    Loop time 12.6

    Token time 12.89999

    Assignment time 18.5

    Multiplication time 5.59999


    Die Zeiten sind jeweils Microsekunden für eine Operation. Die Token Time ist die Zeit, die es braucht, um ein Token aus dem Speicher zu laden und zu interpretieren. Sie ist im Wesentlichen von der Taktfrequenz und der Bandbreite des Speichers abhängig. UNO mit Float ist hier eine Ausnahme. Dort bremst die langsame Arithmetik auch die Token Time aus. Die Messung ist in dem Fall ungenau.

  • slenz Mit optimierten Compile-Optionen (-O3 evtl. mit LTO) scheinen meine Vergleichwerte
    (bis auf die STM32-Token-Time) noch etwas schneller zu sein :)
    - gemessen mit BENCH2.BAS -


    PyBoard v1.1 STM32F405RGT6 -O3 LTO ==> keine Einschalt-/Boot-Meldung vor dem Prompt

    Loop time 3.4

    Token time 1.9

    Assignment time 7.4

    Multiplication time 1.79999


    TTGO VGA32 (ESP32) -O3

    Loop time 3.9

    Token time 2.29999

    Assignment time 5.4

    Multiplication time 1.79999


    RP2040 PicoW 250Mhz -O3

    Loop time 7.59999

    Token time 3.7

    Assignment time 11.69999

    Multiplication time 1.7


    RP2040 Pico 250Mhz -O3

    Loop time 4.9

    Token time 2.5

    Assignment time 10.5

    Multiplication time 5.4


    RP2040 Pico 250Mhz -Ofast

    Loop time 5.4

    Token time 2.7

    Assignment time 10.89999

    Multiplication time 1.7

    Einmal editiert, zuletzt von guidol ()


  • Spannend ist da die Veränderung der Token Time. Ich nehme an, dass er die Speicherzugriffe "inlined". Sehr sehr interessant. Der Interpreter klopft ja die 32bit Architekturen flach und tut so, als ob er auf einer 8bit Maschine läuft. Das passiert in memread(), memwrite(), memwrite2().

  • PyBoard v1.1 STM32F405RGT6 -O3 LTO ==> keine Einschalt-/Boot-Meldung vor dem Prompt

    Loop time 3.4

    Token time 1.9

    Assignment time 7.4

    Multiplication time 1.79999

    PyBoard v1.1 STM32F405RGT6 -O3 ohne LTO ==> hier gibt es eine Einschalt-/Boot-Meldung vor dem Prompt

    Loop time 4.9

    Token time 1.6

    Assignment time 8.5

    Multiplication time 2.4

  • Da wird also die Token Time schlechter. Sehr spannend. Aber die Messung der Token TIme ist auch ungenau.

  • Hier noch im Vergleich ein

    Deneyap Mini v2 - ESP32-S2 240Mhz -O3 compile Option

    Loop time 3.29999

    Token time 1.39999

    Assignment time 14.8

    Multiplication time 1.7



    Die BENCH2 Werte sehen ja nicht schlecht aus, aber gegen den TTGO VGA32 (ESP32 normal) braucht der
    Deneyap (ESP32-S2) fast die doppelte Zeit bei 1000 Stellen Pi,
    was ich aufgrund der BENCH2-Werte nicht erwartet haette.



  • hier mal ein Exote - aufgrund der CPU-Plattform - fuer IoTBASIC ;)
    Ein Verwandter des Ardunio UNO, aber mit PIC32MX-CPU (wie der DuinoMite, aber weniger RAM/Flash)


    chipKit Uno32 PIC32-MX320F128H up to 80Mhz

    Loop time 5

    Token time 2

    Assignment time 10

    Multiplication time 4


    Leider unterstuetzt die SD.h nicht die pic32 CPU-Plattform, so dass ich kein #def ARDUINOSD

    nutzen kann :(

    Ich weiss auch nicht wieviel RAM dies kosten wuerde, da er beim kompilieren nur 4096 Bytes zulaesst

    (ohne Low Memory) - obwohl der Uno32 16KB Ram hat.



  • Es sieht so aus als ob die Assignment Time des Deneyap viel länger ist. Faktor 3. Das heißt, dass irgendwas mit dem Schreiben auf's Memory langsam ist. Die ESPe haben ja serielles Memory an einem eigenen SPI Bus. Kann sein, dass billigere Bausteine verbaut wurden und die Bus Geschwindigkeit niedriger ist. Bei den sehr billigen Teilen schneiden die Hersteller oft die eine oder andere Kurve.


    Zum Spass mal die (sehr ungenauen) Zahlen eines Raspberry PI 3 mit raspbian bullseye, ohne -O3:


    Loop time 8.1

    Token time 7.2

    Assignment time 8.4

    Multiplication time 0.6


    1000 Stellen Pi: 221.5 sec


    Und mit -O3:


    Loop time 7.5

    Token time 7.3

    Assignment time 7.8

    Multiplication time 0.1


    1000 Stellen Pi: 193.8 sec


    Hier sieht man den Effekt der Arithmetik. Die schlechtere Loop Time wundert mich. Normalerweise würde ich erwarten, dass der Raspberry PI 3 da schneller ist.


    Dann noch das Macbook:


    Loop time 2.7

    Token time 3.2

    Assignment time 2.7

    Multiplication time 0


    1000 Stellen Pi: 72.6 sec.


    Multiplication Time ist unterhalb der Messgenauigkeit.


    Der Vergleich hier ist natürlich unfair, weil Macbook und Linux jede Menge Background Tasks machen. Effektiv misst man hier den Scheduler mit.


    Trotzdem sieht man, wie schnell die kleinen Microcontroller inzwischen sind.

  • chipKit Uno32 PIC32-MX320F128H up to 80Mhz


    Leider unterstuetzt die SD.h nicht die pic32 CPU-Plattform, so dass ich kein #define ARDUINOSD

    nutzen kann :(

    Ich weiss auch nicht wieviel RAM dies kosten wuerde, da er beim kompilieren nur 4096 Bytes zulaesst

    Ich habe die pic32 SD.h gefunden und eingebunden, so dass ich das #define ADUINOSD nutzen konnte,

    allerdings musste ich dazu

    #define PROGMEM

    #undef ARDUINOPROGMEM

    setzen.

    Dann liess es sich auch mit SD compilieren, allerdings scheint da die Krux zu liegen, denn wenn ich versuche auf die SD zuzugreifen bleibt der Uno32 haengen :(

    Ohne dies zu setzen hat er Probleme mit der pgmspace.h


    Nun ja, dann habe ich mir meinen Arduino DUE genommen mit SDCard-Hat (CS/SS auf Pin 4) und nach langem basteln auch eine lauffaehige Version hinbekommen.
    Die Bildschirmanzeige hatte ich schnell, aber die SDKarte wollte erst auch nicht.

    Ein

    #define SDPIN 4

    zum

    #define ARDUINOEEPROM

    half dann wohl.


    Arduino DUE compiliert mit -O3

    Loop time 15.39999

    Token time 4.5

    Assignment time 17.29999

    Multiplication time 8.69999


    Die 1000 Stellen Pi in ca. 1110 Sekunden (das Bild ist schon wieder weg ;) )


    Der Uno32 sollte wegen Speichermangel nicht mal 100 Stellen rechnen :(
    (Hatte ihn wegen der Speicherkarte auf 3072 Bytes begrenzt/begrenzen muessen)


    slenz obwohl ich ein

    #define ARDUINOTONEEMULATION

    hatte musste ich zum compilieren fuer den DUE folgenden Bereich loeschen in der hardware-adruino.h:


  • Hab grad gemerkt, dass das Unsinn ist. Das ist die Non Blocking I/O die den Rechner ausbremst. Das ioctl wird viel zu oft aufgerufen und bremst die Loop. Muss mir das später ansehen.

  • Oh jeh! Das hat sich beim XMC Port eingeschlichen. Wird Zeit für einen großen Code Check.

  • slenz auf dem Arduino DUE habe ich das Phaenomen, dass er beim ersten RUN-Versuch die letzte Zeile eines Programmes an"meckert" mit Unknown Line Error

    Dies habe ich auch, wenn ich mit Standard -Os compiliere anstatt -O3

    Auf dem Pico , VGA32 und Windows/Linux ist mir dies nicht aufgefallen.


    Beim 2ten RUN laeuft dann das gleiche Programm - ohne Aenderung - einwandfrei.


    Hier mal Testausgaben von 2 Programmen:

  • Auf dem PicoW hatte ich bei Nutzung von unsigned int Warnings beim compilieren (convert to int o.ae.)


    und beim Start des CalcPi-Basic-Programm auf einmal ein OUT OF RANGE


    Ich habe jetzt erstmal wieder auf unsigned short zureuckgestellt und per

    #define MEMSIZE 65535

    den Speicher hart eingestellt.


    Getestet habe ich mal die Heap-InfoFunktionen vom arduino-core fuer den Pico:


    Im Source bei der mgreet-Ausgabe:




    dann gibt es folgende Ausgabe beim Boot:

    Stefan's Basic v1.4 on RPi PicoW - Memory 65535 0

    Total-Heap: 125028 Bytes

    Used -Heap: 96 Bytes

    Free -Heap: 124932 Bytes

    >


    Das kommt ja hin, da ich mit unsigned int ungefaehr 184KB allokieren konnte, auch wenn es dann Ausfuehrungsprobleme gab.

  • Hab den DUE Code gerade angeschaut und geändert. Das müsste jetzt wieder direkt durchkomplierbar sein. Es hat sich ein #ifdef Fehler eingeschlichen. Auf dem DUE wird jetzt per Default die Tone-Emulation gesetzt.


  • Das Out of Range schaue ich mir auch mal an. Das heisst, dass irgendwo bei den types noch Inkonsistenzen sind. Der Interpreter muss absolut skalierbar sein, was die Typen angeht.

  • Das checke ich auch nochmal. Das liegt sicher an einem Mechanismus beim Speichermanagement.

  • Hab den DUE Code gerade angeschaut und geändert. Das müsste jetzt wieder direkt durchkomplierbar sein. Es hat sich ein #ifdef Fehler eingeschlichen. Auf dem DUE wird jetzt per Default die Tone-Emulation gesetzt.

    Ja - compiliert auf dem DUE jetzt wieder ohne Probleme.
    Zusaetzlich zum default habe ich es als Erinnerung auch als

    #define ARDUINOTONEEMULATION

    in mein DUEPLAIN HW-Profil eingetragen ;)

  • Ich kann das nicht nachvollziehen. Läuft bei mir einwandfrei schon beim ersten Run. Hast Du andere Programme auch ausprobiert?


    PS: 1391 secs für 1000 Stellen PI.

    Einmal editiert, zuletzt von slenz ()

  • Ich kann das nicht nachvollziehen.

    Läuft bei mir einwandfrei schon beim ersten Run.

    Hast Du andere Programme auch ausprobiert?

    slenz Ich habe es mal sogar auf 2 PRINT-Statements runtergebrochen.

    Habe ich nur ein 10 PRINT "Zeile 1" klappt es.

    Kommt aber auch nur eine 2te PRINT Zeile hinzu, dann gibt es den LINE ERROR:


    Wie Du siehst, klappt es beim 2ten RUN.

    Zusaetzlich habe ich rausgefunden habe ich, dass es nur direkt nach dem Einschalten oder RESET passiert.

    D.h. wenn ich mit NEW den Speicher loesche (also initialisiere) und dann genau die selben PRINT Zeilen eingebe, startet es beim 1sten RUN ohne Probleme.


    Auch interessant finde ich, dass er nach dem Einschalten/Reset die erste Zeile abarbeitet und dann mit dem
    LINE ERROR auf die letzte Zeilennummer springt und so hier z.B. die Zeile 20 dazwischen auslaesst:



    Fuer ein Problem des fehlenden Speicher-Init beim Einschalten/RESET spricht auch, dass es beim ersten RUN klappt wenn man vor dem Programm eingeben oder laden des Programms ein NEW absetzt:


    Da mein DUE kein TFT hat, nehme ich nicht Dein Profil DUETFT, sondern habe folgendes als DUEPLAIN:

    Code
    /*
     * DUE Plain
     */
    #if defined(DUEPLAIN)
    #undef ARDUINOEEPROM
    #define ARDUINOSD
    #define ARDUINOTONEEMULATION
    #define SDPIN 4
    #endif
  • slenz Evtl. habe ich einen Unterschied in unserem DUE-compile gefunden?
    Welches BASIC-Set nutzt Du?


    Weil mit folgenden SETs habe ich den LINE ERROR:

    BASICFULL

    BASICINTEGER

    BASICSIMPLE


    aber bei folgenden SETs startet das 2-3 Zeilen Programm OHNE LINE ERROR:

    BASICMINIMAL

    BASICSIMPLEWITHFLOAT

    BASICTINYWITHFLOAT


    Mit BASICFULL klappt es aber wenn man in die Routine der Bootmeldung (mgreet) am Ende ein

    resetbasicstate(); einbaut:


    Code
    /* check if there is something to autorun and prepare 
            the interpreter to got into autorun once loop is reached */
         if (!autorun()) {
            printmessage(MGREET); outspc();
            printmessage(EOUTOFMEMORY); outspc(); 
            outnumber(memsize+1); outspc();
            outnumber(elength()); outcr();
            resetbasicstate();
         }

    2 Mal editiert, zuletzt von guidol ()

  • Kannst Du mal mit dem zwei Zeilen Programm ein


    Code
    DUMP 0,50


    machen? Mich würde interessieren, wie der BASIC Speicher aussieht. Ich habe das mit BASICFULL ausprobiert und es tritt kein Fehler auf. Das tokenisierte Programm müsste so aussehen:

    Code
    > dump 0,50
    0      -126   10     0      -118   -125   7      90     101
    8      105    108    101    32     49     -126   20     0
    16     -118   -125   7      90     101    105    108    101
    24     32     50     0      0      0      0      0      0
    32     0      0      0      0      0      0      0      0
    40     0      0      0      0      0      0      0      0
    48     0      0      0      0      0      0      0      0
    top: 26
    himem: 65534


    Mein Verdacht: Sonderzeichen wegen der Terminalemulation.


    Dann nochwas, hast du die neue Version? Ich teste grad im Zweig TinybasicArduino in meinem Repo. Ich habe auch Änderungen an basic.h eingecheckt. Kann sein, dass es daran liegt.

  • Code
    DUMP 0,50

    Mein Verdacht: Sonderzeichen wegen der Terminalemulation.

    Dann nochwas, hast du die neue Version? Ich teste grad im Zweig TinybasicArduino in meinem Repo. Ich habe auch Änderungen an basic.h eingecheckt. Kann sein, dass es daran liegt.

    slenz Mein DUMP 0,50 sieht 100% genauso aus wie Deiner (laut Compare-Funktion von Notpad++) ;)

    Sonderzeichen wuerde ich ausschliessen, da wenn ich nach NEW im selben Terminal das selbe Programm eingebe es laeuft.


    Ich nutze zur Zeit den IoTBASIC-Zweig vom 15.04.2023 (plus die NOTONEEMULATION).


    Ich habe mal tiefer gegraben, weil das BASICFULL laeuft, wenn man resetbasicstate(); in die autorun-check bzw. mgreet-routine anhaengt.

    So habe ich die Sourcezeilen vom resetbasicstate(); einzeln getestet und es hat sich gezeigt, dass alles in Ordnung ist, wenn von der resetbasicstate-Funktion NUR folgender Teil an die die autorun-check bzw. mgreet-routine anhaengt wird:

    Code
    /* switch off timers and interrupts */
    #ifdef HASTIMER
        resettimer(&after_timer);
        resettimer(&every_timer);
    #endif


    Das zeigt auch eine Uebereinstimmung mit den BASIC-Varianten, die bei mir den LINE ERROR brachten nach dem RESET, denn die haben alle ein

    #define HASTIMER

    und die BASIC-Varianten mit

    #undef HASTIMER

    brachten keinen LINE ERROR


    Ich denke es ist ein Timing-Problem - evtl. nur von meinem DUE-Clone, weil mir aufgefallen ist dass er scheinbar in die autorun-check-Routine laeuft um gleich darauf nochmal neu zu starten:


    DEBUG im autorun-check: