Los compiladores son muy buenos para optimizar switch. El gcc reciente también es bueno para optimizar un montón de condiciones en unif .
Hice algunos casos de prueba en Godbolt .
Cuando el case valores se agrupan juntos, gcc, clang e icc son lo suficientemente inteligentes como para usar un mapa de bits para verificar si un valor es uno de los especiales.
Por ejemplo, gcc 5.2 -O3 compila el switchto (y ifalgo muy similar):
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()
Tenga en cuenta que el mapa de bits son datos inmediatos, por lo que no hay una posible falta de caché de datos para acceder a él, o una tabla de salto.
gcc 4.9.2 -O3 compila el switchen un mapa de bits, pero lo hace 1U<<errNumbercon mov / shift. Recopila la ifversión a series de ramas.
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()
Observe cómo resta 1 de errNumber(con leapara combinar esa operación con un movimiento). Eso le permite ajustar el mapa de bits en un inmediato de 32 bits, evitando el inmediato de 64 bitsmovabsq que requiere más bytes de instrucciones.
Una secuencia más corta (en código máquina) sería:
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
(La omisión de uso jc fire_special_eventes omnipresente y es un error del compilador ).
rep ret se usa en objetivos de ramificación, y siguiendo ramificaciones condicionales, en beneficio de los viejos AMD K8 y K10 (pre-Bulldozer): ¿Qué significa `rep ret`? . Sin ella, la predicción de rama no funciona tan bien en esas CPU obsoletas.
bt(prueba de bit) con un registro arg es rápido. Combina el trabajo de desplazar a la izquierda un 1 porerrNumber bits y hacer untest , pero sigue siendo una latencia de 1 ciclo y solo una única Intel uop. Es lento con un argumento de memoria debido a su semántica CISC: con un operando de memoria para la "cadena de bits", la dirección del byte a probar se calcula en función del otro argumento (dividido por 8), e isn no se limita al fragmento de 1, 2, 4 u 8 bytes señalado por el operando de memoria.
De las tablas de instrucciones de Agner Fog , una instrucción de cambio de conteo variable es más lenta que una btde Intel reciente (2 uops en lugar de 1, y shift no hace todo lo demás que se necesita).