Martin Backschat, Michael Bernards, Arnd Beißner
Bei vielen komplizierten Funktionen sollten Sie sich überlegen, diese zu berechnen und die Ergebnisse als Tabellen anzulegen. Ein Beispiel ist der Sinus. Hier empfiehlt es sich, anstatt aufwendigem Rechnen, den Wert schnell aus einer Tabelle auszulesen.
Stack reinigen: Benutzen Sie statt ADDA.L #Bytes,A7 (14,6) bei bis zu 8 Byte ADDQ.L #Bytes,A7 (8,2) sonst LEA Bytes(A7),A7 (8,4)
Umrechnen vom Intel- ins Motorola-Format. Wort steht in Dß:
ROR.W#8,D0.
Langwort steht in D0:
ROR.W#8,D0,SWAP D0, ROR.W#8 D0.
Die PC-Relative Adressierung ist 20 bis 30 Prozent schneller und um den selben Faktor kompakter als die direkte Adressierung.
Da man die PC-Relative Addressierung nicht im Zieloperanden verwenden kann, laden Sie bei vielen Zugriffen die Adresse in ein Adressregister und adressieren relativ.
PEA Adresse (20,6)
ist schneller als MOVE #Adresse,-(A7) (24,6).
LEA Adresse, An (12,6)
ist schneller als MOVEA #Adresse,An (20,6).
Bei Sprüngen größer 32 KByte BRA und BSR statt JMP und JSR verwenden. Bei sehr kurzen Sprüngen (kleiner 128 Byte) BRA.S und BSR.S einsetzen.
Die Datenübergabe an Unterprogramme führen Sie schneller über Register statt über den Stack durch.
CMPI #0,<ea> ersetzen durch TST <ea>.
Datenbereiche, die nicht initialisiert werden müssen ( z.B. Sektorpuffer), sollten ins BSS-Segment gelegt werden. Dort benötigen sie zwar genausoviel Speicher, aber auf Diskette sind die Programme kürzer, lassen sich deshalb schneller laden und sparen Platz.
Die schnellste Speicherverschiebe-Routine bedient sich des MOVEM-Befehls. Bei 14 Registern lassen sich mit zwei Befehlen in 244 Taktzyklen so 56 Bytes verschieben. Die normale Routine benötigt dafür mehr als 400 Taktzyklen.
Vergleiche mit Konstanten kleiner gleich 8 Byte sind schneller mit einem SUBQ-Befehl (4,2) als mit CMPI (8,4).
Zahlen zwischen -128 und +127 lassen sich mit einem MOVEQ dreimal schneller in ein Datenregister laden als mit MOVE.
Um einen Wert zu einem Adreßregister zu zählen, verwenden Sie statt
add.l/sub.l #,Ax
für kleine Werte
addq.l/subq.l #,Ax
und für größere
lea.l #(Ax),Ax
Bei Aufrufen des TOS reicht es vollkommen aus, sich die Register D0,D1,A0,A1 zu merken. Der Rückgabewert wird immer in DO übergeben. Einen Fehler testen Sie folgendermaßen:
trap #.. ;TOS-Call
tst.l d0
bmi AnError
Zum Löschen von Datenregistern verwenden Sie den Befehl
moveq #0,Dx
statt clr.l Dx
Im Interpreter sollten Sie FOR-NEXT-Schleifen vermeiden und durch REPEAT-UNTIL oder WHILE-WEND-Konstrukte ersetzen. FOR-NEXT-Schleifen arbeitet der Interpreter langsamer ab, als die anderen Schleifenarten. Für den Compiler ist dies allerdings ohne Bedeutung.
Integer-Variablen sollten Sie immer benutzen, wenn keine Fließkommazahlen nötig sind. Es ist unwichtig, ob Wort-Integer (Suffix &) oder Langwort-Integer (Suffix %) verwendet werden, da kaum ein Unterschied besteht.
Die neue Version von GFA-Basic, 3.0, bietet spezielle Arithmetikbefehle für Integerzahlen (SUB,ADD,etc.). Diese sind erheblich schneller als die normalen Operanden (+,-,etc.).
Zum Speichern und Laden von ASCII—Texten empfehlen sich die Befehle STORE und RECALL. Sie sind um ein Vielfaches schneller als die Befehle PRINT# und INPUT#.
In der neuen Version des GFA-Basic gibt es zum schnellen Zeichnen von Bezierkurven den »Curve«-Befehl. Dieser nimmt vier Punktkoordinaten als Parameter.
Für die anderen Sprachen finden Sie einen schnellen Algorithmus von Bezierkurven im Expertenforum der kommenden Ausgabe.
Statt eine Datei in kleinen Stücken zu laden, nehmen Sie größere Blöcke, die Sie im Speicher trennen, denn Dateizugriffe sind allgemein sehr langsam.
Wenn in eine Datei häufiger geschrieben oder aus ihr gelesen werden muß (z.B. Adressverwaltung) ist es besser, die Datei zu Beginn größer zu dimensionieren, um dann in die vorhandene Datei zu schreiben. Das GEMDOS vor der TOS-Version 1.4 sucht sehr lange nach freien Sektoren.
Wenn bei einem Fenster—Redraw Slider oder Pfeile betätigt werden, zeichnet man nicht das ganze Fenster neu, sondern verschiebt es mit der Copy-Raster—Funktion und zeichnet dann nur die neuerscheinenden Bereiche. Bei Fenstern, die viele Objekte enthalten, ist der Geschwindigkeitsgewinn beträchtlich.
Beim Lesen und Schreiben von Sektoren (Rwabs, Floprd, Flopwr) sparen Sie Zeit indem Sie darauf achten, daß die Pufferadresse gerade ist.
Die Zeichenausgabe unter BIOS (Bconout) ist erheblich schneller als die entsprechenden Routinen des GEMDOS (Cconout). Nachteil: Die BIOS—Routinen lassen sich nicht auf andere Geräte umleiten und erfordern deshalb eventuell mehr Programmieraufwand.
Gibt man Zeichenketten auf den Bildschirm aus, die ASCII-Code 0 enthalten, benutzt man Fwrite mit Handle 1, da Cconws bei dem Auftreten von ASCII 0 abbricht.
Um die schnellstmöglichen Grafikroutinen für die Grundfunktionen wie Linien, Kreise, Füllen, Speicherverschiebungen und Textausgaben zu nutzen, greifen Sie auf die Line—A-Routinen des Betriebsystems zu, statt über GDOS und dem VDI zu arbeiten. Bibliotheken zum Ansprechen der Routinen sollten für jede Sprache zur Verfügung stehen.
Am besten eine andere Sprache benutzen.
Um eine Struktur mit vielen Flags kleiner und handlicher zu gestalten, bietet es sich an, mit Bitfeldern zu arbeiten.
struct beispiel {
char flag1,flag2,flag3;
int color;
/* Werte 0-7 */ };
kann auch als
struct beispiel
{
unsigned int flag1 : 1;
flag2 : 1;
flag3 : 1;
unsigned int color : 3;
};
geschrieben werden und belegt nur noch 2 statt vorher 5 Byte.
Variablen, Strukturen, Zeiger etc., die gedanklich zusammengehören, können Sie der Übersicht halber als Struktur zusammenfassen. Dies hat einen Riesenvorteil: Das Kopieren aller Komponenten an eine andere Stelle geschieht mit einem Befehl und wird vom Compiler höchst optimiert:
int datensatz_nr;
char *datensatz_text;
long seriennummer;
fassen Sie zusammen zu:
struct Datensatz
{ int datensatz_nr;
char *datensatz_text;
long seriennummer};
und können nun diese Struktur mit «datensatz2 = datensatz1» kopieren.
Wenn nicht unbedingt nötigt, auf PRINTF, SCANF und deren Verwandten verzichten, da die meisten Compiler bei diesen Routinen eine große Library in das Programm binden und die Routinen langsamer sind als die des TOS.
Bei einigen C-Compilern (z.B. Megamax 1.1) lohnt es sich, Bibliotheksroutinen, die zwar miteingebunden, aber nicht benötigt werden, durch Dummies zu ersetzen.
Verwendet man bei C die Standard-Dateiroutinen wie FOPEN, nimmt der Code um einiges an Umfang zu. Die Systemroutinen FOPEN etc. reichen oft aus.
Um bei einer Float—Integer-Wandlung den Rundungsfehler zu minimieren, gibt es einen Trick: »Aint = (int) (Afloat+0.5)«
In Kopierschleifen ersetzen Sic die Array Index-Adressierung (carray[i] = darray[j]) durch die direkte Adressierung (*carray++ = *darray++).
Um einen größeren Speicherblock in C zu verschieben, bietet es sich an, Strukturen zu kopieren. Dabei optimiert der C-Compiler automatisch. 1000*4 Byte lassen sich etwa folgendermaßen kopieren:
struct BYTE4000
{ long long4[1000] };
*src_block,*dest_block;
src_block =...;
dest_block = ...;
*dest_block = *src_block;
Bei zeitkritischen Routinen benutzen Sie Registervariable. Hierbei werden die C-Variablen in den Prozessorregistem abgelegt. Beispiel:
register char *adr_pointer;
register long long_value;
Um Kopierschleifen mit komplizierten Arrays zu optimieren, kopieren Sie statt mit den Arrays mit Zeigern auf diese. Beispiel:
src_ptr= &src_array[i][0];
dest_ptr = &dest_array[j][0];
*src_ptr++ =*dest_ptr++;
Bei vielen Zugriffen auf eine Datei verwenden Sie die gepufferten Dateifunktionen »fopen«, »fclose«, »fseek«, »fread«, »fwrite« und »fflush« etc. Damit wird Ihre Datei im Speicher gepuffert. Die Puffergröße stellen Sie mit der Funktion »setbuf« ein.
Zum schnellen Datensortieren verwenden Sie die Bibliotheksroutine »qsort«. Sie basiert auf dem schnellen Quicksort-Algorithmus.
Der Multiplikationsbefehl »mulu« benötigt viel Zeit. Multiplikationen mit kleinen Werten optimieren Sie durch »add« und »lsl«. Beispiele:
D0 x 2: lsl.l #1,d0
D0 x 3: move.l d0,d1
add.l d1,d1
add.l d1,d0
D0 x 4: lsl.l #2,d0
D0 x 5: move.l d0,d1
lsl.l #2,d1
add.l d1,d0
Um auch ohne die langsamen Gleitkommazahlen schnell aber genau zu rechnen, teilen Sie ein Langwort (32 Bit) in zwei 16 Bit-Worte auf (Hi- und Lo-Wort). Das Hi-Wort deklarieren Sie dabei als Vorkomma— und das Lo-Wort als Nachkommawert. Zwei Zahlen (1.0 und 5.5) addieren Sie beispielsweise folgendermaßen:
move.l #$00010000,d0 ; 1.0
add.l #$00058000,d0 ; +5.5
Runden können Sie die Zahl mit
add.l #$00008000,d0 ; +0.5
clr.w d0
swap d0 ; Vorkomma
Um eine Zahl durch eine Zweierpotenz zu dividieren, benutzen Sie die Bitschiebebefehle:
D0 / 2: lsr.l #1,d0
D0 / 4: lsr.l #2,d0
D0 / (2 ^ 17): clr.w d0
swap d0
lsr.w 01, d0
Um Routinen im Supervisor auszuführen, eignet sich die XBIOS—Routine »Supexec(void *prgptr)«. Damit sparen Sie sich den lästigen Moduswechsel mit »Super(stack)« und Zeit.
Bei verschachtelten FOR-NEXT-Schleifen plazieren Sie die größte Schleife als die innerste Schleife.
Um die Geschwindigkeit bei Berechnungen zu beschleunigen, sperren Sie mit »or.w #$700,sr« in Assembler oder »IPL 6« in Omikron-Basic alle Interrupts.
In Omikron-Basic ist die Integer-Division vorzuziehen (»\« statt»/«). Diese teilt zwei Integerwerte extrem schnell.
Bestimmte Rechenfunktionen bewirken automatisch, daß die gesamte Berechnung mit Fließkommazahlen durchgeführt wird. Um in das Integer-Format zurückzukehren, verwenden Sie den Befehl CINT. Beispiel:
A=CINT (SQR(I))+J-I*3
Ohne CINT würde die komplette Formel mit Fließkommazahlen berechnet und mehr Rechenzeit beanspruchen.