Компілятори справді хороші в оптимізації switch
. Останні gcc також добре оптимізують купу умов уif
.
Я зробив кілька тестових випадків на Godbolt .
Коли 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 ( передбульдозер ): Що означає `re ret`? . Без цього прогнозування галузей не працює на цих застарілих процесорах.
bt
(бітовий тест) з аргументом регістру швидко. Він поєднує в собі роботу лівого зсуву a 1 на errNumber
біти і виконання atest
, але все ж затримка 1 циклу і лише одна Intel взагалі. Із аргументом пам’яті це повільно через його семантику, яка занадто є CISC: з операндом пам’яті для «бітового рядка» адреса байта, що підлягає тестуванню, обчислюється на основі іншого аргументу (розділеного на 8), і isn не обмежується 1, 2, 4 або 8-байтним фрагментом, на який вказує операнд пам'яті.
З таблиць інструкцій Agner Fog інструкція зсуву змінної повільніше відбувається, ніж у bt
недавнього Intel (2 Uops замість 1, і shift не робить все, що потрібно).