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 switch
to (y if
algo 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 switch
en un mapa de bits, pero lo hace 1U<<errNumber
con mov / shift. Recopila la if
versió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 lea
para 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_event
es 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 bt
de Intel reciente (2 uops en lugar de 1, y shift no hace todo lo demás que se necesita).