Compiladores são realmente bons em otimizar switch
. O gcc recente também é bom em otimizar várias condições em um if
.
Eu fiz alguns casos de teste no godbolt .
Quando os case
valores são agrupados, gcc, clang e icc são inteligentes o suficiente para usar um bitmap para verificar se um valor é um dos especiais.
por exemplo, gcc 5.2 -O3 compila o switch
para (e if
algo muito semelhante):
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()
Observe que o bitmap é um dado imediato, portanto, não há falta de cache de dados em potencial ao acessá-lo ou uma tabela de salto.
O gcc 4.9.2 -O3 compila o switch
em um bitmap, mas o faz 1U<<errNumber
com mov / shift. Compila a if
versão em séries de ramificações.
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 como subtrai 1 de errNumber
(com lea
para combinar essa operação com uma movimentação). Isso permite ajustar o bitmap em um imediato de 32 bits, evitando o imediato de 64 bits, movabsq
que requer mais bytes de instrução.
Uma sequência mais curta (no código da máquina) seria:
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
(A falha no uso jc fire_special_event
é onipresente e é um erro do compilador .)
rep ret
é usado em destinos de ramificação e após ramificações condicionais, para o benefício dos antigos AMD K8 e K10 (pré-Bulldozer): O que significa `rep ret`? . Sem ele, a previsão de ramificação não funciona tão bem nessas CPUs obsoletas.
bt
(teste de bits) com um registro arg é rápido. Ele combina o trabalho de deslocar um 1 por errNumber
bits e fazer um test
, mas ainda é uma latência de 1 ciclo e apenas um único processador Intel. É lento com um argumento de memória por causa de sua semântica CISC: com um operando de memória para a "sequência de bits", o endereço do byte a ser testado é calculado com base no outro argumento (dividido por 8) e isn está limitado ao pedaço de 1, 2, 4 ou 8 bytes apontado pelo operando de memória.
Nas tabelas de instruções de Agner Fog , uma instrução de turno de contagem variável é mais lenta que a bt
da Intel recente (2 uops em vez de 1 e turno não faz todo o necessário).