web-dev-qa-db-fra.com

Instruction LEA ou ADD?

Quand j'écris à la main Assembly, je choisis généralement la forme

lea eax, [eax+4]

Sur le formulaire ..

add eax, 4

J'ai entendu dire que lea est une instruction "0-clock" (comme NOP), alors que "add" ne l'est pas. Cependant, quand je regarde Assembly créé par le compilateur, je vois souvent la dernière forme utilisée à la place de la première. Je suis assez intelligent pour faire confiance au compilateur. Quelqu'un peut-il nous éclairer pour savoir lequel est le meilleur? Lequel est le plus rapide? Pourquoi le compilateur choisit-il la dernière forme par rapport à la première?

42
jakobbotsch

Une différence significative entre LEA et ADD sur les processeurs x86 réside dans l'unité d'exécution qui exécute réellement l'instruction. Les processeurs x86 modernes sont superscalaires et possèdent plusieurs unités d’exécution qui fonctionnent en parallèle, le pipeline les alimentant un peu à la ronde. LEA est traité par (une des) unité (s) chargée (s) de l’adressage (ce qui se passe à un stade précoce du processus), alors que ADD est envoyé à la ou aux ALU (unités arithmétiques/logiques) et le pipeline. Cela signifie qu'un processeur x86 superscalar peut exécuter simultanément une instruction LEA et une instruction arithmétique/logique.

Le fait que LEA parcourt la logique de génération d'adresses au lieu des unités arithmétiques est également la raison pour laquelle on l'appelait auparavant "horloge zéro"; son exécution ne prend pas de temps car la génération d’adresses est déjà arrivée au moment où elle serait/est exécutée.

Ce n'est pas free , étant donné que la génération d'adresses est une étape du pipeline d'exécution, mais qu'elle ne génère pas de surcharge d'exécution. Et cela n'occupe pas de fente dans le (s) pipeline (s) d'ALU.

Edit: Pour clarifier, LEA est pas gratuit . Même sur les processeurs qui ne l'implémentent pas via l'unité arithmétique, son exécution prend du temps en raison des instructions de décodage/envoi/retrait et/ou d'autres étapes de pipeline par lesquelles les instructions all sont exécutées. Le temps nécessaire pour faire LEA se produit simplement dans une étape différente du pipeline pour les CPU qui l'implémentent via la génération d'adresses.

51
FrankH.

Je suis assez intelligent pour faire confiance au compilateur. Quelqu'un peut-il nous éclairer pour savoir lequel est le meilleur?

Oui un peu. Premièrement, je tire ceci du message suivant: https://groups.google.com/group/bsdnt-devel/msg/23a48bb18571b9a6

Dans ce message, un développeur optimise un assemblage que j’ai très mal écrit pour s’exécuter follement dans les processeurs Intel Core 2. En toile de fond de ce projet, il s’agit d’une bibliothèque bsd bignum à laquelle moi-même et quelques autres développeurs avons participé. 

Dans ce cas, tout ce qui est optimisé est l'ajout de deux tableaux ressemblant à ceci: uint64_t* x, uint64_t* y. Chaque "membre" ou membre du tableau représente une partie du bignum; le processus de base consiste à parcourir le membre en commençant par le membre le moins significatif, à additionner la paire et à continuer vers le haut, en passant le report (tout débordement) à chaque fois. adc le fait pour vous sur un processeur (il est impossible d'accéder à l'indicateur de portage depuis C, je ne pense pas).

Dans cet élément de code, une combinaison de lea something, [something+1] et jrcxz est utilisée, ce qui est apparemment plus efficace que la paire jnz/add something, size que nous aurions pu utiliser auparavant. Je ne suis pas sûr si cela a été découvert à la suite du test de différentes instructions, cependant. Tu devrais demander.

Cependant, dans un message ultérieur, il est mesuré sur une puce AMD et ne fonctionne pas aussi bien.

On me donne également à comprendre que différentes opérations fonctionnent différemment sur différents processeurs. Je sais, par exemple, que le projet GMP détecte des processeurs utilisant cpuid et transmet différentes routines d'assemblage basées sur différentes architectures, par exemple. core2, nehalem.

La question que vous devez vous poser est la suivante: votre compilateur produit-il une sortie optimisée pour votre architecture cpu? Le compilateur Intel, par exemple, est connu pour le faire. Il peut donc être intéressant de mesurer les performances et de voir ce qu’il produit en sortie.

15
user257111

LEA n'est pas plus rapide que l'instruction ADD, la vitesse d'exécution est la même.

Mais LEA offre parfois plus que ADD . Si nous avons besoin d’une addition/multiplication simple et rapide associée à un second registre, le LEA peut accélérer l’exécution du programme. De l’autre côté, LEA n'affecte pas les indicateurs de la CPU, donc il n'y a aucune possibilité de détection de dépassement de capacité.

8
GJ.

La raison principale est la suivante. Comme vous pouvez le constater si vous regardez attentivement le x86, ceci ISA est à deux adresses. Chaque instruction accepte au plus deux arguments. Ainsi, la sémantique des opérations est la suivante:

DST = DST <operation> SRC

Le LEA est une sorte d’instruction de hack, car c’est l’instruction SINGLE dans le x86 ISA qui est en réalité une adresse à trois adresses:

DST = SRC1 <operation> SRC2

C'est une sorte d'instruction de piratage, car elle réutilise le circuit de répartition des arguments du processeur x86 pour effectuer l'addition et le décalage.

Les compilateurs utilisent LEA parce que cette introduction leur permet de remplacer quelques intructions par une seule instruction dans les cas où le contenu des registres de sommandes est bénéfique pour conserver inchangé. Notez que, dans tous les cas, lorsque le compilateur utilise le registre LEA DST diffère du registre SRC, l'argument SRC exploite une logique de calcul d'adresse complexe.

Par exemple, il est presque impossible de trouver dans le code généré un tel cas d'utilisation:

LEA EAX, [EAX   ] // equivalent of NOP
LEA EAX, [ECX   ] // equivalent of MOV EAX, ECX
LEA EAX, [EAX+12] // equivalent of ADD EAX, 12

mais les prochains cas d'utilisation sont communs:

LEA EAX, [ECX      +12] // there is no single-instruction equivalent
LEA EAX, [ECX+EDX*4+12] // there is no single-instruction equivalent
LEA EDX, [ECX+EDX*4+12] // there is no single-instruction equivalent

En effet, imaginez le scénario suivant avec l’hypothèse que la valeur de l’EBP devrait être préservée pour une utilisation future:

LEA EAX, [EBP+12]
LEA EDX, [EBP+48]

Juste deux instructions! Mais en cas d'absence de LEA, le code sera le suivant

MOV EAX, EBP
MOV EDX, EBP
ADD EAX, 12
ADD EDX, 48

Je pense que l’avantage de l’utilisation de la LEA devrait être évident maintenant. Vous pouvez essayer de remplacer cette instruction

LEA EDX, [ECX+EDX*4+12] // there is no single-instruction equivalent

par code ADD. 

1
ZarathustrA

Vous pouvez effectuer une instruction Lea dans le même cycle d'horloge comme une opération d'ajout, mais si vous utilisez lea et Add ensemble, vous pouvez effectuer l'ajout de trois opérandes en un seul cycle! Si vous utilisiez deux opérations add qui ne pourraient être exécutées qu’en deux cycles:

mov eax, [esp+4]   ; get a from stack
mov edx, [esp+8]   ; get b from stack
mov ecx, [esp+12]  ; get c from stack
lea eax, [eax+edx] ; add a and b in the adress decoding/fetch stage of the pipeline
add eax, ecx       ; Add c + eax in the execution stage of the pipeline
ret 12
0
Sebi2020