wake-up-neo.com

LEA- oder ADD-Anweisung?

Wenn ich handschriftliche Assembler bin, wähle ich im Allgemeinen die Form

lea eax, [eax+4]

Über das Formular ..

add eax, 4

Ich habe gehört, dass lea eine "0-Uhr" -Anweisung ist (wie NOP), während 'add' dies nicht ist. Wenn ich mir jedoch die vom Compiler erstellte Assembly anschaue, sehe ich oft die letztere Form anstelle der ersten. Ich bin klug genug, um dem Compiler zu vertrauen. Kann also jemand etwas Licht ins Dunkel bringen, welches besser ist? Welches ist schneller? Warum wählt der Compiler die letztere Form gegenüber der ersteren?

42
jakobbotsch

Ein wesentlicher Unterschied zwischen LEA und ADD bei x86-CPUs ist die Ausführungseinheit, die die Anweisung tatsächlich ausführt. Moderne x86-CPUs sind superskalar und verfügen über mehrere Ausführungseinheiten, die parallel arbeiten. Die Pipeline speist sie wie Rundläufer (Barstände). Das heißt, LEA wird von (einer) der Einheit (en) verarbeitet, die sich mit der Adressierung befasst (was zu einem frühen Zeitpunkt in der Pipeline geschieht), während ADD zu den ALU (s) (arithmetische/logische Einheit) geht und spät dran geht die Pipeline. Das heißt, eine superskalare x86-CPU kann gleichzeitig eine LEA- und eine arithmetische/logische Anweisung ausführen.

Die Tatsache, dass LEA die Adressgenerierungslogik anstelle der Arithmetikeinheiten durchläuft, ist auch der Grund, warum sie früher als "Nulltakte" bezeichnet wurde. Die Ausführung dauert nicht lange, da die Adressgenerierung bereits geschehen ist zu dem Zeitpunkt, zu dem sie ausgeführt wurde/wird.

Es ist nicht free , da die Adressgenerierung ein Schritt in der Ausführungspipeline ist, aber keinen Ausführungsaufwand hat. Und es belegt keinen Platz in der ALU-Pipeline (n).

Editieren: Zur Verdeutlichung ist LEA nicht frei . Selbst bei CPUs, die es nicht über die Recheneinheit implementieren, dauert die Ausführung aufgrund der Befehlsdecodierung/-abgabe/-abnahme und/oder anderer Pipeline-Stufen, die von all - Anweisungen durchlaufen werden, einige Zeit. Die Zeit, die erforderlich ist, um LEA auszuführen, tritt gerade auf in einer anderen Phase der Pipeline für CPUs, die dies über die Adressgenerierung implementieren.

51
FrankH.

Ich bin intelligent genug, um dem Compiler zu vertrauen. Kann also jemand etwas Licht aufgeben, welches besser ist?

Ja ein bisschen. Zunächst nehme ich dies aus der folgenden Meldung an: https://groups.google.com/group/bsdnt-devel/msg/23a48bb18571b9a6

In dieser Nachricht optimiert ein Entwickler einige Assemblys, die ich sehr schlecht geschrieben habe, um in Intel Core 2-Prozessoren rasend schnell zu laufen. Als Hintergrund für dieses Projekt ist es eine bsdum-Bibliothek von bsd, an der ich und einige andere Entwickler beteiligt waren. 

In diesem Fall wird lediglich das Hinzufügen von zwei Arrays optimiert, die wie folgt aussehen: uint64_t* x, uint64_t* y. Jedes Glied oder Glied des Arrays stellt einen Teil des Bignums dar; Der grundlegende Prozess besteht darin, von der niederwertigen Extremität aus iterieren, das Paar aufaddieren und nach oben fortfahren, wobei der Carry (jeder Überlauf) jedes Mal weitergegeben wird. adc erledigt das für Sie auf einem Prozessor (es ist nicht möglich, auf das Carry-Flag von C zuzugreifen, glaube ich nicht).

In diesem Code wird eine Kombination aus lea something, [something+1] und jrcxz verwendet, die offensichtlich effizienter sind als das jnz/add something, size-Paar, das wir zuvor verwendet haben könnten. Ich bin mir jedoch nicht sicher, ob dies durch einfaches Testen verschiedener Anweisungen entdeckt wurde. Sie müssten fragen.

In einer späteren Nachricht wird sie jedoch auf einem AMD-Chip gemessen und funktioniert nicht so gut.

Ich verstehe auch, dass verschiedene Vorgänge auf verschiedenen Prozessoren unterschiedlich ablaufen. Ich kenne zum Beispiel, dass das GMP-Projekt Prozessoren unter Verwendung von cpuid erkennt und verschiedene Assembly-Routinen durchläuft, die auf unterschiedlichen Architekturen basieren, z. core2, nehalem.

Die Frage, die Sie sich stellen müssen, lautet: Produziert Ihr Compiler für Ihre CPU-Architektur optimierte Ausgaben? Der Intel-Compiler zum Beispiel ist dafür bekannt, dass es sich lohnt, die Leistung zu messen und zu sehen, welche Ausgabe er erzeugt.

15
user257111

LEA ist nicht schneller als der Befehl ADD. Die Ausführungsgeschwindigkeit ist gleich.

Aber LEA bietet manchmal mehr als ADD . Wenn wir einfache und schnelle Additionen/Multiplikationen in Kombination mit einem zweiten Register benötigen, kann LEA die Programmausführung beschleunigen. Von der anderen Seite aus kann das LEA wirkt sich nicht auf die CPU-Flags aus, dh Es gibt keine Möglichkeit zur Überlauferkennung.

8
GJ.

Der Hauptgrund ist der nächste. Wie Sie feststellen können, wenn Sie den x86 genau betrachten, ist diese ISA zwei Adressen. Jede Anweisung akzeptiert höchstens zwei Argumente. Als nächstes kommt die Semantik der Operationen:

DST = DST <operation> SRC

Die LEA ist eine Art Hack-Anweisung, da es sich um die SINGLE-Anweisung in x86 ISA handelt, die eigentlich drei Adressen hat:

DST = SRC1 <operation> SRC2

Es ist eine Art Hackbefehl, da er die Dispatcher-Schaltung der x86-CPU für das Hinzufügen und Verschieben wiederverwendet.

Compiler verwenden LEA, weil diese Anweisung es ihnen ermöglicht, wenige Anweisungen durch eine einzige Anweisung zu ersetzen, wenn der Inhalt der Summand-Register für die Beibehaltung der Änderungen von Vorteil ist. Beachten Sie, dass in allen Fällen, in denen der Compiler das LEA-DST-Register verwendet, das SRC-Register oder das SRC-Argument eine komplexe Adressberechnungslogik ausnutzt.

Beispielsweise ist es fast unmöglich, im generierten Code einen solchen Anwendungsfall zu finden:

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

die nächsten Anwendungsfälle sind jedoch üblich:

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

Stellen Sie sich das nächste Szenario vor, bei dem davon ausgegangen wird, dass der Wert von EBP für die zukünftige Verwendung erhalten bleiben sollte:

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

Nur zwei Anweisungen! Im Falle der Abwesenheit von LEA ist der Code jedoch der nächste

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

Ich glaube, dass der Nutzen der LEA-Nutzung jetzt offensichtlich sein sollte. Sie können versuchen, diese Anweisung zu ersetzen

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

durch ADD-basierten Code. 

1
ZarathustrA

Sie können eine Lea-Anweisung in demselben Takt wie eine Add-Operation ausführen, aber wenn Sie Lea und Add verwenden, können Sie in nur einem Zyklus drei Operanden addieren! Wenn Sie zwei Add-Operationen verwenden würden, die nur in 2 Taktzyklen ausgeführt werden könnten:

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