Bol som zvedavý, jednak ako bude mať tá ručne vyčačkaná ASM frame copy implementácia vo vzťahu k naivným C-čkovým a dvak, ako to pobeži na reálnom hw.:
https://pastebin.com/6fu6Bi7D
Prekladal som to prekladačom z TC++ 3.0 v DOSBoxe, pre porovnanie je tam inlajnutá aj tá Pavlova implementácia.
Na najstaršom funkčnom PC čo mám momentálne po ruke (Amstrad PPC 640, NEC V30 @ 8Mhz, 1987) to vyzerá takto:
C assign b far: 3.4 fps, 0.2 Mpxps C assign w far: 5.2 fps, 0.3 Mpxps C assign l far: 5.7 fps, 0.4 Mpxps C assign w near: 5.5 fps, 0.3 Mpxps C assign l near: 6.4 fps, 0.4 Mpxps C fmemcpy: 19.2 fps, 1.2 Mpxps ASM movsw: 22.3 fps, 1.4 Mpxps
(fmemcpy je interne implementovaná tiež pomocou rep movsw, len teda šaškovanie so zásobníkom pri jej volaní zožerie nejaké cykly na každý riadok)
V30 je upgradnutá 8086, ak by to niekto chcel testnúť na niečom ešte bližšom pôvodnému XT s 8088 @ 4,77MHz, môžem posunúť binárku.
Díky za doplnění!
Jak vlastně vypadá výstup v assembleru z toho TC++? Podle benchmarků to nebude nic moc (to mě překvapuje popravdě). Nepomůžou nějaký triky typu modifikátor "registry", přepis smyček tak, aby se explicitně pracovalo přes pointry (a ne jakoby šlo o pole) atd.?
(je asi blbé, že se ptám. TC jsem hodně používal, učil jsem se na tom céčko, ale už ho nemám)
divoko, vútorné x-ové slučky vyzerajú takto:
frame_copy_movb()
0000351D 8B1EB006 mov bx,[0x6b0] 00003521 8A00 mov al,[bx+si] 00003523 C41EAB00 les bx,[0xab] 00003527 268801 mov [es:bx+di],al 0000352A 46 inc si 0000352B 47 inc di 0000352C FE46FF inc byte [bp-0x1] 0000352F 807EFF50 cmp byte [bp-0x1],0x50 00003533 72E8 jc 0x351d
frame_copy_movw_near()
0000367D 8BC6 mov ax,si 0000367F D1E0 shl ax,1 00003681 8B1EB006 mov bx,[0x6b0] 00003685 03D8 add bx,ax 00003687 8B07 mov ax,[bx] 00003689 8BD7 mov dx,di 0000368B D1E2 shl dx,1 0000368D 8B1EAB00 mov bx,[0xab] 00003691 03DA add bx,dx 00003693 268907 mov [es:bx],ax 00003696 46 inc si 00003697 47 inc di 00003698 FE46FF inc byte [bp-0x1] 0000369B 807EFF28 cmp byte [bp-0x1],0x28 0000369F 72DC jc 0x367d
frame_copy_movl_near() je už úplná divočina s 25 inštr. a plejádou push/pop v každej it. (aj tak je ale o chlp rýchlejší než ten movw, keďže presunie 32b na it.)
a hej, keď sa array indexovanie nahradí čisto ptr aritmetikou:
void frame_copy_movw_near_ptr() {
unsigned char x, y, b;
unsigned int _es *screen_p;
unsigned int *buf_p;
_ES = FP_SEG(screen);
for (b = 0; b < SCR_BANKS; b++) {
screen_p = &screen[b * SCR_BANK_OFFSET];
buf_p = &buf[b * SCR_LINE_OFFSET];
for (y = 0; y < SCR_H / SCR_BANKS; y++) {
for (x = 0; x < SCR_LINE_OFFSET / 2; x++) {
*screen_p++ = *buf_p++;
}
buf_p += SCR_LINE_OFFSET / 2; // skip even/odd line of src
}
}
}
vedie to k relatívne decentnému výsledku:
000036FC 8B04 mov ax,[si] 000036FE 268905 mov [es:di],ax 00003701 83C602 add si,byte +0x2 00003704 83C702 add di,byte +0x2 00003707 FE46FF inc byte [bp-0x1] 0000370A 807EFF28 cmp byte [bp-0x1],0x28 0000370E 72EC jc 0x36fc
škoda akurát tej loop premennej v stack frame namiesto registra a add si/di, 8b neviem či je optimálnejšie než 2x inc
Nie som teraz pri tom Amstrade, no v DOSBoxe je toto zhruba 1,7-krát efektívnejšie než najrýchlejší z tých array index prístupov.
pre tú kritickú loop premennú sa dá vynútiť register explicitne:
for (_CL = SCR_LINE_OFFSET / 2; _CL > 0; _CL--) {
*screen_p++ = *buf_p++;
}
potom je to:
000036FA 8B04 mov ax,[si] 000036FC 268905 mov [es:di],ax 000036FF 83C602 add si,byte +0x2 00003702 83C702 add di,byte +0x2 00003705 FEC9 dec cl 00003707 0AC9 or cl,cl 00003709 77EF ja 0x36fa
a aj toho OR CL, CL testu by sa asi dalo zbaviť prepisom na while{} alebo do{}while. Tu robí hneď na úvod JMP na ten OR test, aby otestoval, či náhodou už úvodné SCR_LINE_OFFSET / 2 nie je rovné 0 (hoci je to v tomto prípade nenulová konštanta). To už potom ale človek ohýba to C-čko takmer na assembler... :D