Los compiladores son realmente buenos para optimizar switch
. El gcc reciente también es bueno para optimizar un conjunto de condiciones en un if
.
Hice algunos casos de prueba en godbolt.
Cuando los valores case
se agrupan muy juntos, gcc, clang e icc son lo suficientemente inteligentes como para usar un mapa de bits para comprobar si un valor es uno de los especiales.
p. Ej. gcc 5.2 -O3 compila el switch
a (y el 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()
en cuenta que el mapa de bits son datos inmediatos, así que no hay potencial miss-caché de datos para acceder a él, o una tabla de saltos.
gcc 4.9.2 -O3 compila el switch
a un mapa de bits, pero lo hace el 1U<<errNumber
con mov/shift. Compila la versión if
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()
Nota cómo se resta 1 de errNumber
(con lea
combinar esta operación con un movimiento). Eso le permite ajustar el mapa de bits en un inmediato de 32 bits, evitando el inmediato de 64 bits movabsq
que requiere más bytes de instrucción.
A (en código de máquina) secuencia más corta 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 falta de uso de jc fire_special_event
es omnipresente, y es a compiler bug)
rep ret
se utiliza en objetivos de las ramificaciones, y siguiendo las ramas condicionales, en beneficio de los antiguos AMD K8 y K10 (pre-Bulldozer): What does `rep ret` mean?. Sin él, la predicción de bifurcación no funciona tan bien en esas CPU obsoletas.
bt
(prueba de bit) con un registro arg es rápido. Combina el trabajo del desplazamiento a la izquierda de un 1 por errNumber
bits y haciendo un test
, pero sigue siendo 1 ciclo de latencia y solo un único Intel UOP. Es lento con una memoria arg debido a su semántica de modo demasiado CISC: con un operando de memoria para la "cadena de bits", la dirección del byte que se va a probar se calcula en función de la otra arg (dividida por 8), e isn No se limita al fragmento de 1, 2, 4 u 8 bytes al que apunta el operando de memoria.
De Agner Fog's instruction tables, una instrucción de cambio de cuenta variable es más lenta que bt
en Intel reciente (2 uops en lugar de 1, y shift no hace todo lo demás).
¿Esto está editado como 'subjetivo'? De Verdad? ¿Seguramente "subjetivo" es para cosas que no pueden ser probadas de una manera u otra? –
Claro que puedes verlo desde el punto de que genera el código más eficiente, pero cualquier compilador moderno debería ser igualmente eficiente. Al final, esto es más una cuestión del color del cobertizo de la bicicleta. – jfs
No estoy de acuerdo, no creo que esto sea subjetivo. Una diferencia simple de ASM importa, en muchos casos no se puede ignorar unos pocos segundos de optimización.Y en esta pregunta, no se trata de una guerra o debate religioso, hay una explicación racional de por qué uno sería más rápido, solo lea la respuesta aceptada. – chakrit