Compiler können wirklich gut optimieren switch
. Aktuelle gcc ist auch gut in der Optimierung einer Reihe von Bedingungen in einemif
.
Ich habe einige Testfälle auf Godbolt gemacht .
Wenn die case
Werte eng zusammen gruppiert sind, sind gcc, clang und icc intelligent genug, um mithilfe einer Bitmap zu überprüfen, ob ein Wert einer der besonderen ist.
zB gcc 5.2 -O3 kompiliert das switch
to (und das if
etwas sehr ähnliches):
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()
Beachten Sie, dass es sich bei der Bitmap um unmittelbare Daten handelt, sodass kein potenzieller Daten-Cache-Fehler beim Zugriff darauf oder eine Sprungtabelle auftritt.
gcc 4.9.2 -O3 kompiliert das switch
zu einer Bitmap, macht das aber 1U<<errNumber
mit mov / shift. Es kompiliert die if
Version zu einer Reihe von Zweigen.
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()
Beachten Sie, wie 1 von subtrahiert wird errNumber
(mit lea
, um diese Operation mit einer Bewegung zu kombinieren). Dadurch kann die Bitmap sofort in eine 32-Bit-Version eingepasst werden, wobei die 64-Bit-Version sofort vermieden wirdmovabsq
mehr Anweisungsbytes benötigt.
Eine kürzere (im Maschinencode) Sequenz wäre:
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
(Die Nichtverwendung jc fire_special_event
ist allgegenwärtig und ein Compiler-Fehler .)
rep ret
wird in Zweigzielen und folgenden bedingten Zweigen zum Nutzen des alten AMD K8 und K10 (Pre-Bulldozer) verwendet: Was bedeutet "rep ret"? . Ohne diese Funktion funktioniert die Verzweigungsvorhersage auf diesen veralteten CPUs nicht so gut.
bt
(Bittest) mit einem Register arg ist schnell. Es kombiniert die Arbeit, eine 1 um errNumber
Bits nach links zu verschieben und a zu tuntest
, hat aber immer noch eine Latenz von 1 Zyklus und nur einen einzigen Intel-UOP. Mit einem Speicherargument ist es aufgrund seiner viel zu CISC-Semantik langsam: Mit einem Speicheroperanden für die "Bitfolge" wird die Adresse des zu testenden Bytes basierend auf dem anderen Arg (geteilt durch 8) berechnet und isn Es ist nicht auf den 1-, 2-, 4- oder 8-Byte-Block beschränkt, auf den der Speicheroperand zeigt.
Aus den Befehlstabellen von Agner Fog geht hervor , dass ein Shift-Befehl mit variabler Anzahl langsamer ist als ein bt
neuerer Intel-Befehl (2 Uops anstelle von 1, und Shift erledigt nicht alles, was sonst noch benötigt wird).