Soll ein Basic-Programm schneller werden, dann greift man einfach zum Basic-Compiler. Aber wie funktioniert ein solches Beschleunigungswunder?
Manche Anwendungen erfordern mehr Tempo, als reine Basic-Programme zu bieten haben. Dann schreibt man zeitkritische Routinen entweder in Maschinensprache oder setzt einen Compiler ein, der ein Basic-Programm in Maschinensprache umwandelt. Letzteres ist natürlich viel einfacher und bequemer, da es lediglich den Umgang mit dem Compiler vorraussetzt. Außerdem benötigt die Compilierung nicht viel Zeit. Das Compilat kann übrigens nicht mehr von Basic aus bearbeitet werden. Wenden wir uns nun der Funktionsweise des Turbo-Basic-Compilers zu.
Wenn Sie ein Basic-Programm eintippen, zum Beispiel die Programmzeile »100 ?3*4
«,
faßt der Computer diese Zeile zunächst nur als eine Folge von Zeichen
im ATASCII-Format auf. Unter dem Begriff ATASCII versteht man die
spezielle Atari-Version des ASCII-Zeichensatzes (ASCII ist die
Abkürzung für: American Standard Code for Information Interchange). Der
Interpreter wandelt den eingegebenen Text in Token um. Gleichzeitig
überprüft er noch die Syntax, also ob die Befehlsfolge auch zulässig
ist. Diese Aufgabe muß meistens auch ein Compiler übernehmen. Bild 1
zeigt, wie die Programmzeile »100 ?3*4
« in Token übersetzt aussieht.
Die
Programmzeile wird dann anhand ihrer Nummer einsortiert. Durch diese
Umwandlung in Token und gleichzeitiger Syntaxprüfung wird die Aufgabe
des Compilers etwas einfacher. Speichert man ein Basic-Programm mit dem
SAVE
-Befehl, werden auch die Token gespeichert. Mit LIST
hingegen liegt es in der Textform vor. Der Turbo-Basic-Compiler kann nur die »SAVE
-Ausführung«
übersetzen. Ein Transfer der TextForm wäre prinzipiell auch möglich,
allerdings würde der Compiler hierfür mehr Zeit benötigen. Schließlich
muß er die Befehlsnamen kennen und die Variablennamen speichern. Dies
würde weiterhin die maximale Länge der zu compilierenden Programme
vermindern. Oder es müßten Zwischenfiles auf Diskette angelegt werden,
was bei den relativ langsamen Atari-Diskettenlaufwerken sehr
zeitaufwendig ist.
Bevor
Sie ein Basic- oder Turbo-Basic-XL-Programm compilieren, sollten Sie
sicherstellen, daß es fehlerfrei läuft. Zwar ist dann immer noch nicht
gewährleistet, daß das Programm anschließend auf Anhieb ordnungsgemäß
compiliert und ausgeführt werden kann. Zumindest entfallen aber
Syntax-Fehler, die zur Folge hätten, daß Sie Ihr Basic-Programm
korrigieren müßten, um es anschließend nochmals zu compilieren. Auch
muß die Struktur Ihrer Basic-Programme stimmen. Dazu bietet sich der LIST
-Befehl unter Turbo-Basic XL an, der bekanntlich FOR
-NEXT
-Schleifen und IF
-ELSE
-ENDIF
Abfragen einrückt.
Atari-Basic und Turbo-Basic XL-SAVE
-Files
bestehen aus einem Vorspann mit den Variablennamen und einigen anderen
Informationen für den Interpreter. Diese werden deshalb vor dem
Einlesen entweder überlesen (wie die Variablennamen) oder, in einer
angepaßten Form, gespeichert. Dies betrifft in erster Linie die
Variablentypen. Der Variablentyp kann aber nicht nur aus der
Variablennummer, die im Programm gespeichert ist, definiert werden. Das
Dollarzeichen ($) beispielsweise, welches die Stringvariablen
kennzeichnet, wird vom Interpreter nicht im eigentlichen Programm
gespeichert.
Bei langen Programmen mit vielen Variablen kann man diese Phase der Compilation deutlich erkennen. In der Info-Zeile (der dritten Bildzeile des Turbo-Basic-Compilers) steht dann nur das Wort Zeile ohne Zeilennummer. Sobald die erste Zeile übersetzt wird, erscheint auch die Anzeige der entsprechenden Zeilennummer.
Jetzt
beginnt die eigentliche Compilierung. Sie setzt sich aus zwei
Durchgängen zusammen. Im ersten Durchgang (Paß 1) wird das Programm
Zeile für Zeile und Befehl für Befehl eingelesen und in Maschinencode
übersetzt. Im zweiten Durchgang (Paß 2) werden dann noch die offenen
Sprungadressen eingesetzt. Dazu werden im Paß 1 GOTO
-Sprünge
im Code durch einen ungültigen Maschinenbefehl mit angehängter
Zeilennummer gekennzeichnet. Im zweiten Durchgang schließlich ersetzen
dann 6502-JMP
-Befehle (Code $4C
) die ungültigen Maschinenbefehle.
Ähnliches
gilt für Zahlen und Textkonstanten. Sie werden in einem Block hinter
dem eigentlichen Basic-Programm gespeichert. Die endgültigen Adressen
stehen dann erst nach der Übersetzung fest. Im Anschluß an den zweiten
Compilier-Durchgang folgt dann noch eine Kontrolle, ob offene Schleifen
vorliegen. Also ob ein NEXT
zu einem FOR
fehlt oder ein WEND
zu einem WHILE
etc. Trifft dies zu, erscheint eine Meldung auf dem Bildschirm. Dann folgt noch die Überprüfung nach fehlenden ENDIF
s.
Durch diese Aufteilung der Fehlerprüfung (die meisten Fehler werden
bereits im ersten Durchgang festgestellt) werden die fehlerhaften
Zeilennummern nicht komplett sortiert ausgegeben, sondern
gegebenenfalls in vier einzelnen Gruppen.
Die meiste Arbeit wird im ersten Durchgang geleistet, der Rest dauert nur wenige Sekunden. Das EinLesen des Programms erfolgt Zeile für Zeile. Zuerst zur Zeilennummer: Wenn die Zeilennummer größer als 32767 ist, dann ist Paß 1 beendet (der Interpreter verwendet die Zeilennummer 32768 übrigens als Endekennzeichen). Dann schließt der zweite Durchgang an. Ansonsten wird die Zeilennummer angezeigt und die Zeile Befehl für Befehl eingelesen und übersetzt.
Sollte der Compiler auf einen LIST
- oder ENTER
-Befehl stoßen, wird einfach eine Fehlermeldung mit Zeilennummer ausgegeben, und der nächste Befehl kommt an die Reihe.
Keinerlei Probleme bereiten REM
- oder »--
«-Zeilen. Diese Befehle werden einfach überlesen und ignoriert, da sie den Programmablauf nicht beeinflussen.
Befehle ohne Parameter (wie END
, POP
oder CLR
) werden einfach zu einem entsprechenden Unterprogrammaufruf compiliert.
Bei einem DATA
-Befehl
wird der Text mit Zeilennummer und Länge in einer Tabelle im Anschluß
an das Programm eingetragen. Sie nimmt noch folgende Informationen auf:
Diese
Informationen werden gegebenenfalls auch im Speicher verschoben, wenn
das zu compilierende Programm oder eine der Tabellen anwächst. Deshalb
nimmt auch die Compilierungsgeschwindigkeit bei langen Programmen gegen
Ende ab; vor allem wenn viele DATA
s am
Anfang des Programms stehen. Diese Verlangsamung betrifft lediglich die
eigentliche Compilierung und nicht den Programmablauf.
Alle Befehle, die Parameter erfordern, sind wesentlich schwieriger zu übersetzen. Bild 2 zeigt nur einen Befehl mit einem einzigen Parameter.
Die Umwandlung des Befehls von Schritt 1 nach Schritt 2 hat der Interpreter schon erledigt. Der Compiler liest das Befehlstoken $03
für COLOR
und verzweigt in die entsprechende Compilationsroutine. Diese ruft
wiederum die Routine für die Übersetzung eines Ausdrucks auf, die eine
Integerzahl zurückliefern soll. Dann wird nur noch »STA $C8
« angehängt, und damit ist die Compilierung des Befehls schon erledigt. Beim Befehl GRAPHICS
würde »JSR @Graphics
« angehängt werden. Der Klammeraffe (@) kennzeichnet übrigens eine Routine in der Runtime Bibliothek.
Ein großer Fortschritt wurde bis jetzt noch nicht verzeichnet. Zwar sind die Befehle jetzt prinzipiell compiliert, das ändert aber leider nichts daran, daß mit dem entsprechenden Parameter, wie in unserem Beispiel die 1, noch nichts passiert ist.
Die dafür benötigte Routine gliedert sich in mehrere Abschnitte:
P-Code (Pseudo-Code) ähnelt der Maschinensprache eines hypothetischen (gedachten) Prozessors. Manche Compiler erzeugen nur einen P-Code, der dann von einem kleinen Interpreter ausgeführt wird. Die Abarbeitungsgeschwindigkeit eines solchen Codes ist zwar schneller als ein herkömmliches Basic-Programm, allerdings von der Geschwindigkeit einer »richtigen« Maschinensprache noch weit entfernt. Aber platzsparender ist P-Code im Vergleich zu voll compiliertem Basic. Echte Maschinensprache ist jedoch von keinem Compiler zu übertreffen.
Bild 3 zeigt ein komplizierteres Beispiel. »03
« entspricht wieder dem Token für COLOR
, »46
« steht für PEEK
, »3A
« für die offene Klammer, »2C
« für die schließende Klammer, »35
« steht für die Addition (+), »16
« bedeutet Zeilenende oder auch einen Doppelpunkt, der Basic-Befehle in einer Zeile trennt. Der Wert »OE
« kennzeichnet schließlich noch eine Zahl in der internen Darstellung (entspricht sechs Byte).
Das Token »03
«
ist in unserem Beispiel bereits identifiziert. Es beginnt jetzt der
Test, in welcher Reihenfolge die Operationen ablaufen müssen. Der
Interpreter hat dabei fast die gleichen Tests durchzuführen, und zwar
jedesmal, wenn er diese Zeile erreicht. Der Compiler übersetzt jede
Zeile nur einmal bei der Compilierung. Dadurch laufen die Programme
schneller ab.
Bei der Übersetzung in P-Code bedient sich der Compiler einer Tabelle, in der die Prioritäten aller Operatoren vermerkt sind. Übrigens handelt es sich hierbei fast um die gleiche Tabelle, die auch der Interpreter benutzt. Daraus ergibt sich dann folgender P-Code. In Bild 4 sind die einzelnen Werte lediglich in Kurzform dargestellt. Der Compiler betrachtet sie natürlich als Zahlen.
Jetzt könnte theoretisch schon die Routine aufgerufen werden, die den Maschinencode erzeugt. Allerdings würden dann im compilierten Programm die gleichen Berechnungen ablaufen, die der Interpreter normalerweise auch durchführt. Schließlich steht jetzt viel Zeit zur Optimierung des P-Codes zur Verfügung, da die Programmzeilen nur ein einziges Mal in Maschinencode umgesetzt werden.
2 parte