Schneller Drawline-Algorithmus

Basierend auf der Pascal-Routine aus dem Artikel "Schneller Drawline-Algorithmus" habe ich diese umgesetzt in eine Routine für Assembler. An dieser Stelle möchte ich die Entwicklung von Pascal (Version 3.01) auf eine optimierte Assembler-Routine beschreiben.

Schritt 1: TURBO-Pascal Quelle

[Listing des verwendeten TURBO-Pascal Programms]

Das Programm zeichnet eine Gerade von X=100 bis 200 und Y=10 bis 20. Die Routine plot ist nicht implementiert, da diese speziell von der Videodarstellung der verwendeten Maschine abhängig ist.

Schritt 2: Disassemblierter Hauptteil

[Listing des disassemblierten TURBO-Pascal Programms]

Der Quelltext ist dargestellt ohne die Laufzeitbibliothek, also ab Adresse 20E2h. Der erste freie Speicherplatz liegt bei 234Eh (Symbol HEAP). Die eigentliche Routine beginnt bei 2110h (Symbol Line) und endet bei 2334h (Symbol MAIN). Das ergibt eine Länge der Routine von 548 Bytes.

Schritt 3: Optimierte Quelle

[Listing des optimierten Assembler-Programms]

Die Optimierung bezog sich im Wesentlichen auf das Umgehen der folgenden Laufzeitroutinen:
LaufzeitroutineOriginalcodeOptimierter Code
Integervergleich <>
ld      hl,(v.x)     ; Laden x
push    hl
ld      hl,(ende)    ; Laden ende
pop     de
call    .NE.         ; Test x <> ende
bit     0,l
jp      z,EndeGerade ; x = ende
ld      hl,(v.x)     ; Laden x
ld      de,(ende)    ; Laden ende
or      a
sbc     hl,de        ; Test x <> ende
ret     z            ; x = ende
Integervergleich >
ld      hl,(v.e)     ; Laden e
push    hl
ld      hl,0
pop     de
call    .GT.         ; Test e > 0
bit     0,l
jp      z,Gerade1add ; e <= 0
ld      hl,(v.e)     ; Laden e
dec     hl
rl      h            ; Test e > 0
jr      c,Gerade1add ; e <= 0
Integervergleich <
ld      hl,(dx)      ; Laden dx
push    hl
ld      hl,0
pop     de
call    .LT.         ; Test dx < 0
bit     0,l
jp      z,spiegelX   ; dx >= 0
ld      de,(dx)      ; Laden dx
bit     7,d          ; Test dx < 0
jr      z,spiegelX   ; dx >= 0
Integervergleich <
ld      hl,(dx)      ; Laden dx
push    hl
ld      hl,(dy)      ; Laden dy
pop     de
call    .LT.         ; Test dx < dy
bit     0,l
jp      z,keintausch ; dx >= dy
ld      hl,(dx)      ; Laden dx
ld      de,(dy)      ; Laden dy
or      a
sbc     hl,de        ; Test dx < dy
jr      nc,keintausch; dx >= dy
Integer nach links schieben
ld      hl,(dy)     ; Laden dy
push    hl
ld      hl,1
pop     de
call    .SHL.
ld      (v.a),hl     ; a=dy * 2
ld      hl,(dy)      ; Laden dy
add     hl,hl
ld      (v.a),hl     ; a=dy * 2

Weitere Optimierungen sind z.B.:
OriginalcodeOptimierter Code
ld      hl,(x2)      ; Laden x2
push    hl
ld      hl,(x1)      ; Laden x1
pop     de
ex      de,hl
or      a
sbc     hl,de
ld      (dx),hl      ; dx=x2-x1
ld      hl,(x2)      ; Laden x2
ld      de,(x1)      ; Laden x1
or      a
sbc     hl,de
ld      (dx),hl      ; dx=x2-x1
ld      hl,(dx)      ; Laden dx
ld      a,l
cpl
ld      l,a
ld      a,h
cpl
ld      h,a
inc     hl
ld      (dx),hl      ; Negieren dx
ld      de,(dx)      ; Laden dx
ld      hl,0
or      a
sbc     hl,de        ; Negieren dx
ld      (dx),hl

Die eigentliche Routine beginnt hier ebenfalls bei 2110h (Symbol Line) und endet nun bei 2297h (Symbol MAIN). Das ergibt eine verkürzte Länge der Routine von 391 Bytes, also etwa 29 % weniger Code.

Schritt 4: Assembler-Modul

[Listing des modularen Assembler-Programms]

Das Modul weist eine nochmals verkürzte Code-Länge von 351 Bytes aus, das ist 36 % weniger gegenüber dem Originalcode.
Bei TURBO-Pascal erfolgt die Übergabe der Parameter bekanntlich über den Stack. Die Routine "plot" ist maschinenabhägig und kann nicht generell implementiert werden. Für das Modul habe ich den Aufruf für die Routinen wie folgt definiert:
Entry "Line"Extern "plot"
  • X-Startwert in Register HL
  • Y-Startwert in Register DE
  • X-Endwert in Register HL'
  • Y-Endwert in Register DE'
  • Farbe im Akku
  • X-Punktwert im Register HL
  • Y-Punktwert im Register DE
  • Modus im Akku
Die Routine "plot" für den JOYCE wird grundsätzlich besprochen z.B. in Turbo-Grafik für den JOYCE. Dort ist der Bereich für X=0..719 und Y=0..255. Modus ist 0, wenn der Bildpunkt gesetzt, 1, wenn er gelöscht, 2, wenn er umgeschaltet und 3, wenn er getestet werden soll. Im Testfall ist bei gesetztem Bit der Akku 0FFh und die Carry-Flag ebenfalls gesetzt. Bei nicht gesetztem Bit ist der Akku 000h und die Carry-Flag nicht gesetzt. Ich habe diese Routine entsprechend angepasst.
[Listing des maschinenabhängigen Assembler-Programms]
Mit dem folgenden Testprogramm wird eine Linie von oben links nach unten rechts über den Bildschirm gezeichnet.
[Listing des Testprogramms]