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 caseWerte 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 switchto (und das ifetwas 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 switchzu einer Bitmap, macht das aber 1U<<errNumbermit mov / shift. Es kompiliert die ifVersion 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_eventist allgegenwärtig und ein Compiler-Fehler .)
rep retwird 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 errNumberBits 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 btneuerer Intel-Befehl (2 Uops anstelle von 1, und Shift erledigt nicht alles, was sonst noch benötigt wird).