Ich habe Google gebeten, mir die Bedeutung der gcc
Option anzugeben -fomit-frame-pointer
, wodurch ich zur folgenden Anweisung weitergeleitet werde.
-fomit-frame-pointer
Bewahren Sie den Frame-Zeiger nicht in einem Register für Funktionen auf, die keinen benötigen. Dadurch werden die Anweisungen zum Speichern, Einrichten und Wiederherstellen von Frame-Zeigern vermieden. Es stellt auch ein zusätzliches Register in vielen Funktionen zur Verfügung. Es macht auch das Debuggen auf einigen Computern unmöglich.
Nach meinem Wissen über jede Funktion wird ein Aktivierungsdatensatz im Stapel des Prozessspeichers erstellt, um alle lokalen Variablen und einige weitere Informationen zu speichern. Ich hoffe, dieser Frame-Zeiger bedeutet die Adresse des Aktivierungsdatensatzes einer Funktion.
Was sind in diesem Fall die Arten von Funktionen, für die der Frame-Zeiger nicht in einem Register gespeichert werden muss? Wenn ich diese Informationen erhalte, werde ich versuchen, die neue Funktion basierend darauf zu entwerfen (falls möglich), da einige Anweisungen in Binärform weggelassen werden, wenn der Rahmenzeiger nicht in Registern gespeichert ist. Dies wird die Leistung in einer Anwendung mit vielen Funktionen spürbar verbessern.
quelle
Release
und dasDebug
ist tatsächlich sehr nützlich.Release
-build ausführt?Antworten:
Die meisten kleineren Funktionen benötigen keinen Frame-Zeiger - größere Funktionen benötigen möglicherweise einen.
Es geht wirklich darum, wie gut der Compiler es schafft, zu verfolgen, wie der Stapel verwendet wird und wo sich die Dinge auf dem Stapel befinden (lokale Variablen, an die aktuelle Funktion übergebene Argumente und Argumente, die für eine Funktion vorbereitet werden, die aufgerufen werden soll). Ich denke nicht, dass es einfach ist, die Funktionen zu charakterisieren, die einen Frame-Zeiger benötigen oder nicht benötigen (technisch gesehen muss KEINE Funktion einen Frame-Zeiger haben - es ist eher ein Fall von "wenn der Compiler es für notwendig hält, die Komplexität von zu reduzieren." anderer Code ").
Ich denke nicht, dass Sie "versuchen sollten, Funktionen so zu gestalten, dass sie keinen Frame-Zeiger haben" als Teil Ihrer Codierungsstrategie - wie gesagt, einfache Funktionen benötigen sie nicht. Verwenden
-fomit-frame-pointer
Sie sie also , und Sie erhalten ein weiteres Register zur Verfügung für den Registerzuordner und speichern Sie 1-3 Anweisungen beim Ein- / Ausstieg in Funktionen. Wenn Ihre Funktion einen Frame-Zeiger benötigt, ist der Compiler der Meinung, dass dies eine bessere Option ist, als keinen Frame-Zeiger zu verwenden. Es ist kein Ziel, Funktionen ohne Frame-Zeiger zu haben, sondern ein Code, der sowohl korrekt als auch schnell funktioniert.Beachten Sie, dass "kein Frame-Zeiger" eine bessere Leistung bringen sollte, aber es ist kein Wundermittel, das enorme Verbesserungen bringt - insbesondere nicht bei x86-64, das bereits 16 Register hat. Bei 32-Bit-x86 bedeutet dies, dass 25% des Registerplatzes belegt sind, da nur 8 Register vorhanden sind, von denen eines der Stapelzeiger ist und ein anderes als Rahmenzeiger verwendet wird. Dies auf 12,5% zu ändern, ist eine ziemliche Verbesserung. Natürlich hilft auch das Kompilieren für 64-Bit sehr.
quelle
alloca
die den Stapelzeiger um einen variablen Betrag bewegt. Das Auslassen von Frame-Zeigern erschwert das Debuggen erheblich. Lokale Variablen sind schwieriger zu lokalisieren und Stapelspuren sind viel schwieriger zu rekonstruieren, ohne dass ein Frame-Zeiger hilft. Außerdem kann der Zugriff auf Parameter teurer werden, da sie weit vom oberen Rand des Stapels entfernt sind und möglicherweise teurere Adressierungsmodi erfordern.alloca
[wer tut das? - Ich bin mir zu 99% sicher, dass ich noch nie Code geschrieben habe, deralloca
] odervariable size local arrays
[was eine moderne Form vonalloca
] ist, dann kann der Compiler immer noch entscheiden, dass die Verwendung eines Frame-Zeigers eine bessere Option ist - da Compiler so geschrieben sind, dass sie dem nicht blind folgen Optionen gegeben, aber geben Sie die besten Entscheidungen.alloca
: Sie werden weggeworfen, sobald Sie den Bereich verlassen, in dem sie deklariert sind, währendalloca
Speicherplatz nur freigegeben wird, wenn Sie die Funktion verlassen. Dies macht es viel einfacher, VLA zu folgen, alsalloca
ich denke.-fomit-frame-pointer
für x86-64 standardmäßig aktiviert ist.alloca
'ed space) zum Zeitpunkt der Kompilierung unbekannt ist . Normalerweise verwendet der Compiler den Frame-Zeiger, um die Adresse lokaler Variablen abzurufen. Wenn sich die Größe des Stack-Frames nicht ändert, kann er sie an einem festen Versatz zum Stack-Zeiger lokalisieren.Hier geht es um das BP / EBP / RBP-Register auf Intel-Plattformen. Dieses Register ist standardmäßig auf das Stapelsegment eingestellt (für den Zugriff auf das Stapelsegment ist kein spezielles Präfix erforderlich).
(Quelle - http://css.csail.mit.edu/6.858/2017/readings/i386/s02_03.htm )
Da auf den meisten 32-Bit-Plattformen Datensegment und Stapelsegment identisch sind, ist diese Zuordnung von EBP / RBP zum Stapel kein Problem mehr. Dies gilt auch für 64-Bit-Plattformen: Die von AMD 2003 eingeführte x86-64-Architektur hat die Unterstützung für die Segmentierung im 64-Bit-Modus weitgehend eingestellt: Vier der Segmentregister: CS, SS, DS und ES werden auf 0 gesetzt Diese Umstände von x86 32-Bit- und 64-Bit-Plattformen bedeuten im Wesentlichen, dass das EBP / RBP-Register ohne Präfix in den Prozessoranweisungen verwendet werden kann, die auf den Speicher zugreifen.
Die Compiler-Option, über die Sie geschrieben haben, ermöglicht es also, BP / EBP / RBP für andere Zwecke zu verwenden, z. B. um eine lokale Variable zu speichern.
Mit "Dies vermeidet die Anweisungen zum Speichern, Einrichten und Wiederherstellen von Frame-Zeigern" ist das Vermeiden des folgenden Codes bei der Eingabe jeder Funktion gemeint:
oder die
enter
Anweisung, die auf Intel 80286- und 80386-Prozessoren sehr nützlich war.Vor der Rückkehr der Funktion wird außerdem der folgende Code verwendet:
oder die
leave
Anweisung.Debugging-Tools können die Stack-Daten scannen und diese Push-EBP-Registerdaten beim Auffinden verwenden
call sites
, dh um die Namen der Funktion und die Argumente in der Reihenfolge anzuzeigen, in der sie hierarchisch aufgerufen wurden.Programmierer haben möglicherweise Fragen zu Stapelrahmen nicht in einem weiten Sinne (dass es sich um eine einzelne Entität im Stapel handelt, die nur einen Funktionsaufruf bedient und die Rücksprungadresse, Argumente und lokalen Variablen beibehält), sondern im engeren Sinne - wenn der Begriff
stack frames
in erwähnt wird den Kontext der Compileroptionen. Aus Sicht des Compilers ist ein Stapelrahmen nur der Eingangs- und Ausgangscode für die Routine , der einen Anker auf den Stapel schiebt - der auch zum Debuggen und zur Ausnahmebehandlung verwendet werden kann. Debugging-Tools können die Stapeldaten scannen und diese Anker für die Rückverfolgung verwenden, während sie sichcall sites
im Stapel befinden, dh um die Namen der Funktion in der Reihenfolge anzuzeigen, in der sie hierarchisch aufgerufen wurden.Aus diesem Grund ist es für einen Programmierer sehr wichtig zu verstehen, was ein Stapelrahmen in Bezug auf Compileroptionen ist - da der Compiler steuern kann, ob dieser Code generiert werden soll oder nicht.
In einigen Fällen kann der Compiler auf den Stapelrahmen (Eingangs- und Ausgangscode für die Routine) verzichten, und auf die Variablen wird direkt über den Stapelzeiger (SP / ESP / RSP) und nicht über den praktischen Basiszeiger (BP /) zugegriffen. ESP / RSP). Die Bedingungen, unter denen ein Compiler die Stapelrahmen für einige Funktionen weglässt, können unterschiedlich sein, zum Beispiel: (1) Die Funktion ist eine Blattfunktion (dh eine Endeinheit, die keine anderen Funktionen aufruft). (2) es werden keine Ausnahmen verwendet; (3) Es werden keine Routinen mit ausgehenden Parametern auf dem Stapel aufgerufen. (4) Die Funktion hat keine Parameter.
Das Weglassen von Stapelrahmen (Eingabe- und Ausstiegscode für die Routine) kann den Code kleiner und schneller machen, kann jedoch auch die Fähigkeit der Debugger beeinträchtigen, die Daten im Stapel zurückzuverfolgen und dem Programmierer anzuzeigen. Dies sind die Compileroptionen, die bestimmen, unter welchen Bedingungen eine Funktion erfüllt sein soll, damit der Compiler ihr den Stack-Frame-Ein- und Ausstiegscode zuweist. Beispielsweise kann ein Compiler in den folgenden Fällen Optionen zum Hinzufügen eines solchen Eingangs- und Ausgangscodes zu Funktionen haben: (a) immer, (b) nie, (c) bei Bedarf (Angabe der Bedingungen).
Zurück von den Allgemeinheiten zu den Besonderheiten: Wenn Sie die
-fomit-frame-pointer
GCC-Compileroption verwenden, können Sie sowohl beim Eingabe- als auch beim Ausstiegscode für die Routine und beim Vorhandensein eines zusätzlichen Registers gewinnen (es sei denn, es ist standardmäßig entweder selbst oder implizit von anderen aktiviert Optionen, in diesem Fall profitieren Sie bereits vom Gewinn der Verwendung des EBP / RBP-Registers, und durch explizite Angabe dieser Option wird kein zusätzlicher Gewinn erzielt, wenn sie bereits implizit aktiviert ist. Beachten Sie jedoch, dass das BP-Register im 16-Bit- und 32-Bit-Modus nicht wie AX (AL und AH) auf 8-Bit-Teile zugreifen kann.Da diese Option nicht nur dem Compiler ermöglicht, EBP als Allzweckregister für Optimierungen zu verwenden, sondern auch das Generieren von Exit- und Entry-Code für den Stack-Frame verhindert, was das Debuggen erschwert, wird in der GCC-Dokumentation ausdrücklich angegeben (ungewöhnlich fett hervorgehoben) Stil), dass das Aktivieren dieser Option das Debuggen auf einigen Computern unmöglich macht
Beachten Sie auch, dass andere Compileroptionen, die sich auf das Debuggen oder Optimieren beziehen, die
-fomit-frame-pointer
Option möglicherweise implizit ein- oder ausschalten.Ich habe auf gcc.gnu.org keine offiziellen Informationen darüber gefunden, wie sich andere Optionen
-fomit-frame-pointer
auf x86-Plattformen auswirken , https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html gibt nur folgendes an:Aus der Dokumentation an sich geht also nicht hervor, ob sie aktiviert
-fomit-frame-pointer
wird, wenn Sie nur mit einer einzigen-O
Option auf der x86-Plattform kompilieren . Es kann empirisch getestet werden, aber in diesem Fall sind die GCC-Entwickler nicht verpflichtet, das Verhalten dieser Option in Zukunft nicht ohne vorherige Ankündigung zu ändern.Doch Peter Cordes hat in den Kommentaren darauf hingewiesen , dass es einen Unterschied für die Standardeinstellungen der
-fomit-frame-pointer
zwischen x86-16 - Plattformen und x86-32 / 64 - Plattformen.Diese Option -
-fomit-frame-pointer
- ist auch für den Intel C ++ Compiler 15.0 relevant , nicht nur für den GCC:Für den Intel Compiler hat diese Option einen Alias
/Oy
.Folgendes hat Intel darüber geschrieben:
Bitte beachten Sie, dass das obige Zitat nur für den Intel C ++ 15-Compiler relevant ist, nicht für GCC.
quelle
gcc -m16
existiert, aber das ist ein seltsamer Sonderfall, bei dem im Grunde genommen 32-Bit-Code erstellt wird, der im 16-Bit-Modus mit Präfixen überall ausgeführt wird. Beachten Sie auch, dass dies-fomit-frame-pointer
unter x86 seit Jahren standardmäßig aktiviert-m32
ist und länger als unter x86-64 (-m64
).