2009-07-16 4 views
39

Il existe deux façons bien connues de définir un registre d'entier à valeur nulle sur x86.Est-ce que l'utilisation de xor reg, reg donne un avantage sur mov reg, 0?

Soit

mov reg, 0 

ou

xor reg, reg 

Il y a un avis que la deuxième variante est meilleure puisque la valeur 0 ne sont pas stockées dans le code et qui enregistre plusieurs octets de code machine produit. C'est certainement bon - moins de cache d'instructions est utilisé et cela peut parfois permettre une exécution plus rapide du code. De nombreux compilateurs produisent un tel code.

Cependant, il y a formellement une dépendance inter-instruction entre l'instruction xor et n'importe quelle instruction précédente qui change le même registre. Comme il y a une dérive, la dernière instruction doit attendre que la dernière instruction soit terminée, ce qui pourrait réduire la charge des unités de traitement et nuire aux performances.

add reg, 17 
;do something else with reg here 
xor reg, reg 

Il est évident que le résultat de xor sera exactement le même quelle que soit la valeur du registre initial. Mais le processeur est-il capable de le reconnaître?

J'ai essayé le test suivant dans VC++ 7:

const int Count = 10 * 1000 * 1000 * 1000; 
int _tmain(int argc, _TCHAR* argv[]) 
{ 
    int i; 
    DWORD start = GetTickCount(); 
    for(i = 0; i < Count ; i++) { 
     __asm { 
      mov eax, 10 
      xor eax, eax 
     }; 
    } 
    DWORD diff = GetTickCount() - start; 
    start = GetTickCount(); 
    for(i = 0; i < Count ; i++) { 
     __asm { 
      mov eax, 10 
      mov eax, 0 
     }; 
    } 
    diff = GetTickCount() - start; 
    return 0; 
} 

Avec des optimisations off les deux boucles prennent exactement en même temps. Cela prouve-t-il raisonnablement que le processeur reconnaît qu'il n'y a pas de dépendance de l'instruction xor reg, reg sur l'instruction mov eax, 0 précédente? Quel pourrait être un meilleur test pour vérifier cela?

+2

Je pense que c'est la raison pour laquelle nous utilisons des langages de haut niveau. Si vous voulez vraiment savoir, changez simplement l'étape du codegen pour faire l'un ou l'autre. Référence. Choisissez le meilleur. – jrockway

+3

ah, le vieux 'xor reg, reg' truc - bon vieux temps :) –

+1

Je pense que l'architecture x86 définit explicitement XOR reg, reg en tant que rupture de la dépendance sur reg. Voir le manuel d'architecture Intel. Je m'attendrais à ce que MOV reg ... fasse la même chose simplement parce que c'est un MOV. Donc, votre vrai choix est, lequel prend moins de place (je suppose que le temps d'exécution est le même), si vous ne vous souciez pas des bits d'état (XOR les endommage tous). –

Répondre

25

une réponse réelle pour vous:

Intel 64 and IA-32 Architectures Optimization Reference Manual

Section 3.5.1.8 est l'endroit où vous voulez regarder.

En résumé, il existe des situations dans lesquelles un xor ou un mov peut être préféré. Les enjeux sont centrés sur les chaînes de dépendance et la préservation des codes de condition.

+0

Il ne semble pas que le texte cité recommande d'utiliser un MOV dans n'importe quelle situation. – mwfearnley

+0

@mwfearnley Malheureusement, Addison a décidé d'éditer ma réponse et de sélectionner un sous-ensemble du contenu, on ne sait pas pourquoi cela a été fait. Vous devriez lire les documents complets qui couvrent les situations où mov est préféré. – Mark

+0

Merci pour la clarification. Je suppose que c'était une tentative d'éviter le problème avec le déplacement/changement de document, mais malheureusement la citation ne contenait pas tous les points dont il avait besoin .. Je peux voir maintenant de cette section, il dit d'utiliser MOV quand vous voulez éviter régler les codes de condition. – mwfearnley

2

Je pense que sur les architectures précédentes, l'instruction mov eax, 0 prenait aussi un peu plus de temps que la xor eax, eax ... et je ne me souviens pas exactement pourquoi. Sauf si vous avez beaucoup plus de mov s cependant, j'imagine que vous n'êtes pas susceptible de provoquer des échecs de cache en raison de ce littéral stocké dans le code.

Notez également que de mémoire le statut des drapeaux n'est pas identique entre ces méthodes, mais je me trompe peut-être.

12

J'ai cessé d'être capable de réparer mes propres voitures après avoir vendu ma familiale 1966. Je suis dans une solution similaire avec les processeurs modernes :-)

Cela dépendra vraiment du microcode ou des circuits sous-jacents. Il est tout à fait possible que le processeur puisse reconnaître "XOR Rn,Rn" et simplement mettre à zéro tous les bits sans se soucier du contenu. Mais bien sûr, il peut faire la même chose avec un "MOV Rn, 0". Un bon compilateur choisira de toute façon la meilleure variante pour la plate-forme cible, ce qui n'est généralement un problème que si vous codez en assembleur.

Si la CPU est assez intelligent, votre dépendance XOR disparaît car il sait la valeur est hors de propos et mis à zéro de toute façon (encore une fois cela dépend de la CPU réelle utilisée).

Cependant, je me soucie depuis longtemps de quelques octets ou de quelques cycles d'horloge dans mon code - cela semble être une micro-optimisation devenue folle.

+3

Qu'il s'agisse d'une optimisation excessive pour une utilisation pratique, il peut être utile de comprendre que toutes les instructions similaires ne sont pas égales. ;) – jerryjvl

+3

@jerryjvl - Il est également utile de se rendre compte que les processeurs x86 de bureau modernes n'exécutent pas de code machine x86 - ils décodent le x86 en une instruction interne RISC à exécuter. En tant que tels, ils peuvent reconnaître des séquences de code communes (comme xor eax, eax) et les traduire en instructions plus simples, comme peut-être une instruction «clear reg» à la place. Un xor réel n'est probablement pas fait dans ce cas. – Michael

+0

Il se peut que la micro-optimisation devienne folle lorsque vous écrivez un MBR =). – brianmearns

-8

Comme d'autres l'ont noté, la réponse est "qui s'en soucie?". Est-ce que vous écrivez un compilateur?

Et sur une deuxième note, votre analyse comparative ne fonctionnera probablement pas, car vous avez probablement une succursale qui prend tout le temps. (sauf si votre compilateur déroule la boucle pour vous)

Une autre raison pour laquelle vous ne pouvez pas référencer une seule instruction dans une boucle est que tout votre code sera mis en cache (contrairement au code réel). Donc, vous avez pris une grande partie de la différence de taille entre mov eax, 0 et xor eax, eax hors de l'image en l'ayant en L1-mis en cache tout le temps. Je suppose que toute différence de performance mesurable dans le monde réel serait due à la différence de taille qui alimente le cache, et non au temps d'exécution des deux options.

+9

Tout le site a une qualité "qui se soucie" du reste du monde. Je ne pense pas que ce serait une bonne réponse. –

9

x86 a des instructions de longueur variable. MOV EAX, 0 nécessite un ou deux octets de plus dans l'espace de code que XOR EAX, EAX.

+5

'mov eax, 0' est de 5 octets: un pour l'opcode' mov eax, imm32', et 4 pour le 4B des données immédiates. 'xor eax, eax' est de 2 octets: un opcode' xor r32, r/m32', un pour les opérandes. –

6

Sur les processeurs modernes, le modèle XOR est préféré. C'est plus petit et plus rapide.

Plus petit est important car sur de nombreuses charges de travail réelles, l'un des principaux facteurs limitant les performances est l'absence d'i-cache. Cela ne serait pas capturé dans un micro-benchmark comparant les deux options, mais dans le monde réel, il rendra le code légèrement plus rapide. Et, en ignorant les échecs réduits d'i-cache, XOR sur n'importe quel CPU au cours des dernières années est la même vitesse ou plus rapide que MOV. Qu'est-ce qui pourrait être plus rapide que l'exécution d'une instruction MOV? N'exécute aucune instruction du tout! Sur les processeurs Intel récents, la logique dispatch/rename reconnaît le motif XOR, "réalise" que le résultat sera nul et pointe simplement le registre sur un registre physique zéro. Il jette ensuite l'instruction car il n'est pas nécessaire de l'exécuter. Le résultat net est que le modèle XOR utilise zéro ressources d'exécution et peut, sur les processeurs Intel récents, «exécuter» quatre instructions par cycle. MOV termine à trois instructions par cycle.

Pour plus de détails, voir ce billet de blog que j'ai écrit:

https://randomascii.wordpress.com/2012/12/29/the-surprising-subtleties-of-zeroing-a-register/

La plupart des programmeurs ne devraient pas être inquiétant à ce sujet, mais les auteurs du compilateur ne doivent inquiéter, et il est bon de comprendre le code qui est étant généré, et c'est juste cool!

Questions connexes