Obtiaznost: ****
Pawn je vykonávaný na abstraktnom stroji (Abstract Machine eXecutor). Tento číta výsledný skompilovaný kód a vykonáva dané inštrukcie. Ak narazí na switch(), porovnávanie hodnôt vykonáva automaticky (porovnávací kód sa nenachádza v skompilovanom súbore).
2. Kdy a jak použít switch
Okrem toho, že switch vyzerá krajšie, je aj rýchlejší.
Rýchlosť budem porovnávať s týmto kódom:
compif(x) {
if (x == 1) {
return;
} else if (x == 2) {
return;
} else if (x == 3) {
return;
} else if (x == 4) {
return;
} else {
return;
}
}
compswitch1(x) {
switch(x) {
case 1..4: return;
default: return;
}
}
compswitch2(x) {
switch(x) {
case 3: return;
case 2: return;
case 4: return;
case 1: return;
default: return;
}
}
Čím viac hodnôt (case-ov) porovnávate, tým viac sa oplatí používať switch.
Porovnanie (GetTickCount()) môže vyzerať nejak takto:
[02:55:21] iftime: 14435
[02:55:29] switchtime1: 8038
[02:55:37] switchtime2: 8053
Rovnako testovanie na dvoch hodnotách (case 1..2) (ale viac iterácií):
[03:09:03] iftime: 17946
[03:09:17] switchtime1: 14004
[03:09:33] switchtime2: 16093
A testovanie na 30 hodnotách:
[03:14:07] iftime: 72057
[03:14:26] switchtime1: 19457
[03:14:43] switchtime2: 19146
Ako je vidieť, switch už moc nenaberá na čase s viac hodnotami. A tiež switch1 má rovnakú rýchlosť ako switch2.
6. Ako funguje switch
Prečo je switch rýchlejší? Ostatné (Pawn viacmenej tiež) jazyky používajú tzv. Jump table.
Najprv ukážem ako vyzerá bežný if (kód vyššie) po skompilovaní: (kod som vytiahol cez oficialny pawndisasm.c)
* Niektore zbytocne instrukcie som kvoli prehladnosti odstranil. Kvoli tomu adresy nepojdu "za sebou".
* pri je (abstraktny register) miesto v pamati, ktore použiva Pawn AMX, kedze nedokaze porovnat priamo dve premenne. Vsetky operacie sa robia cez pri. Mozete si to predstavit ako premennu s rychlym pristupom.
* Okomentoval som to pre lahsie citanie, staci si pozriet komenty a adresy kam to skáče.
; compif(x)
; adresa | instrukcia
00000354 proc compif
; if (x == 1)
0000035c load.s.pri 0000000c ; pri = x
00000364 eq.c.pri 00000001 ; pri != 1?
0000036c jzer 00000380 ; ak plati, skoc na adresu 00000384 (dalsi else if)
0000037c retn ; (neplati) return
; else if (x == 2)
00000384 load.s.pri 0000000c ; pri = x
0000038c eq.c.pri 00000002 ; pri != 2?
00000394 jzer 000003a8 ; ak plati, skoc na 000003a8
000003a4 retn ; return
000003a8 ; ... dalsie else if
Porovnáva každý if s danou hodnotou, v prípade, že sa nerovnajú, "preskočí" na ďalší else-if. Ten skok trvá najdlhšie.
Jump table
V RAM vyzera dana tabulka takto: (vlavo adresy v RAM, vpravo inštrukcia na danej adrese)
RAM:
--------------------------
| jmptable: |
| 0x01: JUMP 0x2349807 |
| 0x02: JUMP 0x0938490 |
| 0x03: JUMP 0x6454299 |
| .. |
| 0x2349807 kod z case 1 |
| .. |
--------------------------
Kód
switch (x) {..}
sa teda kompilerom prepíše na
jump (jmptable + x);
Ideálne teda nedochádza k žiadnemu porovnaniu hodnôt, iba k jednému skoku.
Switch v Pawn
Pawn je teda v tomto trochu iný. Tabulka (v Pawn nazýzvaná "casetbl") sa rovnako vygeneruje, ale nepoužíva sa priamo skok, ale len porovnávanie hodnôt. AMX V reálnom čase porovnáva hodnotu x so všetkými "cases" a až keď nájde rovnakú hodnotu, skočí na danú adresu.
compswitch1 z kódu uvedeného vyššie:
00000404 proc compswitch1
0000040c load.s.pri 0000000c ; pri = x
00000414 switch 00000444 ; switch (pri), jump table je na adrese 00000444
; case 1..4:
00000424 retn ; return - vnutro case
00000428 jump 00000470 ; skoc za casetbl, v pripade ze nenasiel ziadnu hodnotu v tabulke
; case: default
00000438 retn ; return, resp. iny kod
0000043c jump 00000470 ; skoc za "casetbl", teda na dalsi kod
00000444 casetbl 00000004 00000438 ; celkovo 4 moznosti, pri default skoč na 00000438
00000001 00000424 ; case 1: jmp 00000424
00000002 00000424 ; case 2: jmp 00000424
00000003 00000424
00000004 00000424
00000470 ; ..
Je teda zrejmé, že "1..4" vygeneruje zápisy pre všetky možnosti medzi tým s rovnakou skokovou adresou.
Kód cmpswitch2 vyzerá podobne. Pre stručnosť sem dám len casetbl.
00000478 proc compswitch2
; ..
000004f4 casetbl 00000004 000004e0 ; 4 zapisy
00000001 000004cc ; x == 1 -> skoc na prvy case
00000002 000004a4 ; x == 2 -> druhy case
00000003 00000490
00000004 000004b8
000004e0 ; ..
Rozdiely medzi cmpswitch1 a 2 sú, že každý case má odlišnú adresu kam skočiť.
Záver
Možno ste si všimli, že hodnoty sú v compswitch2 po skompilovaní zoradené. Kompiler ich zoradil automaticky.
The records in the case table are sorted on their value. An abstract machine may take advantage of this lay-out to search through the table with a binary search.
(z Pawn Implementer Guide). Podľa zdrojového kódu amx sa však nepoužíva binary search, ale lineárne porovnávanie všetkých hodnôt zaradom.