19 Stimmen

Warum verwendet gcc movl anstelle von push, um Funktions-Args zu übergeben?

Beachten Sie diesen Code:

#include <stdio.h>
void a(int a, int b, int c)
{
    char buffer1[5];
    char buffer2[10];
}

int main()
{
    a(1,2,3); 
}

danach:

gcc -S a.c

Dieser Befehl zeigt unseren Quellcode in Assembler an.

In der Hauptfunktion wird nie der Befehl "push" verwendet, um die Argumente der Funktion a in den Stack zu schieben. einer Funktion in den Stack zu schieben. stattdessen wird "movel" verwendet

main:
 pushl %ebp
 movl %esp, %ebp
 andl $-16, %esp
 subl $16, %esp
 movl $3, 8(%esp)
 movl $2, 4(%esp)
 movl $1, (%esp)
 call a
 leave

Warum passiert das? Was ist der Unterschied zwischen ihnen?

22voto

Jester Punkte 54678

Hier ist, was das gcc-Handbuch darüber zu sagen hat:

-mpush-args
-mno-push-args
    Use PUSH operations to store outgoing parameters. This method is shorter and usually
    equally fast as method using SUB/MOV operations and is enabled by default. 
    In some cases disabling it may improve performance because of improved scheduling
    and reduced dependencies.

 -maccumulate-outgoing-args
    If enabled, the maximum amount of space required for outgoing arguments will be
    computed in the function prologue. This is faster on most modern CPUs because of
    reduced dependencies, improved scheduling and reduced stack usage when preferred
    stack boundary is not equal to 2. The drawback is a notable increase in code size.
    This switch implies -mno-push-args. 

Offensichtlich -maccumulate-outgoing-args ist standardmäßig aktiviert und hat Vorrang vor -mpush-args . Explizites Kompilieren mit -mno-accumulate-outgoing-args kehrt zum PUSH Methode, hier.


Aktualisierung 2019 Moderne CPUs verfügen etwa seit dem Pentium M über ein effizientes Push/Pop-System.
-mno-accumulate-outgoing-args (und die Verwendung von Push) wurde schließlich zum Standard für -mtune=generic im Januar 2014.

8voto

Ben Zotto Punkte 68632

Dieser Code setzt die Konstanten (1, 2, 3) einfach direkt an die Offset-Positionen des (aktualisierten) Stapelzeigers (esp). Der Compiler wählt die manuelle Durchführung des "Push" mit demselben Ergebnis.

"push" setzt sowohl die Daten als auch aktualisiert den Stapelzeiger. In diesem Fall reduziert der Compiler dies auf nur eine Aktualisierung des Stapelzeigers (im Gegensatz zu drei). Ein interessantes Experiment wäre es, die Funktion "a" so zu ändern, dass sie nur ein Argument annimmt, und zu sehen, ob sich das Anweisungsmuster ändert.

6voto

user502515 Punkte 4287

Gcc führt alle Arten von Optimierungen durch, einschließlich der Auswahl von Anweisungen auf der Grundlage der Ausführungsgeschwindigkeit der jeweiligen CPU, für die optimiert wird. Sie werden feststellen, dass Dinge wie x *= n wird häufig durch eine Mischung aus SHL, ADD und/oder SUB ersetzt, insbesondere wenn n eine Konstante ist; MUL wird nur verwendet, wenn die durchschnittliche Laufzeit (und der Cache- usw. Footprint) der Kombination aus SHL-ADD-SUB die Laufzeit von MUL übersteigen würde, oder n keine Konstante ist (und daher die Verwendung von Schleifen mit shl-add-sub teurer wäre).

Im Falle von Funktionsargumenten: MOV kann von der Hardware parallelisiert werden, PUSH hingegen nicht. (Das zweite PUSH muss wegen der Aktualisierung des esp-Registers warten, bis das erste PUSH beendet ist). Im Falle von Funktionsargumenten können MOVs parallel ausgeführt werden.

2voto

Ville Krumlinde Punkte 6883

Läuft das zufällig unter OS X? Ich habe irgendwo gelesen, dass der Stack-Zeiger an 16-Byte-Grenzen ausgerichtet sein muss. Das könnte möglicherweise diese Art der Codegenerierung erklären.

Ich habe den Artikel gefunden: http://blogs.embarcadero.com/eboling/2009/05/20/5607

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X