63 votes

L'utilisation de xor reg, reg donne-t-elle un avantage par rapport à mov reg, 0 ?

Il y a deux façons bien connues de mettre un registre entier à zéro sur x86.

Soit

mov reg, 0

ou

xor reg, reg

Certains pensent que la deuxième variante est meilleure car la valeur 0 n'est pas stockée dans le code, ce qui permet d'économiser plusieurs octets de code machine produit. C'est certainement une bonne chose - moins de cache d'instructions est utilisé et cela peut parfois permettre une exécution plus rapide du code. De nombreux compilateurs produisent ce type de code.

Cependant, il existe officiellement une dépendance inter-instruction entre l'instruction xor et toute instruction antérieure qui modifie le même registre. Comme il y a une dépendance, la dernière instruction doit attendre que la première soit terminée, ce qui peut réduire la charge des unités du processeur 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 initiale du registre. Mais le processeur est-il capable de le reconnaître ?

J'ai essayé le test suivant en 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 les optimisations désactivées, les deux boucles prennent exactement le même temps. Est-ce que cela prouve raisonnablement que le processeur reconnaît qu'il n'y a pas de dépendance de xor reg, reg l'instruction sur l'ancien mov eax, 0 instruction ? Quel pourrait être un meilleur test pour vérifier cela ?

2 votes

Je pense que c'est pour cela que nous utilisons des langages de haut niveau. Si vous voulez vraiment savoir, il suffit de changer l'étape du codegen pour faire l'un ou l'autre. Benchmark. Choisissez le meilleur.

3 votes

Ah, l'ancien xor reg, reg truc - le bon vieux temps :)

2 votes

Je pense que l'architecture x86 définit explicitement XOR reg,reg comme brisant la dépendance à reg. Voir le manuel de l'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 de savoir lequel prend le 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).

34voto

Mark Points 2257

Une vraie réponse pour vous :

Manuel de référence sur l'optimisation des architectures Intel 64 et IA-32

La section 3.5.1.8 est l'endroit où vous devez regarder.

En bref, il y a des situations où un xor ou un mov peut être préféré. Les problèmes concernent les chaînes de dépendance et la préservation des codes de condition.

0 votes

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

0 votes

@mwfearnley Malheureusement, Addison a décidé d'éditer ma réponse et de sélectionner un sous-ensemble du contenu, la raison n'est pas claire. Vous devriez lire la documentation complète qui couvre les situations où mov est préférable.

0 votes

Merci de clarifier. Je suppose que c'était une tentative d'éviter le problème du déplacement/changement du document, mais malheureusement la citation ne contenait pas tous les points nécessaires Je vois maintenant que dans cette section, il est dit d'utiliser MOV lorsque vous voulez éviter de définir les codes de condition.

13voto

ajs410 Points 899

Le 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.

9 votes

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

12voto

paxdiablo Points 341644

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

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

Si le CPU est assez intelligent, votre XOR La dépendance disparaît puisqu'elle connaît la valeur n'est pas pertinente et sera mise à zéro de toute façon (encore une fois, cela dépend de l'unité centrale utilisée).

Cependant, je ne me soucie plus depuis longtemps de quelques octets ou de quelques cycles d'horloge dans mon code - cela ressemble à une micro-optimisation devenue folle.

4 votes

Indépendamment du fait qu'il s'agisse d'une optimisation excessive pour un usage pratique, il peut être utile de comprendre que toutes les instructions similaires ne sont pas créées égales. ;)

3 votes

@jerryjvl - Il est également utile de réaliser que les CPU x86 de bureau modernes n'exécutent pas le code machine x86 - ils décodent le x86 en instructions internes de type RISC à exécuter. En tant que tels, ils peuvent reconnaître les 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.

1 votes

La micro-optimisation peut avoir besoin de devenir folle lorsque vous écrivez un MBR =).

2voto

jerryjvl Points 9310

Je pense que sur les architectures précédentes, le mov eax, 0 prenait un peu plus de temps que l'instruction xor eax, eax également... je ne me souviens pas exactement pourquoi. A moins que vous n'ayez beaucoup plus mov Cependant, j'imagine que vous n'êtes pas susceptible de provoquer des manques dans la mémoire cache en raison de ce seul 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 il se peut que je me souvienne mal de cela.

-9voto

Thomas Points 1464

Vous écrivez un compilateur ?

D'autre part, votre analyse comparative ne fonctionnera probablement pas, puisque vous avez une branche qui prend probablement tout le temps de toute façon. (sauf si votre compilateur déroule la boucle pour vous)

Une autre raison pour laquelle vous ne pouvez pas évaluer une instruction unique dans une boucle est que tout votre code sera mis en cache (contrairement au code réel). Vous avez donc éliminé une grande partie de la différence de taille entre mov eax,0 et xor eax,eax en l'ayant tout le temps dans le cache L1.

Je pense que toute différence de performance mesurable dans le monde réel serait due à la différence de taille qui consomme le cache, et non au temps d'exécution des deux options.

15 votes

Tout ce site Web a un côté "on s'en fout" pour le reste du monde. Je ne pense pas que ce soit une bonne réponse.

0 votes

Il semble que vous et d'autres se concentrent sur ce que vous percevez comme une offense. J'ai supprimé cette partie car je pense que vous et d'autres n'avez jamais lu au-delà de cela et avez juste rétrogradé.

0 votes

Pour Sandybridge / Ivybridge, vous pouvez assez facilement construire une boucle qui fonctionne à 1 itération par horloge avec nop o xor same,same mais des goulots d'étranglement sur le débit de l'unité d'exécution ALU avec mov reg,0 . Les processeurs Intel ultérieurs ont 4 unités d'exécution ALU, donc un exemple concret d'élimination de xor-zéro faisant une différence mesurable autre que la taille du code est beaucoup moins facile à construire. ( xorps La mise à zéro des registres xmm/ymm est toujours facile, parce qu'il y a moins de ports ALU vectoriels que la largeur du frontal). Et les CPU AMD n'éliminent pas l'uop back-end, donc l'avantage n'est vraiment que la taille du code.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X