Die Rückkehr der Frame Pointer
(brendangregg.com)- Fedora und Ubuntu 24.04 LTS bauen OS-Komponenten wie libc wieder standardmäßig mit Frame Pointern, sodass Linux-Profiling und Flame Graphs vollständigere Stacks sehen können
- Ohne Frame Pointer kann ein Profiler auf Bibliotheksebenen wie libc das Stack Walking abbrechen oder falschen Frames folgen, wodurch CPU- und Off-CPU-Flame-Graphs beschädigt werden können
- Die i386-Änderung in gcc von 2004 war eine Entscheidung, um ein weiteres Register freizumachen; sie breitete sich aber bis x86-64 aus und hinterließ langfristige Kosten für System-Profiler und eBPF-basierte Observability
- In den Erfahrungen von Netflix mit Java und libc lagen die Kosten meist bei unter 1%; einige Berichte nannten 1–2%, in speziellen Microbenchmarks oder ungewöhnlichen Workloads traten bis zu 10% auf
- Es gibt Alternativen wie LBR, DWARF, eBPF-Stack-Walker, ORC, SFrames und Shadow Stacks, aber die derzeit praktikabelste Methode, um in Produktion Performance-Verbesserungen zu finden, ist die standardmäßige Aktivierung von Frame Pointern
Warum Flame Graphs kaputtgehen
- Ein CPU Flame Graph kann äußerlich normal wirken, doch auf Systemen, auf denen libc ohne Frame Pointer kompiliert wurde, können einige Samples fälschlich über [unknown] gesammelt werden
- Im Beispiel liegen 15% der Samples links an der falschen Stelle, und die Applikations-Frames fehlen
- Der Profiler gelangt an den Kernel-Frames vorbei bis zum syscall und zum libc-syscall-Wrapper, scheitert dann aber beim Auflösen des Symbols des nächsten Frames
- Ursache ist, dass durch Compiler-Optimierung das Frame-Pointer-Register nicht als Bezugspunkt des Stack Frames, sondern zum Speichern von Daten verwendet wird
- Der Profiler kann nicht wissen, ob dieser Wert nur eine Zahl ist, und versucht ihn als Funktionsadresse zu interpretieren
- Wenn der Wert nicht auf den nächsten Frame zeigt, wird das Stack Walking abgebrochen
- Wenn er zufällig wie ein gültiger Pointer aussieht, kann ein falscher Junk Frame entstehen
- Wenn der Wert auf sich selbst zeigt, kann ein Turm aus Junk Frames entstehen, bis perf sein maximales Frame-Limit erreicht
- Ein Off-CPU Flame Graph kann viele libc-read/write- und Mutex-Funktionen enthalten und wird ohne Frame Pointer daher noch stärker beschädigt
- Wenn die Applikation selbst ebenfalls ohne Frame Pointer gebaut wurde, gerät nicht nur die Bibliothek, sondern die gesamte Stack Trace ins Wanken
Welche Informationen Frame Pointer liefern
- Die x86-64-ABI definiert, dass
%rbpals Base Pointer eines Stack Frames, also als Frame Pointer, verwendet werden kann - Externe Profiler und Debugger wie Linux perf und eBPF nutzen diese Information, um Stack Traces abzulaufen und die Ergebnisse als Flame Graph zu visualisieren
- In der x86-64-ABI gibt es eine Fußnote, dass die Nutzung von
%rbpoptional ist- Wenn der Stack Frame über
%rspindiziert wird, lassen sich in Prologue und Epilogue zwei Instruktionen einsparen %rbpkann als zusätzliches Allzweckregister verwendet werden
- Wenn der Stack Frame über
Die Entfernung 2004 und 20 Jahre Nachwirkungen
- 2004 wurde gcc im i386-Backend so geändert, dass es standardmäßig einem Verhalten entsprechend
-fomit-frame-pointer -ffixed-ebpfolgte - i386 hat nur vier Allzweckregister, daher vergrößert das Freigeben von
%ebpdie Zahl der nutzbaren Register erheblich- Zu den Gründen für die Änderung gehörte auch der Wunsch, eine bessere Performance als Intels
icc-Compiler zu erzielen - Damals ging man außerdem davon aus, dass Debugger nicht kaputtgehen würden, weil sie andere Stack-Walking-Verfahren unterstützten
- Zu den Gründen für die Änderung gehörte auch der Wunsch, eine bessere Performance als Intels
- Diese Änderung wurde auch auf x86-64 angewendet
- x86-64 hat 12 oder mehr Register, sodass der Vorteil eines zusätzlichen Registers nicht so groß ist wie bei i386
- Heutige System-Profiler wie eBPF existierten damals nicht, und diese Änderung kann sie beschädigen
- Eric Schrock war 2004 der Ansicht, dass der Vorteil eines 17. Allzweckregisters auf amd64 den Verlust an Debuggability nicht ausreichend aufwiegt
- Er urteilte, dass es außer Kontrolle geraten würde, sobald man anfange,
/usr/binohne Frame Pointer zu kompilieren - Unter Linux breitete sich derselbe Trend nicht nur auf
/usr/bin, sondern auch auf/usr/libund Applikationscode aus
- Er urteilte, dass es außer Kontrolle geraten würde, sobald man anfange,
Erfahrungen mit Java, libc und Netflix
- Als ich 2014 zu Netflix kam, waren alle Applikations-Stacks wegen fehlender Frame-Pointer-Unterstützung in Java kaputt
- Für den JVM-c2-Compiler wurde ein Patch erstellt, den Oracle überarbeitete und als Option
-XX:+PreserveFramePointerin JDK8u60 aufnahm - Durch die Java-Änderung konnten mehrere Performance-Verbesserungen im Applikationscode gefunden werden, aber libc beschädigte weiterhin einen Teil der CPU-Samples und die meisten Off-CPU-Flame-Graphs
- Später wurde intern eine libc mit Frame Pointern für die Produktion kompiliert, und gemeinsam mit Canonical wurde auch an einer vorgebauten libc für Ubuntu gearbeitet
- Eine Zeit lang wurde die Nutzung von
libc6-profempfohlen libc6-profwarlibc6mit Frame Pointern
- Eine Zeit lang wurde die Nutzung von
Performance-Overhead und Ausnahmefälle
- Beim Produktionseinsatz lagen die Kosten für das Hinzufügen von Frame Pointern zu libc und Java meist bei unter 1%
- Es gab eine Ausnahme mit 10% Overhead bei einer Applikation
- Es war eine ungewöhnliche Applikation, die über Groovy Stack Traces mit über 1000 Frames erzeugte
- Auch Linux perf konnte das nicht verarbeiten, sodass Arnaldo Carvalho de Melo von Red Hat für den Netflix-Workload das sysctl
kernel.perf_event_max_stackhinzufügte - Da die Umgebung eine virtuelle Maschine ohne Low-Level-Hardware-Profiling-Funktionen war, ließ sich nicht bestätigen, ob die 10% vollständig auf Frame Pointer zurückzuführen waren
- Andere Berichte zeigen ebenfalls Kosten von etwa 1% oder 2%
- Microbenchmarks können sich um bis zu 10% verschlechtern
- Wenn kleine Funktionen wiederholt in einer Schleife ausgeführt werden, können zusätzliche Instruktionen die L1-Cache-Wärme oder Cache-Line-Grenzen beeinflussen
- In diesem Fall kann derselbe Effekt auftreten, wenn irgendetwas zur Hot Function hinzugefügt wird, nicht nur ein Frame Pointer selbst
- Auch der Python-Benchmark
scimark_sparse_mat_multkonnte bis zu 10% erreichen- In Andrii Nakryikos Analyse war es ein Spezialfall, bei dem gcc in einer großen Funktion statt
%rsp-Offsets relative%rbp-Offsets verwendete, wodurch mehr Bytes benötigt wurden und ein Performance-Problem entstand - Später gab es auch die Nachricht, dass Python so geändert wurde, dass Frame Pointer wieder standardmäßig aktiviert werden können
- In Andrii Nakryikos Analyse war es ein Spezialfall, bei dem gcc in einer großen Funktion statt
- Die durch Frame Pointer gefundenen Performance-Verbesserungen reichten von 5% bis 500%, und Kosten von meist unter 1% gelten als vertretbar
- Auf Geräten ohne Möglichkeit für Profiling oder Debugging kann man ohne Frame Pointer kompilieren, aber das Hauptziel sind Enterprise Linux und Backend-Server
Standardmäßige Aktivierung in Fedora und Ubuntu
- Große Unternehmen wie Meta, Google und Netflix nutzten bereits intern libc mit Frame Pointern, um Profiling-Funktionen sicherzustellen
- Der erste Versuch, die standardmäßige Aktivierung in Fedora upstream einzubringen, führte zu einer langen Debatte
- Die Fedora-Diskussion wurde zu einem Thread mit 116 Beiträgen
- Ein Teilnehmer verlangte, dass Meta oder Netflix eine Side-Repository-Infrastruktur für Tests, Benchmarks und Code-Size-Messungen bereitstellen sollten
- Jonathan Corbet fasste dies in Fedora's tempest in a stack frame zusammen
- Fedora prüfte den Vorschlag später erneut und nahm ihn an; damit wurde es die erste Distribution, die Frame Pointer wieder aktivierte
- Auch Ubuntu kündigte Frame pointers by default in Ubuntu 24.04 LTS an
- Zusätzlich gab es die Nachricht, dass Arch Linux ebenfalls an der Aktivierung von Frame Pointern arbeitet
- Das Stack Walking der OS-Bibliotheken verbessert sich durch diese Änderung, aber Applikationsruntimes können separate Einstellungen benötigen
- Java bietet die Option
-XX:+PreserveFramePointer - In Go ist Frame-Pointer-Unterstützung seit einigen Jahren standardmäßig aktiviert
- Java bietet die Option
Kandidaten für Stack Walking nach den Frame Pointern
- LBR (Last Branch Record): eine Intel-Hardwarefunktion mit einer Begrenzung auf 16 oder 32 Frames; für die meisten Applikations-Stacks reicht das nicht, kann aber als letzter Ausweg dienen, um einige Stack-Informationen zu erhalten
- BTS (Branch Trace Store): eine Intel-Funktion mit geringerer Begrenzung der Stack-Tiefe, aber Kosten für Memory Load/Store und die Behandlung von BTS-Buffer-Overflow-Interrupts
- AET (Architectural Event Trace): ein JTAG-basierter Tracer, der Low-Level-CPU-, BIOS- und Device-Events verfolgen kann und offenbar auch für Stack Traces nutzbar ist, aber ich habe keine eigene Nutzungserfahrung damit
- DWARF: ein Binary-Debuginfo-Verfahren, das in Debuggern seit langem verwendet wird
- Es wurde darauf hingewiesen, dass es JIT-to-DWARF-Arbeiten für JIT-Runtimes gab
- Für eine Java-JVM, bei der c2 auf einem stark ausgelasteten Produktionsserver kontinuierlich läuft, wird es nicht als praktikabel erwartet
- Auch die Kosten des DWARF-Stack-Walkings selbst sind hoch
- eBPF stack walking: Ein externer Tracer kann ohne Runtime-Unterstützung durch das Innere einer Runtime wie der JVM laufen
- Möglicherweise müssen viele Daten aus dem Inneren der Runtime per User-Space-Read gelesen werden, was hohen Overhead verursachen kann
- Da es anfällig für Änderungen an der Runtime ist, sollte es am besten zusammen mit der Language Code Base ausgeliefert und gepflegt werden
- ORC (oops rewind capability): ein leichtgewichtiger Stack Unwinder des Linux-Kernels, der es neuen Kerneln ermöglicht, Frame Pointer zu entfernen und dennoch Stack Walking beizubehalten
- SFrames (Stack Frames): ein auf ORC basierendes leichtgewichtiges Verfahren für User-Stack-Unwinding
- Shadow Stacks: eine Sicherheitsfunktion von Intel und AMD, bei der Return Addresses von Funktionen auf einen separaten Hardware-Stack gepusht und beim Return geprüft werden; sie scheint auch für Stack Traces nutzbar zu sein
Was sich jetzt unmittelbar ändert
- Der i386-Performance-Vorteil, die damalige Einschätzung zur Debugger-Kompatibilität und die Konkurrenz zu
icc, die 2004 Gründe für das Weglassen von Frame Pointern waren, passen nicht mehr zur Situation im Jahr 2024 - Schon damals wurde bewertet, dass das Entfernen von Frame Pointern auf x86-64 gegenüber dem Verlust an Debuggability keinen ausreichenden Vorteil brachte
- Wenn Fedora und Ubuntu Frame Pointer zurückbringen, werden CPU-Flame-Graphs für Nutzer der 2024er-Releases verständlicher, und Off-CPU-Flame-Graphs lassen sich erstmals richtig nutzen
- Auch Continuous Profiler können leichter vollständigere Profile erhalten, ohne von Kunden eine OS-Änderung zu verlangen
- Vielleicht werden in Zukunft Verfahren wie SFrames oder Shadow Stacks Stack Traces wieder ohne Frame Pointer ermöglichen, aber die heute verfügbare Verbesserung besteht darin, Frame Pointer standardmäßig einzuschalten
1 Kommentare
Hacker-News-Kommentare
Ich erinnere mich noch daran, wie sich Anfang der 2000er das Weglassen des Stack-Frame-Pointers zu verbreiten begann
Damals studierte ich Informatik an einer Universität in einem armen Land der Dritten Welt und nutzte wegen der alten, langsamen Computer für die meisten Aufgaben Compiler statt Interpreter
Es gab viele spannende Kurse, in denen wir Low-Level-Datenstrukturen, Compiler, numerische Routinen in Assembler und sogar Minix-Gerätetreiber implementierten, und wenn sich ein Programm seltsam verhielt, hängte ich mich mit gdb dran und debugte direkt auf Assembler-Ebene, indem ich dem Stack folgte
Doch plötzlich wurde
-fomit-frame-pointerpopulär, und Stack-Traces ergaben keinen Sinn mehr, wodurch das Debuggen von Segfaults oder illegal instructions viel schwieriger wurdeAm Ende nutzte ich fast für alles Python, um kaputte Debugging-Sessions zu vermeiden; ich verlor zwar Leistung im ein- bis niedrigen zweistelligen Bereich, aber Python gelernt zu haben, war später nützlich
-fno-omit-frame-pointerdamals nicht bekannt warSchön, dass Fedora erwähnt wird. Den Frame Pointer distributionsweit aktiviert zu halten, war ein ziemlich zermürbender Kampf
Beispiel: https://pagure.io/fesco/issue/3084
Es hält sich hartnäckig der Mythos, der Frame-Pointer-Overhead sei groß; das geht auf einen Python-Fall zurück, der einmal +10 % langsamer war, inzwischen aber behoben wurde
Der tatsächlich gemessene Overhead liegt unter 1 %, und der Nutzen ist bei bestimmten Anwendungen deutlich größer
Das passt auch nicht gut zu Messungen aus dem Linux-Kernel-Umfeld; dort habe ich Werte im Bereich von 5–10 % gesehen: https://lore.kernel.org/lkml/20170602104048.jkkzssljsompjdwy...
Bei verschiedenen Workloads wie netperf, Microbenchmarks für den Page Allocator, pgbench und sqlite ergab das Aktivieren des Frame Pointers 5–10 % Overhead, und wichtig ist, dass PostgreSQL und SQLite davon betroffen waren
Ein DBMS ist eine der guten Methoden, ein System stark unter Druck zu setzen
OCaml 5 verwendet getrennte Stacks für OCaml-Code und C-Code; GDB kann beide über DWARF-Informationen verbinden, aber der DWARF-Call-Graph von perf kann das nicht: https://github.com/ocaml/ocaml/issues/12563#issuecomment-193...
Falls man auch in künftigen Releases eine Begründung dafür braucht, die Frame Pointer beizubehalten, könnte OCaml 5 als Beispiel dienen
Ich habe gerade erst erfahren, dass Fedora 39 Frame Pointer bereits standardmäßig aktiviert hat; mein normales Profiling läuft immer noch meist auf einem CentOS-7-ähnlichen System mit
perf record --call-graph dwarf -F 47 -aDass man Frame Pointer entfernt hat, war kein Mythos, sondern entsprang der Realität der Zeit vor 64 Bit, und das ist noch gar nicht so lange her
Auch heute sind solche Optimierungen sinnvoll, wenn man älteren 64-Bit-Systemen neues Leben einhauchen will
Im Idealfall sollte das auf sicherheitskritischen Systemen ebenfalls der Standard sein, und nicht alles muss für „Observability“ optimiert werden
Eine Sache hat Apple bei ARM gut gemacht: Der x29-Frame-Pointer zeigt immer auf einen gültigen Frame-Record
Manche Funktionen wie leaf functions oder tail calls erzeugen möglicherweise keinen Eintrag in der Liste, aber das Ergebnis ist, dass Stack-Traces auch ohne Debug-Informationen immer sinnvoll bleiben
https://developer.apple.com/documentation/xcode/writing-arm6...
2005 war ich bei Google auf der Gegenseite, und die damalige Überlegung war simpel
Selbst wenn
$BIG_COMPANYbeschließt, alles mit Frame Pointern zu kompilieren, wird der Rest der Community das nicht tun, also führt man weiterhin eine nicht zu gewinnende Debatte mit einer viel größeren CommunityAm Ende wurde daraus fast eine 20-jährige Debatte, und später schrieb ich Patches, damit libunwind in gperftools funktioniert, und wartete dann einige Jahre lang libunwind
Inzwischen bin ich in einen anderen Bereich des Computings gewechselt und nur noch passiver Beobachter, aber es ist interessant, die Geschichte aus der Perspektive der Gegenseite zu lesen
Wenn man
RBPschon als Frame Pointer übergibt, könnte man gleich zwei Stacks verwendenEiner, auf den
RBPzeigt und der die aktiven Frames speichert, und ein anderer, auf denRSPzeigt und der nur Rücksprungadressen speichertDann wäre der Call Stack buchstäblich nur ein flaches Array von Rücksprungadressen, sodass man den „Stack Walk“ gar nicht mehr bräuchte
Ich verstehe von vornherein nicht, warum Rücksprungadressen in der Nähe lokaler Variablen gespeichert werden, und es scheint sehr viele Nachteile zu haben
Der vorgeschlagene Ansatz braucht zwei Guard Pages, verdoppelt die Stack-Manipulationen und verdoppelt auch die Wahrscheinlichkeit von Cache Misses
Ich kenne den Grund, aber wie bei so vielem ergab das zuletzt vor etwa 30 Jahren Sinn, und die Auswirkungen davon waren interessant
Merkwürdigerweise scheint der Wikipedia-Artikel nicht stark genug herauszustellen, dass Forth sowohl auf den Parameter-Stack als auch auf den Return-Stack zugreifen kann, obwohl das ein wichtiges Merkmal des Modells ist
https://en.wikipedia.org/wiki/Forth_(programming_language)
Man müsste zeigen, dass die Kosten für das Verschieben auf eine separate Seite und das Verwalten von zwei Zeigern in der Praxis tatsächlich geringer sind als Stack Cookies/Canaries, die Schutz genau dort bieten, wo er ohnehin gebraucht wird
Gegenüber heutigen Stack Canaries gibt es auch keinen praktischen Sicherheitsvorteil. Wenn beliebiges Lesen/Schreiben möglich ist, kann das am Ende ohnehin zu einer Umgehung der Control-Flow-Integrity führen
Virgil verwendet keine Frame Pointer. Wenn es keine dynamische Stack-Allokation gibt, ist die Frame-Größe einer bestimmten Funktion fest und kann mit einer einfachen binären Suche in einer Tabelle gefunden werden
Virgils Ansatz verwendet zusätzlich seitenindexbasierte Bereiche, um die Suche im Durchschnitt mit nur wenigen Vergleichen einzugrenzen, kombiniert Unwind-Informationen mit Stackmaps für den GC und benötigt nur sehr wenig Platz
Der zentrale Code steht in https://github.com/titzer/virgil/blob/master/rt/native/Nativ..., und der Rest des Codes im selben Verzeichnis implementiert die Metadaten-Dekodierung
Ich halte Frame Pointer nur dann für sinnvoll, wenn die Frame-Größe dynamisch ist, also wenn Daten auf dem Stack allokiert werden
Ein dynamisches Verfahren zu verwenden, obwohl ein statisches ausreicht, ist seltsam; vermutlich ist es vor allem so gekommen, weil es keine Einigung auf eine ABI für Metadaten-Kodierung oder auf Unwind-Routinen gab
Die Messwerte von 1–2 % sind glaubwürdig und liegen in einer ähnlichen Größenordnung wie die Kosten von Array-Bounds-Checks
Es ist eine sehr seltsame Prioritätensetzung, 1 % Overhead für Debugging und Profiling als Sonderfall zu behandeln, aber sich gegen eine zusätzliche Sicherheitsschicht zu sträuben
In C++ sind bei
std::vectorBounds-Checks standardmäßig deaktiviert, was wohl daran liegt, dass C++ von völlig Verrückten entworfen wurde und für solche Leute gedacht istAbgesehen davon fällt mir spontan keine Sprache ohne Bounds-Checks ein
Guter Artikel. Ich habe es bedauert, als die Frame Pointer verschwanden
Nicht nur auf anderen Systemen, sondern auch unter Linux haben viele Menschen wegen des Fehlens von Frame Pointern lange gelitten und versucht, sie in möglichst vielen Umgebungen beizubehalten
Es fühlt sich einerseits wie späte Anerkennung an, zu sehen, dass Mainstream-Linux sie wieder zurückbringt, andererseits ist es auch ein wenig frustrierend
Unter Debian-artigen Systemen ist es langsam, weil aus Lizenzgründen für
perfnur der langsame Unwind-Pfad paketiert wird, aber mit brauchbaren Tools merke ich den Unterschied fast gar nichtIch frage mich, was ich dabei übersehe
Ich bin insgesamt für Frame Pointer, aber nach einigen Jahren Arbeit in diesem Bereich ist mir Folgendes aufgefallen.
Viele Stack-Unwinder auf Basis von Frame Pointern berücksichtigen Probleme nicht, die in DWARF-Unwind-Informationen nicht auftreten. Das Aufsetzen eines Frames ist nicht atomar, sondern besteht aus den zwei Instruktionen
push $rbpundmov $rsp $rbp; wird ein Snapshot währendpushaufgenommen, kann der Parent-Frame verloren gehen.Vielleicht lässt sich das durch Code-Inspektion abmildern, aber es kann auch
push %rbpgeben, das nichts mit dem Stack-Frame zu tun hat, daher wirkt das eher wie eine Heuristik.Ich habe auch die von Brendan erwähnte BPF-basierte Lösung für schnelles In-Kernel-Unwinding entwickelt: https://web.archive.org/web/20231222054207/https://www.polar...
Dabei wird DWARF CFI nicht direkt verwendet, sondern in ein Format mit wahlfreiem Zugriff umgewandelt, das in BPF nutzbar ist.
Derzeit werden nur JIT-Abschnitte mit Frame Pointern unterstützt, aber ich halte es für möglich, das Unwinding des JVM-Interpreters mit nativem Unwinding zu verzahnen.
Idealerweise sollte das Aktivieren von Frame Pointern fallweise erfolgen, und Benchmarking ist entscheidend.
Je nach Branche und Art der Software können die Zielkonflikte zwischen Performance, Observability und Geschäftsmetriken sehr unterschiedlich ausfallen.
Fedora hat hier sehr gute und rigorose Arbeit geleistet.
Außerdem ist ein Build-System, das diese Einstellung systemweit einschließlich abhängiger Bibliotheken ändern kann, nicht nur für Tests, sondern auch für den Einsatz in Produktion sehr nützlich.
Abschließend bin ich gespannt auf SFrame, an dem Indu arbeitet. Es scheint viele der aktuellen Probleme zu lösen und den Nutzern gleichzeitig die Wahl zu lassen, ob sie Frame Pointer verwenden wollen, aber bis die Infrastruktur steht und alle upgegradet haben, könnten noch einige Jahre vergehen.
Um zu verstehen, was tatsächlich passiert, braucht man eine systemweite Analyse, und bei der heutigen Struktur binärer Linux-Distributionen wie Fedora oder Debian gibt es praktisch keine andere Alternative.
ENTER N,0reserviert N Stack-Speicher für lokale Variablen und ist grob äquivalent zuPUSH EBP,MOV ESP,ESP,SUB SP,N.Ich weiß allerdings nicht mehr, ob es
ENTERauf x86-64 gibt.Trotzdem ist es zwischen
CALLund dem Aufsetzen des Frames nicht atomar; wenn der Snapshot nachCALL, aber vorENTERaufgenommen wird, bekommt man den Frame-Setup nicht mit.Der Grund, warum
ENTERkaum verwendet wird, ist wohl, dass es als zu langsam gilt.LEAVEwird benutzt, weil es genauso schnell oder schneller ist als die ersetzte Instruktionsfolge, aber beiENTERruiniert der zweite Operand die Performance.Dieser Operand dient dazu, dass verschachtelte Funktionen auf übergeordnete Stack-Frames zugreifen können, und die Kosten dafür sind sehr hoch.
Ich finde es interessant, dass ich jetzt endlich verstehe, warum in Profilen diese Berge von
[unknown]auftauchen.Es ist aber schwer zu rechtfertigen. 2 % Performance-Unterschied sind tatsächlich ziemlich viel.
Es wäre gut, wenn man die Einbindung von Frame Pointern feiner steuern könnte.
Mit granularem Profiling ließe sich vermutlich feststellen, ob bestimmte Funktionen oder Compilation Units Frame Pointer benötigen.
Es würde mich nicht überraschen, wenn sich zeigt, dass nur wenige Workloads durch Frame Pointer dramatisch langsamer werden und der Rest kaum beeinflusst wird.
__attribute__((optimize("no-omit-frame-pointer")))__attribute__((optimize("omit-frame-pointer")))Solche Benchmarks sind etwas künstlich und sollten nicht vollständig geglaubt werden; reale Anwendungen weichen in den Ergebnissen oft stark ab.
Profiling ist wichtig, und ich habe durch sorgfältiges Profiling verschiedene Abschnitte des Codes schon um bis zu 20 % beschleunigt.
Wenn eine Anwendung extrem empfindlich auf Performance-Verluste reagiert, kann man im Labor Frame Pointer aktivieren und profilieren und sie in der an Kunden ausgelieferten Version weglassen.
Die Unterstützung für JIT-Code ist leider nicht gut, aber LLVM hat einen hervorragenden Hook, der jede erzeugte Methode und ihre Adresse protokolliert.
Damit lässt sich ein einfaches Mixed-Mode-Stack-Unwinding relativ leicht bauen, allerdings meist nur innerhalb des Prozesses.
Intels DNN-Reihe scheint die Informationen in eine gemeinsame Datei zu dumpen, die perf lesen kann, aber die Kernel von oneDNN selbst verwenden RBP fortlaufend neu, wodurch das praktisch nutzlos wird.
Auch die Aussage im Artikel, dass es für JIT-Runtimes wie die Java JVM keine DWARF-Informationen gibt, überrascht mich.
Ich frage mich, ob das standardmäßig deaktiviert ist oder ob es wirklich wörtlich nicht nutzbar ist.
Bei der Suche stößt man meist auf Diskussionen darüber, wie man JNI/C in den JVM-Stack einbeziehen kann: https://github.com/async-profiler/async-profiler/issues/215