Ich bin ein Anfänger in Assemblersprache und habe festgestellt, dass der von Compilern ausgegebene x86-Code den Frame-Zeiger normalerweise auch im Release- / optimierten Modus herumhält, wenn er das EBP
Register für etwas anderes verwenden könnte.
Ich verstehe, warum der Frame-Zeiger das Debuggen von Code erleichtert und möglicherweise erforderlich ist, wenn alloca()
er innerhalb einer Funktion aufgerufen wird. X86 hat jedoch nur sehr wenige Register, und zwei davon zu verwenden, um die Position des Stapelrahmens zu halten, wenn eines ausreichen würde, macht für mich einfach keinen Sinn. Warum wird das Weglassen des Frame-Zeigers selbst in optimierten / Release-Builds als schlechte Idee angesehen?
performance
assembly
x86
Dsimcha
quelle
quelle
alloca
) 3. Einfache Laufzeitimplementierung: Ausnahmebehandlung, Sandbox, GCAntworten:
Der Frame-Zeiger ist ein Referenzzeiger, mit dem ein Debugger mit einem einzigen konstanten Offset erkennen kann, wo sich die lokale Variable oder ein Argument befindet. Obwohl sich der Wert von ESP im Laufe der Ausführung ändert, bleibt EBP derselbe, sodass dieselbe Variable bei demselben Offset erreicht werden kann (z. B. der erste Parameter liegt immer bei EBP + 8, während sich die ESP-Offsets erheblich ändern können, da Sie Druck ausüben / knallende Dinge)
Warum werfen Compiler den Frame-Zeiger nicht weg? Denn mit dem Frame-Zeiger kann der Debugger herausfinden, wo lokale Variablen und Argumente die Symboltabelle verwenden, da sie garantiert einen konstanten Versatz zu EBP aufweisen. Andernfalls gibt es keine einfache Möglichkeit, herauszufinden, wo sich eine lokale Variable an einem beliebigen Punkt im Code befindet.
Wie Greg erwähnt hat, hilft es auch beim Abwickeln des Stapels für einen Debugger, da EBP eine umgekehrt verknüpfte Liste von Stapelrahmen bereitstellt, sodass der Debugger die Größe des Stapelrahmens (lokale Variablen + Argumente) der Funktion ermitteln kann.
Die meisten Compiler bieten die Möglichkeit, Frame-Zeiger wegzulassen, obwohl dies das Debuggen sehr erschwert. Diese Option sollte niemals global verwendet werden, auch nicht im Release-Code. Sie wissen nicht, wann Sie den Absturz eines Benutzers debuggen müssen.
quelle
-fomit-frame-pointer
. Diese Einstellung ist die Standardeinstellung in der letzten gcc..eh_frame_hdr
Abschnitt wird auch für Laufzeitausnahmen verwendet. Sie finden es (mitobjdump -h
) in den meisten Binärdateien auf einem Linux-System. Es ist ungefähr 16k für/bin/bash
, gegenüber 572B für GNU/bin/true
, 108k fürffmpeg
. Es gibt eine gcc-Option, um das Generieren zu deaktivieren, aber es ist ein "normaler" Datenabschnitt, kein Debug-Abschnitt,strip
der standardmäßig entfernt wird. Andernfalls könnten Sie nicht durch eine Bibliotheksfunktion zurückverfolgen, die keine Debug-Symbole hatte. Dieser Abschnitt ist möglicherweise größer als diepush/mov/pop
Anweisungen, die er ersetzt, hat jedoch Laufzeitkosten nahe Null (z. B. UOP-Cache).Ich addiere nur meine zwei Cent zu bereits guten Antworten.
Es ist Teil einer guten Spracharchitektur, eine Kette von Stapelrahmen zu haben. Der BP zeigt auf den aktuellen Frame, in dem subroutinenlokale Variablen gespeichert sind. (Einheimische haben negative Offsets und Argumente positive Offsets.)
Die Idee, dass verhindert wird, dass ein perfektes Register für die Optimierung verwendet wird, wirft die Frage auf: Wann und wo lohnt sich die Optimierung tatsächlich?
Die Optimierung lohnt sich nur in engen Schleifen, in denen 1) keine Funktionen aufgerufen werden, 2) der Programmzähler einen erheblichen Teil seiner Zeit verbringt und 3) der Code, den der Compiler tatsächlich jemals sehen wird (dh Funktionen außerhalb der Bibliothek). Dies ist normalerweise ein sehr kleiner Teil des gesamten Codes, insbesondere in großen Systemen.
Anderer Code kann verdreht und zusammengedrückt werden, um Zyklen loszuwerden, und es spielt einfach keine Rolle, da der Programmzähler praktisch nie da ist.
Ich weiß, dass Sie dies nicht gefragt haben, aber meiner Erfahrung nach haben 99% der Leistungsprobleme überhaupt nichts mit der Compileroptimierung zu tun. Sie haben alles mit Überdesign zu tun.
quelle
Das hängt natürlich vom Compiler ab. Ich habe optimierten Code gesehen, der von x86-Compilern ausgegeben wird, die das EBP-Register frei als Allzweckregister verwenden. (Ich erinnere mich jedoch nicht, mit welchem Compiler ich das bemerkt habe.)
Compiler können auch das EBP-Register verwalten, um das Abwickeln des Stapels während der Ausnahmebehandlung zu unterstützen. Dies hängt jedoch wiederum von der genauen Implementierung des Compilers ab.
quelle
-fomit-frame-pointer
wenn die Optimierung aktiviert ist. (wenn der ABI es erlaubt). GCC, Clang, ICC und MSVC machen es alle, IIRC, selbst wenn sie auf 32-Bit-Windows abzielen. Ja, meine Antwort auf Warum ist es besser, das ebp als das esp-Register zu verwenden, um Parameter auf dem Stapel zu lokalisieren? zeigt, dass auch 32-Bit-Windows den Frame-Zeiger weglassen kann. 32-Bit x86 Linux kann und tut es definitiv. Und natürlich haben 64-Bit-ABIs von Anfang an das Weglassen von Frame-Zeigern ermöglicht.Dies gilt nur in dem Sinne, dass Opcodes nur 8 Register adressieren können. Der Prozessor selbst wird tatsächlich viel mehr Register haben und Registerumbenennung, Pipelining, spekulative Ausführung und andere Prozessor-Schlagworte verwenden, um diese Grenze zu umgehen. Wikipedia hat einen guten einleitenden Abschnitt darüber, was ein x86-Prozessor tun kann, um das Registerlimit zu überwinden: http://en.wikipedia.org/wiki/X86#Current_implementations .
quelle
Die Verwendung von Stack-Frames ist in jeder Hardware, auch aus der Ferne, unglaublich billig geworden. Wenn Sie billige Stapelrahmen haben, ist das Speichern einiger Register nicht so wichtig. Ich bin sicher, dass schnelle Stapelrahmen im Vergleich zu mehr Registern ein technischer Kompromiss waren und schnelle Stapelrahmen gewonnen haben.
Wie viel sparen Sie bei der reinen Registrierung? Lohnt es sich?
quelle