Optimieren des erzeugten Programmcodes

Wenn Sie ein BASIC-Programm entwickelt haben und es endgültig compilieren möchten, um es als fertiges Maschinencodeprogramm verwenden zu können, sollte der erzeugte Maschinencode optimiert sein. Unter einem optimalen Maschinenode versteht man, dass der Code so kurz bzw. so schnell, d.h. eine so hohe Ausführungsgeschwindigkeit besitzt, wie nur möglich ist. Aufgrund der internen Arbeitsweise des Compilers, der ja den Quelltext nach vorgegebenen Regeln übersetzt, und aufgrund der Tatsache, dass der Compiler die Semantik des Programms nicht kennt, kann ein Compiler nie so optimalen Maschinencode erzeugen wie ein Mensch, der das Programm gleich in Maschinencode bzw. Assembler entwickelt. Trotzdem ist es der Anspruch eines Compilers, optimierten, d.h. dem Optimum möglichst nahe kommenden Programmcode zu erzeugen.

Der JKCEMU-BASIC-Compiler erzeugt lokal optimierten Programmcode, d.h., innerhalb einer Anweisung wird der Programmcode optimiert. Eine globale Optimierung, d.h. über Anweisungsgrenzen hinweg, erfolgt nicht. Dennoch weist der erzeugte Programmcode einen hohen Optimierungsgrad auf. Mit Hilfe der Compiler-Optionen können Sie Einfluss auf die Optimierung nehmen und so für noch besseren Programmcode sorgen. Die Wirkung der Optionen auf den erzeugten Programmcode können Sie selbst sehen, indem Sie die Option Erzeugten Assembler-Quelltext anzeigen einschalten.

Den schnellsten und meistens auch kürzesten Programmcode erhalten Sie, wenn in der Gruppe Laufzeiteigenschaften die Optionen ausgeschaltet und in der Gruppe Sonstiges die Optionen eingeschaltet werden.

Die letzten beiden genannten Optionen in der Gruppe Sonstiges können aber in bestimmten Situationen zu fehlerhaftem Programmcode führen. Aus diesem Grund werden sie nachfolgend ausführlich behandelt, damit Sie wissen, in welchen Situationen die Optionen ausgeschaltet werden müssen.

1. FOR/NEXT als strukturierte Schleife übersetzen

BASIC ist keine strukturierte Programmiersprache. Mit der GOTO-Anweisung kann man zu jeder beliebigen Stelle springen. Auch die FOR/NEXT-Schleife ist nicht strukturiert. Auf eine FOR-Anweisung wird immer das im Programmablauf und nicht das im Quelltext folgende NEXT als Ende der Schleife angesehen. Folgendes Beispiel demonstriert diesen Fall:

100 PRINT "1. SCHLEIFE"
110 FOR I=1 TO 5
120 PRINT I
130 NEXT I
140 IF I<1 GOTO 180
150 PRINT "2. SCHLEIFE"
160 FOR I=5 TO 1 STEP -1
170 GOTO 120
180 PRINT "FERTIG"

Das kurze BASIC-Programm ist zwar unstrukturiert und damit auch unübersichtlich, aber inhaltlich völlig korrekt. Die zweite FOR-Schleife in Zeile 160 benutzt einfach Teile der ersten Schleife mit, und zwar den Schleifeninhalt (PRINT-Anweisung in Zeile 120) und das Schleifenende (NEXT-Anweisung in Zeile 130). Die eine NEXT-Anweisung schließt somit zwei Schleifen ab. Ja, das ist in BASIC erlaubt! Folglich muss der Compiler die FOR-Schleife so übersetzen, dass alle relevanten Schleifeninformationen auf den Stack gelegt werden, damit sie bei der NEXT-Anweisung zur Verfügung stehen. Konkret betrifft das vier Werte:
  1. Adresse der Schleifenvariable
    Nun gut, man kann einwenden, dass die Schleifenvariable bei der NEXT-Anweisung mit angegeben ist, aber NEXT ist auch ohne Angabe einer Variable erlaubt. Und schon ist nicht mehr sicher, ob bei der im Programmablauf nächst folgenden NEXT-Anweisung, die ja die FOR-Schleife abschließt, eine Variable angegeben ist. Und wenn ja, ist es auch die gleiche?
  2. Schrittweite
  3. Endwert
  4. Adresse des Schleifeninhalts, damit die NEXT-Anweisung an die richtige Stelle zurückspringen kann.

Mit vier Werten auf dem Stack wird ein relativ hoher Aufwand für die Schleife selbst betrieben. Insbesondere bei sehr kleinen Schleifen, wie z.B.

100 REM Z1013-BILDSCHIRM MIT BASIC-MITTELN LOESCHEN
110 FOR A=HEX(EC00) TO HEX(EFFF)
120 POKE A,32
130 NEXT A

ist die Ausführungszeit, die die Schleife für sich selbst benötigt, unverhältnismäßig groß zu der Zeit, die der Schleifeninhalt benötigt. Eine Schleife lässt sich wesentlich effizienter übersetzen, wenn klar wäre, dass eine NEXT-Anweisung zu genau einer FOR-Anweisung gehört. In dem Fall wären die meisten, wenn nicht sogar alle notwendigen Schleifeninformationen beim Compilieren bereits bekannt und müssten nicht erst vom Stack gelesen werden. Jeder strukturiert programmierende Softwareentwickler wird die FOR/NEXT-Schleife auch so anwenden, dass im Quelltext auf eine FOR-Anweisung auch eine NEXT-Anweisung folgt, die zu genau dieser einen FOR-Anweisung gehört. Und genau für diesen Fall gibt es die Option FOR/NEXT als strukturierte Schleifen übersetzen. Ist diese Option eingeschaltet, werden nur noch die variablen Werte (z.B. Schrittweite oder Endwert, falls diese in einer Variablen stehen) auf den Stack gelegt. Die konstanten Werte werden bei der NEXT-Anweisung fest kodiert und beziehen sich auf die zugehörige und im Quelltext vorher stehende FOR-Anweisung.

Zusammenfassung: Die Option erzeugt effizienteren Programmcode, darf aber nur eingeschaltet werden, wenn niemals mit GOTO aus einer FOR/NEXT-Schleife heraus oder in so eine Schleife hinein gesprungen wird und somit sichergestellt ist, dass jede NEXT-Anweisung zu genau einer FOR-Anweisung gehört. Erfüllt das BASIC-Programm diese Bedingungen nicht, muss die Option ausgeschaltet werden, da sonst ein sematisch anderer und somit inhaltlich falscher Programmcode erzeugt wird!

2. Relative Sprünge bevorzugen

Sprünge zu bestimmten Anweisungen werden als absolute Sprünge mit drei Bytes übersetzt, da beim Compilieren die Sprungdistanz nicht bekannt ist. Wird die Option Relative Sprünge bevorzugen eingeschaltet, übersetzt der Compiler die Sprünge, die wahrscheinlich nur eine kurze Sprungweite haben, mit relativen Sprungbefehlen, die nur zwei Bytes lang sind und auch schneller ausgeführt werden als absolute Sprüge.

Sprünge mit einer wahrscheinlich kurzen Sprungweite sind z.B. Sprünge von einer IF-Anweisung zum ELSE-Zweig bzw. zur nächsten Programmzeile, falls die IF-Bedingung nicht erfüllt ist.

In seltenen Fällen kann es vorkommen, dass die Sprungdistanz für einen relativen Sprung zu groß ist, z.B. wenn sehr viele Anweisungen in einer Programmzeile mit einer IF-Anweisung stehen. In dem Fall meldet dann der Assembler, der automatisch nach dem eigentlichen Compiler gestartet wird, "Relative Sprungdistanz zu groß". Für Sie bedeutet das nur, dass Sie die Option ausschalten müssen, wenn die besagte Fehlermeldung erscheint.