Kompilatory są naprawdę dobre w optymalizacji switch
. Najnowsze gcc jest również dobre w optymalizacji szeregu warunków w if
.
Zrobiłem kilka przypadków testowych na godbolt .
Kiedy case
wartości są zgrupowane blisko siebie, gcc, clang i icc są wystarczająco inteligentne, aby użyć mapy bitowej, aby sprawdzić, czy wartość jest jedną z tych specjalnych.
np. gcc 5.2 -O3 kompiluje switch
to (i if
coś bardzo podobnego):
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()
Zauważ, że mapa bitowa to dane natychmiastowe, więc nie ma potencjalnego braku dostępu do niej w pamięci podręcznej danych lub tabeli skoków.
gcc 4.9.2 -O3 kompiluje switch
do bitmapy, ale robi to 1U<<errNumber
z mov / shift. Kompiluje if
wersję do serii gałęzi.
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()
Zwróć uwagę, jak odejmuje 1 od errNumber
( lea
by połączyć tę operację z ruchem). To pozwala natychmiast dopasować bitmapę do 32-bitowej wersji, unikając natychmiastowej 64-bitowejmovabsq
która zajmuje więcej bajtów instrukcji.
Krótsza (w kodzie maszynowym) sekwencja to:
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
(Brak użycia jc fire_special_event
jest wszechobecny i jest błędem kompilatora ).
rep ret
jest używany w gałęziach docelowych i kolejnych gałęziach warunkowych na korzyść starych AMD K8 i K10 (pre-Bulldozer): Co oznacza „rep ret”? . Bez tego przewidywanie gałęzi nie działa tak dobrze na tych przestarzałych procesorach.
bt
(test bitowy) z rejestrem arg jest szybki. Łączy pracę przesuwania w lewo 1 o errNumber
bity i robienia atest
, ale nadal jest opóźnieniem 1 cyklu i tylko pojedynczym uopem Intel. Jest powolny z argumentem pamięci ze względu na jego zbyt dużą semantykę CISC: z operandem pamięci dla „ciągu bitowego” adres bajtu do testowania jest obliczany na podstawie drugiego argumentu (podzielonego przez 8) i nie jest nie ogranicza się do 1, 2, 4 lub 8-bajtowego fragmentu wskazywanego przez operand pamięci.
Z tabel instrukcji Agner Fog, instrukcja zmiany liczby zmiennej jest wolniejsza niż bt
w ostatnim Intelu (2 uops zamiast 1, a shift nie robi wszystkiego innego, co jest potrzebne).