Les compilateurs sont vraiment bons pour l'optimisation switch
. Gcc récent est également bon pour optimiser un tas de conditions dans unif
.
J'ai fait quelques cas de test sur godbolt .
Lorsque les case
valeurs sont regroupées étroitement, gcc, clang et icc sont tous suffisamment intelligents pour utiliser une image bitmap pour vérifier si une valeur est l'une des valeurs spéciales.
par exemple, gcc 5.2 -O3 compile le switch
to (et if
quelque chose de très similaire):
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()
Notez que le bitmap est des données immédiates, donc il n'y a pas de manque potentiel de cache de données pour y accéder, ni de table de saut.
gcc 4.9.2 -O3 compile le switch
en un bitmap, mais le fait 1U<<errNumber
avec mov / shift. Il compile la if
version en série de branches.
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()
Notez comment il soustrait 1 de errNumber
(avec lea
pour combiner cette opération avec un mouvement). Cela lui permet d'adapter le bitmap dans un 32 bits immédiat, évitant ainsi le 64 bits immédiatmovabsq
qui prend plus d'octets d'instructions.
Une séquence plus courte (en code machine) serait:
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
(L'échec de l'utilisation jc fire_special_event
est omniprésent et est un bogue du compilateur .)
rep ret
est utilisé dans les cibles de branche, et après les branches conditionnelles, au profit des anciens AMD K8 et K10 (pré-Bulldozer): Que signifie «rep ret»? . Sans cela, la prédiction de branche ne fonctionne pas aussi bien sur ces processeurs obsolètes.
bt
(bit test) avec un registre arg est rapide. Il combine le travail de décalage à gauche d'un 1 par errNumber
bits et de faire untest
, mais c'est toujours une latence de 1 cycle et un seul uop Intel. C'est lent avec un argument mémoire en raison de sa sémantique trop CISC: avec un opérande mémoire pour la "chaîne de bits", l'adresse de l'octet à tester est calculée sur la base de l'autre arg (divisé par 8), et est n'est pas limité au bloc de 1, 2, 4 ou 8 octets pointé par l'opérande mémoire.
D'après les tableaux d'instructions d'Agner Fog , une instruction de décalage à nombre variable est plus lente qu'une instruction bt
Intel récente (2 uops au lieu de 1, et shift ne fait pas tout le reste).