Jeszcze szybciej czyli proste kopiowanie – pojedynek C vs Action!

W sumie równolegle i bez umawiania się pomyśleliśmy wczoraj/dzisiaj rano z Perinoidem o jeszcze większym przyśpieszeniu generowania szachownicy kolorów – bo serio – ile może się rysować ekran w 80 kolomnowym trybie.

Ponieważ jestem ciekawy czy ktoś nie wymyśli czegoś lepiej – załączam dwa punkty startowe – kod w BASICu znaleziony przeze mnie w internecie, który przerobiłem na Action! i który Perinoid przerobił na C – może ktoś pokusi się o przerobienie tego na Pascala czy inny język w celach testów wydajnościowych 🙂

10 REM * DISPLAY ALL 256 COLORS
11 GRAPHICS 9:FOR V=0 TO 15:COLOR V
12 FOR N=0 TO 4:PLOT 5*V+N,0
13 DRAWTO 5*V+N,191:NEXT N:NEXT V
14 D=256*PEEK(561)+PEEK(560)
15 FOR N=1 TO 16:READ V:POKE D+V,143
16 NEXT N:FOR N=0 TO 25:READ V
17 POKE 1664+N,V:NEXT N:POKE 512,139
18 POKE 513,6:POKE 546,128:POKE 547,6
19 POKE 53248,40:POKE 53249,208
20 POKE 53261,255:POKE 53262,255
21 POKE 53266,0:POKE 53267,0
22 POKE 54286,192
23 GOTO 23
24 DATA 16,28,40,52,64,76,88,102,114
25 DATA 126,138,150,162,174,186,198
26 DATA 8,72,169,0,133,203,104,40,76
27 DATA 95,228,8,72,165,203,24,105,16
28 DATA 141,26,208,133,203,104,40,64

Perinoid sklecił szybki kod w C – gdzie kluczem prędkości jest memcpy kopiujące 40 bajtów (czyli jedną linię grafiki) zamiast wolnego Poke. Jest szybko, bardzo szybko. 0,1 sekundy na całość kodu. Nagrywając na 30fps widzę to w trzech ramkach od początku liczenia do pełnego wyświetlenia.

// powiel 40 bajtow cc65
    for(i=1; i<192; i++) {
        memcpy(ptr, video_ptr, 40);
        ptr+=40;
    }

W Action! to samo robi moveblock(gdzie,skąd,ile) – zabawne, bo procedura ta nie przenosi (move) tylko kopiuje – dokładnie na tej samej zasadzie jak memcpy.

; powiel 40 bajtow Action
FOR i=1 TO 192
   DO
          moveblock(ptr, video_ptr, 40)
          SCREEN==+40
   OD

Jednak ta sama operacja zajmuje… 0.2 sekundy – dwa razy dłużej niż w C.

Odpowiedź jest oczywiście w asemblerze – biblioteki do CC65 są bardzo mocno zoptymalizowane pod kątem wydajności i tak też jest z kodem memcpy. Czyli przydało by się też coś takiego pod Action!

Ciekawe rzeczy napisał o tym Tebe na atari.org.pl, a Kuba Husak potwierdził – dalsze zwiększenie wydajności to pchanie tematu w asm i pisanie własnej procki… do kopiowania prostokątów (bo w sumie docelowo mi o to chodziło).

Póki co moje wypociny w Action! (tak wiem – Graphics i Poke do wywalenia po zastąpieniu swoimi prockami i DL).

; DISPLAY ALL 256 GTIA COLORS

INCLUDE "RUNTIME.ACT"

BYTE ch=764			; odczyt klawiatury
BYTE RTCLOCK=20  	; sys timer do pomiaru szybkosci kodu

BYTE SAVMSCL=88 	; lsb of screen addr
BYTE SAVMSCH=89 	; msb

BYTE ARRAY TAB01 = [16 28 40 52 64 76 88 102 114 126 138 150 162 174 186 198]
BYTE ARRAY TAB02 = [8 72 169 0 133 203 104 40 76 95 228 8 72 165 203 24 105 16 141 26 208 133 203 104 40 64]
BYTE ARRAY TAB03 = [
$00 $00 $01 $11 $11 $22 $22 $23 $33 $33 
$44 $44 $45 $55 $55 $66 $66 $67 $77 $77 
$88 $88 $89 $99 $99 $AA $AA $AB $BB $BB 
$CC $CC $CD $DD $DD $EE $EE $EF $FF $FF
]

BYTE V, N
CARD DLIST, SCREEN

PROC SHOW256COLORS ()

; RTCLOCK=0					; potrzebne gdy mierzymy czas wykonania

GRAPHICS (9)

SCREEN=SAVMSCL+256*SAVMSCH	; adres pamieci ekranu

FOR V=0 TO 191
	DO
          moveblock(SCREEN, tab03, 40)
          SCREEN==+40
	OD

; gdzie jest DisplayList???

DLIST=256*PEEK(561)+PEEK(560)	; wychodzi, ze pod adresem 41014 w ACTION

; ponizsza petla laduje kod 143 w displayliste co 12 bajtow 
; co robi ten kod - to jest najstarszy bit opcodu Antic'a - włącza przerwanie DLI

FOR N=0 TO 15
   DO
       V=TAB01(N)
       POKE (DLIST+V,143)
   OD

; ponizsza petla laduje 26 bajtowy kod asm z tablicy nr 2, kod asm zalaczam na koncu

FOR N=0 TO 25
   DO
       V=TAB02(N)
       POKE (1664+N,V)
   OD
   
POKE (512,139)         ; 1675 ($68B) - punkt B w kodzie asm
POKE (513,6)           ; pod tym adresem definiujemy wektor przerwań DLI

POKE (546,128)         ; 1664 ($680) - punkt A w kodzie asm
POKE (547,6)           ; VVBLKI - VBLANK immediate register

; ponizsze ustawiają duszki jako dwie maski po bokach ekranu

POKE (53248,40)        ; (W) Horizontal position of player 0
POKE (53249,208)       ; (W) Horizontal position of player 1
POKE (53261,255)       ; (W) Graphics shape for player 0
POKE (53262,255)       ; (W) Graphics for player 1
POKE (53266,0)         ; (W) Color and luminance of player and missile 0 (704)
POKE (53267,0)         ; (W) Color and luminance of player and missile 1 (705)

; na koniec włączamy przerwania NMI
; Non-maskable interrupt (NMI) enable
POKE (54286,192)       

; jezeli mierzymy czas wykonywania kodu to ponizsze dwa do odmarkowania

;PRINTE("czas wykonania")
;PRINTB(RTCLOCK)

; moja tradycyjna koncowka kodu
; LOOP + ESC odczytujemy z rejestru ch czy nacisnieto klawisz ESC, jezeli tak to wyjscie z petli

WHILE ch<>28 
   DO
   OD

RETURN

; kod asm wywylywany przerwaniami:
; ASM PART A			; PHA
				; LDA #$00
				; STA $CB
				; PLA
				; PLP
				; JMP $E45F ; SYSVBV
; CZESC B			; PHP  
				; PHA
				; LDA $CB
				; CLC
				; ADC #$10
				; STA $D01A ; COLBK
				; STA $CB
				; PLA
				; PLP
				; RTI

Na koniec oczywiście refleksja – Action! był reklamowany jako najszybszy język 8bitowy. W tamtych czasach – to pewnie była prawda, poza assemblerem mało co mogło się z nim równać. Co więcej – chyba był planowany do sportowania na Apple II i Commodore 64 – szkoda, że do tego nie doszło. Musiało być to jednak mocno zaawansowane w planach, bo jeżeli wczytacie się w treść poniższej reklamy to:

“OS/A+ the first and finest operating system for BOTH Atari and Apple II computers is NOW included FREE as a part of every OSS systems software package.”