Компиляторы действительно хороши в оптимизации switch
. Недавние gcc также хороши для оптимизации множества условий в if
.
Я сделал несколько тестов на Годболт .
Когда значения case
сгруппированы близко друг к другу, gcc, clang и icc достаточно умны, чтобы использовать растровое изображение, чтобы проверить, является ли значение одним из специальных.
например. gcc 5.2 -O3 компилирует switch
в (и if
что-то очень похожее):
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()
Обратите внимание, что растровое изображение является непосредственными данными, поэтому нет потенциальной ошибки при обращении к кешу данных или к таблице переходов.
gcc 4.9.2 -O3 компилирует switch
в растровое изображение, но делает 1U<<errNumber
с mov / shift. Он компилирует версию if
в ряд ветвей.
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()
Обратите внимание, как он вычитает 1 из errNumber
(с lea
, чтобы объединить эту операцию с перемещением). Это позволяет ему вписать растровое изображение в 32-битный немедленный, избегая 64-битного немедленного movabsq
, который занимает больше байтов инструкции.
Более короткая (в машинном коде) последовательность будет:
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
(Отказ от использования jc fire_special_event
вездесущ и является ошибкой компилятора .)
rep ret
используется в целях ветвления и следующих условных ветвлениях для старых AMD K8 и K10 (pre-Bulldozer): Что означает `rep ret`? . Без этого предсказание ветвлений не работает так же хорошо на этих устаревших процессорах.
bt
(битовый тест) с регистром arg быстро. Он сочетает в себе работу сдвига влево на 1 errNumber
битов и выполнения test
, но все еще с задержкой в 1 цикл и только одним мопом Intel. Он медленен с аргументом памяти из-за его семантики «слишком слишком CISC»: с операндом памяти для «битовой строки» адрес проверяемого байта вычисляется на основе другого аргумента (деленного на 8), и isn не ограничивается 1, 2, 4 или 8-байтовым фрагментом, на который указывает операнд памяти.
Из таблиц инструкций Agner Fog , команда сдвига с переменным счетом медленнее, чем bt
на недавнем Intel (2 моп вместо 1, а shift не делает все остальное, что нужно).