53 votes

"entrer" contre "pousser ebp ; mov ebp, esp ; sub esp, imm" et "quitter" contre "mov esp, ebp ; pop ebp".

Quelle est la différence entre le enter et

push ebp
mov  ebp, esp
sub  esp, imm

des instructions ? Y a-t-il une différence de performance ? Si oui, laquelle est la plus rapide et pourquoi les compilateurs utilisent-ils toujours la seconde ?

De la même manière que pour le leave et

mov  esp, ebp
pop  ebp

des instructions.

0 votes

51voto

hirschhornsalz Points 16306

Il y a une différence de performance, surtout pour enter . Sur les processeurs modernes, cela se décode en quelque 10 à 20 µops, tandis que la séquence de trois instructions est d'environ 4 à 6, selon l'architecture. Pour plus de détails, consultez Agner Fog's les tableaux d'instructions.

En outre, le enter a généralement une latence assez élevée, par exemple 8 horloges sur un cœur2, par rapport à la chaîne de dépendance de 3 horloges de la séquence de trois instructions.

En outre, la séquence de trois instructions peut être étalée par le compilateur à des fins d'ordonnancement, en fonction du code environnant bien sûr, pour permettre une exécution plus parallèle des instructions.

6voto

Necrolis Points 17569

Il n'y a pas de réel avantage de vitesse en utilisant l'un ou l'autre, bien que la méthode longue fonctionnera probablement mieux en raison du fait que les CPU de nos jours sont plus "optimisés" pour les instructions plus courtes et plus simples qui sont plus génériques dans l'utilisation (plus il permet la saturation des ports d'exécution si vous êtes chanceux).

L'avantage de LEAVE (qui est toujours utilisé, il suffit de voir les dlls de Windows) est qu'il est plus petit que de démolir manuellement un cadre de pile, ce qui est très utile lorsque votre espace est limité.

Les manuels d'instructions d'Intel (le volume 2A pour être précis) contiennent plus de détails minutieux sur les instructions, il faut donc Manuel d'optimisation du Dr Agner Fogs

6voto

Pr0c3ss0r Points 21

Lors de la conception du 80286, les concepteurs du CPU d'Intel ont décidé d'ajouter deux instructions pour aider à maintenir les affichages.

Voici le micro code à l'intérieur de l'unité centrale :

; ENTER Locals, LexLevel

push    bp              ;Save dynamic link.
mov     tempreg, sp     ;Save for later.
cmp     LexLevel, 0     ;Done if this is lex level zero.
je      Lex0

lp:
dec     LexLevel
jz      Done            ;Quit if at last lex level.
sub     bp, 2           ;Index into display in prev act rec
push    [bp]            ; and push each element there.
jmp     lp              ;Repeat for each entry.

Done:
push    tempreg         ;Add entry for current lex level.

Lex0:
mov     bp, tempreg     ;Ptr to current act rec.
sub     sp, Locals      ;Allocate local storage

L'alternative à ENTER serait :

; entrer n, 0 ;14 cycles sur le 486

push    bp              ;1 cycle on the 486
sub     sp, n           ;1 cycle on the 486

; entrer n, 1 ;17 cycles sur le 486

push    bp              ;1 cycle on the 486
push    [bp-2]          ;4 cycles on the 486
mov     bp, sp          ;1 cycle on the 486
add     bp, 2           ;1 cycle on the 486
sub     sp, n           ;1 cycle on the 486

; entrer n, 3 ;23 cycles sur le 486

push    bp              ;1 cycle on the 486
push    [bp-2]          ;4 cycles on the 486
push    [bp-4]          ;4 cycles on the 486
push    [bp-6]          ;4 cycles on the 486
mov     bp, sp          ;1 cycle on the 486
add     bp, 6           ;1 cycle on the 486
sub     sp, n           ;1 cycle on the 486

Ect. La méthode longue peut augmenter la taille de votre fichier, mais elle est beaucoup plus rapide.

Pour finir, les programmeurs n'utilisent plus vraiment l'affichage depuis qu'il s'agissait d'une solution de contournement très lente, rendant ENTER plutôt inutile maintenant.

Fuente: https://courses.engr.illinois.edu/ece390/books/artofasm/CH12/CH12-3.html

2voto

Peter Cordes Points 1375

enter est inutilement lent sur tous les processeurs, personne ne l'utilise, sauf peut-être pour optimiser la taille du code au détriment de la vitesse. (Si un pointeur de trame est nécessaire du tout, ou souhaité pour permettre des modes d'adressage plus compacts pour l'adressage de l'espace de pile).

leave es assez rapide pour mériter d'être utilisé et le CCG hace l'utiliser (si l'ESP / RSP ne pointe pas déjà vers un EBP/RBP sauvegardé ; sinon, il utilise simplement pop ebp ).

leave n'est que de 3 uops sur les CPU Intel modernes (et 2 sur certains AMD). ( https://agner.org/optimize/ , https://uops.info/ ).

mov / pop ne représente que 2 uops au total (sur les x86 modernes où un "moteur de pile" suit les mises à jour de ESP/RSP). Donc leave est juste un uop de plus que de faire les choses séparément. J'ai testé cela sur Skylake, en comparant un appel/ret dans une boucle avec la fonction qui met en place un pointeur de trame traditionnel et qui démonte sa trame de pile à l'aide de mov / pop ou leave . perf compteurs pour uops_issued.any montre une uop frontale de plus lorsque vous utilisez leave que pour mov/pop (j'ai fait mon propre test au cas où d'autres méthodes de mesure auraient compté une uop stack-sync dans leurs mesures de leave, mais l'utiliser dans une fonction réelle contrôle cela).

Les raisons possibles pour lesquelles les anciens processeurs auraient pu bénéficier davantage de la séparation mov / pop :

  • Dans la plupart des CPU sans cache uop (c'est-à-dire Intel avant Sandybridge, AMD avant Zen), les instructions multi-uop peuvent constituer un goulot d'étranglement au niveau du décodage. Elles ne peuvent être décodées que dans le premier décodeur ("complexe"), ce qui peut signifier que le cycle de décodage précédent a produit moins d'uop que la normale.

  • Certaines conventions d'appel de Windows sont callee-pops stack args, l'utilisation de ret n . (par exemple ret 8 pour faire ESP/RSP += 8 après avoir fait sauter l'adresse de retour). Il s'agit d'une instruction multi-uop, contrairement à l'instruction simple near. ret sur les x86 modernes. La raison ci-dessus est donc double : laisser et ret 12 ne pouvait pas décoder dans le même cycle

  • Ces raisons s'appliquent également au décodage ancien pour construire les entrées du uop-cache.

  • Le Pentium P5 préférait également un sous-ensemble de x86 de type RISC, ne pouvant même pas décomposer les instructions complexes en uops distincts. du tout .

Pour les processeurs modernes , leave occupe 1 uop supplémentaire dans le cache uop. Et les 3 doivent être dans la même ligne du cache uop, ce qui pourrait conduire à un remplissage partiel de la ligne précédente. Donc une taille de code x86 plus grande pourrait améliorent en fait l'empaquetage dans le cache uop. Ou pas, selon la façon dont les choses s'alignent.

L'économie de 2 octets (ou 3 en mode 64 bits) peut ou non valoir 1 uop supplémentaire par fonction.

Le CCG favorise leave , clang et MSVC favorisent mov / pop (même avec clang -Oz optimisation de la taille du code, même au détriment de la vitesse, par exemple en faisant des choses comme push 1 / pop rax (3 octets) au lieu de 5 octets. mov eax,1 ).

La CPI privilégie le mov/pop, mais avec -Os utilisera leave . https://godbolt.org/z/95EnP3G1f

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