Quelle est la meilleure façon de définir un registre à zéro dans un assemblage x86: xor, mov ou and?

Toutes les instructions suivantes font la même chose: définissez %eax sur zéro. Quelle est la voie optimale (nécessitant le moins de cycles de la machine)?

 xorl %eax, %eax mov $0, %eax andl $0, %eax 

TL; DR summary : xor same, same est le meilleur choix pour tous les processeurs . Aucune autre méthode n’a d’avantage sur elle, et elle a au moins un avantage sur toute autre méthode. Il est officiellement recommandé par Intel et AMD. En mode 64 bits, utilisez toujours xor r32, r32 , car l’ écriture d’un reg zeros 32 bits sur le 32 supérieur . xor r64, r64 est un gaspillage d’octet, car il nécessite un préfixe REX.

La mise à zéro d’un registre vectoriel se fait généralement mieux avec pxor xmm, xmm . C’est généralement ce que fait gcc (même avant l’utilisation avec les instructions FP).

xorps xmm, xmm peut avoir du sens. C’est un octet plus court que pxor , mais xorps besoin du port d’exécution 5 sur Intel Nehalem, alors que pxor peut fonctionner sur n’importe quel port (0/1/5). (La latence du délai de contournement 2c de Nehalem entre le nombre entier et le FP n’est généralement pas pertinente, car l’exécution en désordre peut généralement la masquer au début d’une nouvelle chaîne de dépendance).

Sur les microarchitectures de la famille SnB, aucune des deux caractéristiques du xor-zeroing n’a même besoin d’un port d’exécution. Sur AMD, et pré-Nehalem P6 / Core2 Intel, xorps et pxor sont traités de la même manière (en tant qu’instructions vectorielles entières).

L’utilisation de la version AVX d’une instruction vectorielle 128b vpxor xmm, xmm, xmm à zéro la partie supérieure du vpxor xmm, xmm, xmm , donc vpxor xmm, xmm, xmm est un bon choix pour mettre à zéro YMM (AVX1 / AVX2) ou ZMM (AVX512) ou toute future extension vectorielle. vpxor ymm, ymm, ymm ne prend aucun octet supplémentaire pour encoder, cependant, et exécute la même chose. La mise à zéro de l’AVX512 ZMM nécessiterait des octets supplémentaires (pour le préfixe EVEX), de sorte que la mise à zéro XMM ou YMM devrait être préférée.


Certains processeurs reconnaissent le sub same,same comme un idiome de xor zéro comme xor , mais tous les processeurs qui reconnaissent des idiomes de réduction à zéro reconnaissent xor . Il suffit d’utiliser xor pour ne pas avoir à se soucier de savoir 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, puis je développerai sur ces points):

  • taille de code plus petite que mov reg,0 . (Tous les processeurs)
  • évite les pénalités de registre partiel pour les codes ultérieurs. (Famille Intel P6 et famille SnB).
  • n’utilise pas une unité d’exécution, économisant de l’énergie et libérant des ressources d’exécution. (Famille Intel SnB)
  • plus petit uop (pas de données immédiates) laisse de la place dans la ligne de cache uop pour les instructions proches à emprunter si nécessaire. (Famille Intel SnB).
  • n’utilise pas les entrées dans le fichier de registre physique . (Intel SnB-famille (et P4) au moins, probablement aussi AMD puisqu’ils utilisent une conception PRF similaire au lieu de garder l’état de registre dans le ROB comme les microarchitectures de la famille Intel P6.)

Une taille de code machine plus petite (2 octets au lieu de 5) est toujours un avantage: une densité de code plus élevée réduit le nombre d’instructions de cache d’instruction et améliore la récupération des instructions et le décodage potentiel de la bande passante.


L’avantage de ne pas utiliser une 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 ce soit important sur SnB ou IvB, qui ne disposent que de 3 ports d’exécution ALU. Haswell et plus tard ont 4 ports d’exécution capables de 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 dans la pratique), tous ont besoin de ports d’exécution.

Voir ma réponse sur une autre question concernant les registres de mise à zéro pour plus de détails.

Le post du blog de Bruce Dawson que Michael Petch a lié (dans un commentaire sur la question) souligne que xor est géré à la phase de registre-renommé sans avoir besoin d’une unité d’exécution (zéro uops dans le domaine non fusionné). uop dans le domaine fusionné. Les processeurs Intel modernes peuvent émettre et retirer 4 uops de domaine fusionnés par horloge. C’est de là que viennent les 4 zéros par limite d’horloge. La complexité accrue du changement de nom du matériel n’est que l’une des raisons de limiter la largeur de la conception à 4. (Bruce a écrit d’excellents articles sur le blog, comme sa série sur les mathématiques FP et x87 / SSE / arrondis) recommande fortement).


Sur les processeurs de la famille Bulldozer AMD , mov immediate s’exécute sur les mêmes ports d’exécution EX0 / EX1 entiers que xor . mov reg,reg peut également s’exécuter sur AGU0 / 1, mais uniquement pour la copie de registre, et non pour la configuration à partir de l’immédiat. Donc, le seul avantage de xor over mov est le codage plus court. Cela peut également sauver des ressources de registre physique, mais je n’ai vu aucun test.


Les idiomes de réduction à 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 balisera le registre comme ayant les parties supérieures mises à zéro , donc xor eax, eax / inc al / inc eax évite la pénalité de registre partiel habituelle des processeurs pré-IvB. Même sans xor , IvB n’a besoin que d’un uop de fusion lorsque les 8 bits supérieurs ( AH ) sont modifiés, puis le registre entier est lu et Haswell le supprime même.

A partir du guide de microarch d’Agner Fog, page 98 (section Pentium M, référencée par les sections suivantes, y compris SnB):

Le processeur reconnaît le XOR d’un registre avec lui-même en le mettant à zéro. Une balise spéciale dans le registre se souvient que la partie haute du registre est zéro, de sorte que EAX = AL. Cette balise est mémorisée même dans une boucle:

  ; Example 7.9. Partial register problem avoided in loop xor eax, eax mov ecx, 100 LL: mov al, [esi] mov [edi], eax ; No extra uop inc esi add edi, 4 dec ecx jnz LL 

(de la page 82): Le processeur se souvient que les 24 bits supérieurs de EAX sont nuls tant que vous ne recevez pas d’interruption, de mauvaise prédiction ou d’autres événements de sérialisation.

pg82 de ce guide confirme également que mov reg, 0 n’est pas reconnu comme un idiome de réduction à zéro, au moins sur les premiers modèles de P6 comme PIII ou PM. Je serais très surpris qu’ils dépensent des transistors pour le détecter sur les processeurs ultérieurs.


xor définit les drapeaux , ce qui signifie que vous devez faire attention lorsque vous testez les conditions. Étant donné que setcc n’est malheureusement disponible qu’avec une destination 8 bits , vous devez généralement veiller à éviter les pénalités de registre partiel.

Cela aurait été bien si x86-64 réutilisait un des opcodes supprimés (comme AAM) pour un setcc setcc r/m 16/32/64 bits, avec le prédicat codé dans le champ de 3 bits du registre source du r / m champ (la manière dont certaines instructions à un seul opérande les utilisent comme bits d’opcode). Mais ils ne l’ont pas fait, et cela ne serait d’aucune aide pour x86-32.

Idéalement, vous devriez utiliser xor / set flags / setcc / read full register:

 ... call some_func xor ecx,ecx ; zero *before* the test test eax,eax setnz cl ; cl = (some_func() != 0) add ebx, ecx ; no partial-register penalty here 

Cela a des performances optimales sur tous les processeurs (pas d’obstruction, de fusion d’uops ou de fausses dépendances).

Les choses sont plus compliquées lorsque vous ne voulez pas exécuter une instruction de définition de drapeau . Par exemple, vous souhaitez créer une twig sur une condition, puis définir une autre condition à partir des mêmes indicateurs. Par exemple, cmp/jle , sete et vous n’avez pas de registre de secours ou vous voulez garder le xor complètement hors du chemin de code.

Il n’y a aucun idiome de remise à zéro reconnu qui n’affecte pas les drapeaux. Le meilleur choix dépend donc 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. Utiliser mov reg, 0 / setcc aurait une pénalité importante sur les anciens processeurs Intel, et serait encore un peu pire sur les nouveaux Intel.

En utilisant 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 mise en drapeau. Cela devrait être mieux que de répéter le test après une remise à zéro du xor. (Ne considérez même pas sahf / lahf ou pushf / popf ). IvB peut éliminer movzx r32, r8 (c.-à-d. Le gérer avec un registre renommé sans unité d’exécution ni latence, comme xor-zeroing). Haswell et les movzx ultérieures éliminent uniquement les instructions mov , donc movzx prend une unité d’exécution et a une latence différente de zéro, ce qui rend test / setcc / movzx pire que xor / test / setcc , mais au moins aussi bon que test / mov r,0 / setcc (et beaucoup mieux sur les anciens CPU).

L’utilisation de setcc / movzx sans aucune mise à zéro est mauvaise sur AMD / P4 / Silvermont, car ils ne suivent pas les deps séparément pour les sous-registres. Il y aurait un faux dep sur l’ancienne valeur du registre. Utiliser mov reg, 0 / setcc pour la mise à zéro / la rupture de dépendance est probablement la meilleure alternative lorsque xor / test / setcc n’est pas une option.

Bien sûr, si vous n’avez pas besoin setcc la sortie de setcc soit plus large que 8 bits, vous n’avez rien à zéro. Cependant, méfiez-vous des fausses dépendances sur des processeurs autres que P6 / SnB si vous choisissez un registre qui a récemment fait partie d’une longue chaîne de dépendance. (Et méfiez-vous de provoquer un décrochage partiel ou un arrêt supplémentaire si vous appelez une fonction qui pourrait sauvegarder / restaurer le registre que vous utilisez.)


and avec un zéro immédiat, ce n’est pas un cas spécial indépendant de l’ancienne valeur sur les processeurs dont je suis au courant, donc il ne casse pas les chaînes de dépendance. Il ne présente aucun avantage par rapport à xor et de nombreux inconvénients.

Voir http://agner.org/optimize/ pour la documentation sur la microarchie, y compris quels idiomes de remise à zéro sont reconnus comme coupure de dépendance (par exemple sub same,same sur certains mais pas sur tous les processeurs, alors que xor same,same est reconnu sur tous). ne casse pas la chaîne de dépendance sur l’ancienne valeur du registre (quelle que soit la valeur source, zéro ou non, car c’est ainsi que fonctionne mov ). xor ne rompt que les chaînes de dépendance dans le cas particulier où src et dest sont le même registre, ce qui explique pourquoi mov est exclu de la liste des disjoncteurs de dépendances spécialement reconnus. (Aussi, parce que ce n’est pas reconnu comme idiome de la réduction à zéro, avec les autres avantages que cela comporte.)

Fait intéressant, la conception P6 la plus ancienne (PPro) ne reconnaissait pas xor -zeroing comme un disjoncteur de dépendance, seulement comme idiome de réduction à zéro dans le but d’éviter les blocages de registre partiel. (Voir l’exemple 6.17 d’Agner Fog dans son pdf de microarch. Il affirme que cela s’applique également à P2, P3, et même (tôt?) PM, mais je suis sceptique à ce sujet. Un commentaire sur le blog lié dit que c’était seulement PPro) Il semble vraiment improbable que plusieurs générations de la famille P6 aient existé sans reconnaître le xor-zeroing comme un disjoncteur dep.


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 performance autre que la taille du code. Éviter les drapeaux qui claquent est la seule raison raisonnable de ne pas utiliser xor .