Toutes les instructions suivantes font la même chose: mettre %eax
à zéro. Quelle voie est optimale (nécessitant le moins de cycles machine)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
performance
assembly
optimization
x86
micro-optimization
balajimc55
la source
la source
Réponses:
TL; Résumé DR :
xor same, same
est le meilleur choix pour tous les processeurs . Aucune autre méthode n'a aucun avantage sur elle, et elle a au moins un avantage sur toute autre méthode. Il est officiellement recommandé par Intel et AMD, et ce que font les compilateurs. En mode 64 bits, utilisez toujoursxor r32, r32
, car l' écriture d'un reg 32 bits remet à zéro les 32 supérieurs .xor r64, r64
est un gaspillage d'octet, car il a besoin d'un préfixe REX.Pire encore, Silvermont ne reconnaît
xor r32,r32
que la taille d'un opérande de 64 bits, pas de rupture de dép. Ainsi, même si un préfixe REX est toujours nécessaire parce que vous mettez à zéro r8..r15, utilisezxor r10d,r10d
, nonxor r10,r10
.Exemples de GP-integer:
Il est généralement préférable de mettre à zéro un registre vectoriel avec
pxor xmm, xmm
. C'est généralement ce que fait gcc (même avant utilisation avec les instructions FP).xorps xmm, xmm
peut avoir du sens. C'est un octet plus court quepxor
, maisxorps
nécessite le port d'exécution 5 sur Intel Nehalem, alors qu'ilpxor
peut fonctionner sur n'importe quel port (0/1/5). (La latence du délai de contournement 2c de Nehalem entre entier et FP n'est généralement pas pertinente, car une exécution dans le désordre peut généralement la masquer au début d'une nouvelle chaîne de dépendances).Sur les microarchitectures de la famille SnB, aucune des versions de xor-zeroing n'a même besoin d'un port d'exécution. Sur AMD et pré-Nehalem P6 / Core2 Intel,
xorps
etpxor
sont gérées de la même manière (comme des instructions vecteur-entier).L'utilisation de la version AVX d'une instruction vectorielle 128b met également
vpxor xmm, xmm, xmm
à zéro la partie supérieure du reg, c'est donc un bon choix pour la remise à zéro de YMM (AVX1 / AVX2) ou ZMM (AVX512), ou de toute future extension vectorielle.vpxor ymm, ymm, ymm
ne prend pas d'octets supplémentaires à encoder, cependant, et fonctionne de la même manière sur Intel, mais plus lentement sur AMD avant Zen2 (2 uops). La mise à zéro AVX512 ZMM nécessiterait des octets supplémentaires (pour le préfixe EVEX), donc la mise à zéro XMM ou YMM devrait être préférée.Exemples XMM / YMM / ZMM
Voir La mise à zéro de vxorps sur AMD Jaguar / Bulldozer / Zen est-elle plus rapide avec des registres xmm que ymm? et
Quelle est la manière la plus efficace d'effacer un ou quelques registres ZMM sur Knights Landing?
Semi-lié: Le moyen le plus rapide de définir la valeur __m256 sur tous les bits ONE et de
définir tous les bits du registre du processeur sur 1 couvre également efficacement les
k0..7
registres de masque AVX512 . SSE / AVXvpcmpeqd
est dépendant de beaucoup (bien qu'il ait encore besoin d'un uop pour écrire les 1), mais AVX512vpternlogd
pour les regs ZMM n'est même pas dépendant. À l'intérieur d'une boucle, envisagez de copier à partir d'un autre registre au lieu de recréer ceux avec un uop ALU, en particulier avec AVX512.Mais la remise à zéro n'est pas chère: la mise à zéro d'un reg xmm dans une boucle est généralement aussi bonne que la copie, sauf sur certains processeurs AMD (Bulldozer et Zen) qui ont une élimination de mov pour les regs vectoriels mais qui ont toujours besoin d'un uop ALU pour écrire des zéros pour xor -zéro.
Quelle est la particularité de la remise à zéro des idiomes comme xor sur divers uarches
Certains processeurs reconnaissent
sub same,same
comme un idiome de remise à zéroxor
, mais tous les processeurs qui reconnaissent les idiomes de remise à zéro le reconnaissentxor
. Utilisez simplementxor
pour ne pas avoir à vous soucier de quel processeur reconnaît quel idiome de remise à zéro.xor
(étant un idiome de réduction à zéro reconnu, contrairement àmov reg, 0
) présente des avantages évidents et subtils (liste récapitulative, je vais les développer):mov reg,0
. (Tous les processeurs)Une plus petite taille de code machine (2 octets au lieu de 5) est toujours un avantage: une densité de code plus élevée conduit à moins d'erreurs de cache d'instructions, et une meilleure extraction des instructions et potentiellement décodage de la bande passante.
L'avantage de ne pas utiliser d'unité d'exécution pour xor sur les microarchitectures de la famille Intel SnB est mineur, mais économise de l'énergie. Il est plus probable que cela soit important sur SnB ou IvB, qui n'ont que 3 ports d'exécution ALU. Haswell et les versions ultérieures ont 4 ports d'exécution qui peuvent gérer des instructions ALU entières, y compris
mov r32, imm32
, donc avec une prise de décision parfaite par le planificateur (ce qui ne se produit pas toujours dans la pratique), HSW pourrait toujours supporter 4 uops par horloge même quand ils ont tous besoin d'ALU ports d'exécution.Voir ma réponse à une autre question sur la remise à zéro des registres pour plus de détails.
Le billet de blog de Bruce Dawson que Michael Petch a lié (dans un commentaire sur la question) souligne qu'il
xor
est traité à l'étape du changement de nom du registre sans avoir besoin d'une unité d'exécution (zéro uops dans le domaine non fusionné), mais a manqué le fait qu'il reste un uop dans le domaine fusionné. Les processeurs Intel modernes peuvent émettre et retirer 4 uops de domaine fusionné par horloge. C'est de là que vient la limite de 4 zéros par horloge. La complexité accrue du matériel de renommage des registres n'est qu'une des raisons pour lesquelles la largeur de la conception est limitée à 4. (Bruce a écrit d'excellents articles de blog, comme sa série sur les problèmes de mathématiques FP et x87 / SSE / arrondi , ce que je fais recommande fortement).Sur les processeurs de la famille AMD Bulldozer ,
mov immediate
s'exécute sur les mêmes ports d'exécution d'entiers EX0 / EX1 quexor
.mov reg,reg
peut également fonctionner sur AGU0 / 1, mais ce n'est que pour la copie de registre, pas pour la configuration à partir de l'immédiat. Donc , autant que je sache, sur AMD le seul avantage dexor
plusmov
est l'encodage plus court. Cela pourrait également économiser des ressources de registre physiques, mais je n'ai vu aucun test.Les idiomes de remise à zéro reconnus évitent les pénalités de registre partiel sur les processeurs Intel qui renomment les registres partiels séparément des registres complets (familles P6 et SnB).
xor
marquera le registre comme ayant les parties supérieures mises à zéro , doncxor eax, eax
/inc al
/inc eax
évite la pénalité habituelle de registre partiel que les CPU pré-IvB ont. Même sansxor
, IvB n'a besoin d'un uop de fusion que lorsque les 8 bits élevés (AH
) sont modifiés et que tout le registre est lu, et Haswell supprime même cela.Extrait du guide microarch d'Agner Fog, p. 98 (section Pentium M, référencée par les sections suivantes, y compris SnB):
pg82 de ce guide confirme également que ce
mov reg, 0
n'est pas reconnu comme un idiome de réduction à zéro, du moins sur les premières conceptions P6 comme PIII ou PM. Je serais très surpris s'ils passaient des transistors à le détecter sur les processeurs ultérieurs.xor
définit des indicateurs , ce qui signifie que vous devez faire attention lorsque vous testez les conditions. Comme ilsetcc
n'est malheureusement disponible qu'avec une destination 8 bits , vous devez généralement prendre soin d'éviter les pénalités de registre partiel.Cela aurait été bien si x86-64 avait réutilisé l'un des opcodes supprimés (comme AAM) pour un 16/32/64 bits
setcc r/m
, avec le prédicat codé dans le champ 3 bits du registre source du champ r / m (la manière certaines autres instructions à un seul opérande les utilisent comme bits d'opcode). Mais ils ne l'ont pas fait, et cela n'aiderait pas de toute façon pour x86-32.Idéalement, vous devriez utiliser
xor
/ définir des indicateurs /setcc
/ lire le registre complet:Cela a des performances optimales sur tous les processeurs (pas de décrochage, de fusion d'ups ou de fausses dépendances).
Les choses sont plus compliquées lorsque vous ne voulez pas xor avant une instruction de réglage de drapeau . par exemple, vous voulez créer une branche sur une condition, puis setcc sur une autre condition à partir des mêmes indicateurs. par exemple
cmp/jle
,sete
et soit vous n'avez pas de registre de rechange, soit vous voulez garderxor
complètement le chemin de code non pris.Il n'y a pas d'idiomes de remise à zéro reconnus qui n'affectent pas les indicateurs, donc le meilleur choix dépend de la microarchitecture cible. Sur Core2, l'insertion d'un uop de fusion peut provoquer un blocage de 2 ou 3 cycles. Cela semble être moins cher sur SnB, mais je n'ai pas passé beaucoup de temps à essayer de mesurer. L'utilisation de
mov reg, 0
/setcc
aurait une pénalité significative sur les anciens processeurs Intel, et serait encore un peu pire sur les nouveaux Intel.L'utilisation de
setcc
/movzx r32, r8
est probablement la meilleure alternative pour les familles Intel P6 et SnB, si vous ne pouvez pas xor-zero avant l'instruction de réglage du drapeau. Cela devrait être mieux que de répéter le test après un xor-zeroing. (Ne considérez même passahf
/lahf
oupushf
/popf
). IvB peut éliminermovzx r32, r8
(c'est-à-dire le gérer avec un renommage de registre sans unité d'exécution ni latence, comme xor-zeroing). Haswell et les versions ultérieures n'éliminent que lesmov
instructions régulières , doncmovzx
prend une unité d'exécution et a une latence non nulle, ce qui rend test /setcc
/movzx
pire quexor
/ test /setcc
, mais toujours au moins aussi bon que test /mov r,0
/setcc
(et bien meilleur sur les anciens processeurs).Utiliser
setcc
/movzx
sans remise à zéro en premier est mauvais sur AMD / P4 / Silvermont, car ils ne suivent pas les déps séparément pour les sous-registres. Il y aurait un faux dépendant de l'ancienne valeur du registre. Utilisermov reg, 0
/setcc
pour la remise à zéro / la rupture de dépendance est probablement la meilleure alternative lorsquexor
/ test /setcc
n'est pas une option.Bien sûr, si vous n'avez pas besoin
setcc
d'une sortie de plus de 8 bits, vous n'avez rien à mettre à zéro. Cependant, méfiez-vous des fausses dépendances sur les processeurs autres que P6 / SnB si vous choisissez un registre qui faisait récemment partie d'une longue chaîne de dépendances. (Et méfiez-vous de provoquer un décrochage partiel du reg ou un uop supplémentaire si vous appelez une fonction qui pourrait sauvegarder / restaurer le registre dont vous utilisez une partie.)and
avec un zéro immédiat n'est pas une casse spéciale comme indépendante de l'ancienne valeur sur les processeurs que je connais, donc cela ne rompt pas les chaînes de dépendance. Il n'a pas d'avantagesxor
et de nombreux inconvénients.Cela n'est utile que pour écrire des microbenchmarks lorsque vous voulez une dépendance dans le cadre d'un test de latence, mais que vous voulez créer une valeur connue en mettant à zéro et en ajoutant.
Voir http://agner.org/optimize/ pour plus de détails sur les microarchives , y compris les idiomes de remise à zéro qui sont reconnus comme brisant les dépendances (par exemple,
sub same,same
sur certains processeurs mais pas sur tous, alors qu'ilsxor same,same
sont reconnus sur tous.)mov
Rompt la chaîne de dépendances sur l'ancienne valeur du registre (quelle que soit la valeur source, zéro ou non, car c'est ainsi que çamov
marche).xor
ne casse les chaînes de dépendances que dans le cas spécial où src et dest sont le même registre, c'est pourquoi ilmov
est exclu de la liste des disjoncteurs de dépendances spécialement reconnus. (De plus, parce que ce n'est pas reconnu comme un idiome de réduction à zéro, avec les autres avantages que cela comporte.)Il est intéressant de noter que la conception la plus ancienne de P6 (PPro à Pentium III) ne reconnaissait pas la mise à
xor
zéro comme un disjoncteur de dépendance, uniquement comme un idiome de remise à zéro dans le but d'éviter les blocages de registres partiels , donc dans certains cas, il valait la peine d'utiliser les deuxmov
, puisxor
-zéro dans cet ordre pour casser le dep, puis à nouveau à zéro + définir le bit de balise interne que les bits hauts sont à zéro donc EAX = AX = AL.Voir l'exemple 6.17 d'Agner Fog. dans son pdf microarch. Il dit que cela s'applique également à P2, P3 et même (tôt?) PM. Un commentaire sur l'article de blog lié dit que seul PPro avait cette oubli, mais j'ai testé sur Katmai PIII et @Fanael testé sur un Pentium M, et nous avons tous deux constaté qu'il ne cassait pas une dépendance pour une latence
imul
chaîne liée . Cela confirme les résultats d'Agner Fog, malheureusement.TL: DR:
Si cela rend vraiment votre code plus agréable ou enregistre des instructions, alors bien sûr, zéro avec
mov
pour éviter de toucher les indicateurs, tant que vous n'introduisez pas de problème de performances autre que la taille du code. Éviter les drapeaux écrasants est la seule raison raisonnable de ne pas utiliserxor
, mais parfois vous pouvez xor-zéro avant la chose qui définit les drapeaux si vous avez un registre de rechange.mov
-zéro avantsetcc
est meilleur pour la latencemovzx reg32, reg8
qu'après (sauf sur Intel lorsque vous pouvez choisir différents registres), mais la taille du code est pire.la source
mov reg, src
brise également les chaînes dep pour les processeurs OO (indépendamment du fait que src soit imm32[mem]
, ou d'un autre registre). Cette rupture de dépendance n'est pas mentionnée dans les manuels d'optimisation car ce n'est pas un cas spécial qui se produit uniquement lorsque src et dest sont le même registre. Cela arrive toujours pour des instructions qui ne dépendent pas de leur destination. (sauf pour l'implémentation d'Intel d'popcnt/lzcnt/tzcnt
avoir un faux dép sur la destination.)mov
gratuit, seulement zéro latence. La partie "ne pas prendre de port d'exécution" n'est généralement pas importante. Le débit du domaine fusionné peut facilement être le goulot d'étranglement, en particulier. avec des charges ou des magasins dans le mélange.xor r64, r64
ne gaspille pas seulement un octet. Comme vous le dites,xor r32, r32
c'est le meilleur choix, surtout avec KNL. Voir la section 15.7 «Cas particuliers d'indépendance» dans ce manuel de micrarch si vous souhaitez en savoir plus.