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 casewartoś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 switchto (i ifcoś 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 switchdo bitmapy, ale robi to 1U<<errNumberz mov / shift. Kompiluje ifwersję 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( leaby 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_eventjest wszechobecny i jest błędem kompilatora ).
rep retjest 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 errNumberbity 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ż btw ostatnim Intelu (2 uops zamiast 1, a shift nie robi wszystkiego innego, co jest potrzebne).