I compilatori sono davvero bravi a ottimizzare switch
. Gcc recente è anche bravo a ottimizzare un sacco di condizioni in un if
.
Ho realizzato alcuni casi di prova su godbolt .
Quando il case
valori sono raggruppati vicini, gcc, clang e icc sono tutti abbastanza intelligenti da usare una bitmap per verificare se un valore è uno di quelli speciali.
ad esempio gcc 5.2 -O3 compila il switch
to (e if
qualcosa di molto simile):
errhandler_switch(errtype): # gcc 5.2 -O3
cmpl $32, %edi
ja .L5
movabsq $4301325442, %rax # highest set bit is bit 32 (the 33rd bit)
btq %rdi, %rax
jc .L10
.L5:
rep ret
.L10:
jmp fire_special_event()
Si noti che la bitmap è un dato immediato, quindi non ci sono potenziali errori nella cache dei dati ad accedervi o una tabella di salto.
gcc 4.9.2 -O3 compila switch
in una bitmap, ma fa 1U<<errNumber
con mov / shift. Compila la if
versione in serie di rami.
errhandler_switch(errtype): # gcc 4.9.2 -O3
leal -1(%rdi), %ecx
cmpl $31, %ecx # cmpl $32, %edi wouldn't have to wait an extra cycle for lea's output.
# However, register read ports are limited on pre-SnB Intel
ja .L5
movl $1, %eax
salq %cl, %rax # with -march=haswell, it will use BMI's shlx to avoid moving the shift count into ecx
testl $2150662721, %eax
jne .L10
.L5:
rep ret
.L10:
jmp fire_special_event()
Nota come sottrae 1 da errNumber
(con lea
per combinare quell'operazione con una mossa). Ciò consente di adattare la bitmap in un immediato a 32 bit, evitando l'immediato a 64 bitmovabsq
che richiede più byte di istruzione.
Una sequenza più breve (nel codice macchina) sarebbe:
cmpl $32, %edi
ja .L5
mov $2150662721, %eax
dec %edi # movabsq and btq is fewer instructions / fewer Intel uops, but this saves several bytes
bt %edi, %eax
jc fire_special_event
.L5:
ret
(Il mancato utilizzo jc fire_special_event
è onnipresente ed è un bug del compilatore .)
rep ret
viene utilizzato nei target delle filiali e nei seguenti rami condizionati, a beneficio dei vecchi AMD K8 e K10 (pre-bulldozer): cosa significa `rep ret`? . Senza di essa, la previsione delle filiali non funziona altrettanto bene su quelle CPU obsolete.
bt
(bit test) con un registro arg è veloce. Combina il lavoro di spostare a sinistra un 1 per errNumber
bit e fare untest
, ma è ancora 1 ciclo di latenza e solo un singolo Intel UOP. È lento con un arg di memoria a causa della sua semantica CISC troppo lunga: con un operando di memoria per la "stringa di bit", l'indirizzo del byte da testare viene calcolato in base all'altro arg (diviso per 8), e isn non si limita al blocco di 1, 2, 4 o 8 byte a cui punta l'operando di memoria.
Dalle tabelle di istruzioni di Agner Fog, un'istruzione di spostamento a conteggio variabile è più lenta di una bt
recente Intel (2 uops anziché 1 e shift non fa tutto il necessario).