inline - wann, wie und warum?
inline - wann, wie und warum?
Hallo.
Wann sollte man inline nutzen? Wann sollte man __forceinline (MVSC) nutzen?
Zu meinem Verständnis:
Eine inline-Funktion wird direkt dort, wo sie aufgerufen wird, hinkopiert/hincompiliert, als hätte man die dort geschrieben/programmiert. Das spart dann mindestens einen Funktionsaufruf/Instruktion und meistens müssen dadurch weniger Variablen auf den Stack gepackt und am Ende wieder runter genommen werden. Vermutlich können Compiler durch inline-Funktionen auch noch andere Optimierungen vornehmen.
Ich habe zum Testen einige wichtige und oft aufgerufene Funktionen mit __forceinline versehen. Das Ergebnis war eine leicht größere Datei, aber die Performance war quasi identisch (z.B. gleiche FPS).
Ich habe bereits einige Änderungen bei dem Projekten bezüglich der Optimierung vorgenommen (siehe Dateianhang), was das manuelle/extra deklarieren einer Funktion als inline/__forceinline anscheinend komplett überflüssig macht.
Gibt es überhaupt noch Situationen, wo man inline/__forceinline nutzen sollte, wenn man bereits die entsprechenden Optimierungs-Einstellungen vorgenommen hat?
Und wenn ich gerade eh zur Optimierung etwas frage, wozu benötigt man Framezeiger? Ich habe diese, wie man sehen kann, deaktiviert, was ca. 1% mehr Performance rausgeholt hat. Bei der Release-Version mit den deaktivierten Framezeigern kann ich aber weiterhin StackWalk64 korrekt einsetzen und z.B. in Kombination mit der Symboldatei (.pdb) den aktuellen Stack in einer Fehlermeldung anzeigen.
Wann sollte man inline nutzen? Wann sollte man __forceinline (MVSC) nutzen?
Zu meinem Verständnis:
Eine inline-Funktion wird direkt dort, wo sie aufgerufen wird, hinkopiert/hincompiliert, als hätte man die dort geschrieben/programmiert. Das spart dann mindestens einen Funktionsaufruf/Instruktion und meistens müssen dadurch weniger Variablen auf den Stack gepackt und am Ende wieder runter genommen werden. Vermutlich können Compiler durch inline-Funktionen auch noch andere Optimierungen vornehmen.
Ich habe zum Testen einige wichtige und oft aufgerufene Funktionen mit __forceinline versehen. Das Ergebnis war eine leicht größere Datei, aber die Performance war quasi identisch (z.B. gleiche FPS).
Ich habe bereits einige Änderungen bei dem Projekten bezüglich der Optimierung vorgenommen (siehe Dateianhang), was das manuelle/extra deklarieren einer Funktion als inline/__forceinline anscheinend komplett überflüssig macht.
Gibt es überhaupt noch Situationen, wo man inline/__forceinline nutzen sollte, wenn man bereits die entsprechenden Optimierungs-Einstellungen vorgenommen hat?
Und wenn ich gerade eh zur Optimierung etwas frage, wozu benötigt man Framezeiger? Ich habe diese, wie man sehen kann, deaktiviert, was ca. 1% mehr Performance rausgeholt hat. Bei der Release-Version mit den deaktivierten Framezeigern kann ich aber weiterhin StackWalk64 korrekt einsetzen und z.B. in Kombination mit der Symboldatei (.pdb) den aktuellen Stack in einer Fehlermeldung anzeigen.
- Dateianhänge
-
- VCppOptimize.gif (6.76 KiB) 2434 mal betrachtet
- Schrompf
- Moderator
- Beiträge: 5077
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas
- Wohnort: Dresden
- Kontaktdaten:
Re: inline - wann, wie und warum?
Wir reden von C++, vermute ich. Da besagt der Standard erstmal, dass es Pflicht ist, wenn Funktionen mehrfach definiert werden, also z.b. in einem Header implementiert sind und der Header mehrfach angezogen wird. Daneben gibt es aber noch diverse weitere inline-Kriterien, wie z.B. Implementation in einer Klasse und template-Funktionen und -Methoden.
Die Wirkung ist, dass der Compiler anstatt eines Aufrufs direkt den Code der Funktion einfügt. Das erspart nicht nur den Stackframe-Auf- und Abbau, Parameterübergabe, potentiell kühle Cache-Regionen, sondern ermöglicht auch dem Compiler Optimierung und Aliasing-Analyse über Funktionsgrenzen hinweg. Es gibt seit bald Jahrzehnten aber die sogenannte Link Time Code Generation, bei der der Linker erst die Optimierung macht. Der Linker hat zwangsweise den Code aller Funktionen und kann damit alle Vorteile des Inlinens auch ohne inline umsetzen. Und spätestens das war dann auch der Grund, warum inline heutzutage nur noch ein grober Hinweis für den Compiler ist.
Nachteile ergeben sich aber auch, primär aus der gestiegenen Code-Größe. Das bedeutet, dass die Ausführung durch potentiell kühlere Cache-Regionen läuft und das relative Sprünge und Zugriffe über größere Entfernungen arbeiten müssen und damit wahrscheinlicher nicht mehr ins Instruction Set passen.
Ich arbeite allgemein so, dass ich dem Compiler erstmal vertraue, was inline angeht. Sprich: ich mach alles inline, was ich nach Sprachstandard muss, und benutze im voll optimierten Release-Build LTCG und Auto-Inlining wie in Deinem Bild oben. Erst wenn das nicht mehr reicht und der Profiler mich auf eine kritische Ecke hinweist, schau ich mir das dann genauer an.
Die Wirkung ist, dass der Compiler anstatt eines Aufrufs direkt den Code der Funktion einfügt. Das erspart nicht nur den Stackframe-Auf- und Abbau, Parameterübergabe, potentiell kühle Cache-Regionen, sondern ermöglicht auch dem Compiler Optimierung und Aliasing-Analyse über Funktionsgrenzen hinweg. Es gibt seit bald Jahrzehnten aber die sogenannte Link Time Code Generation, bei der der Linker erst die Optimierung macht. Der Linker hat zwangsweise den Code aller Funktionen und kann damit alle Vorteile des Inlinens auch ohne inline umsetzen. Und spätestens das war dann auch der Grund, warum inline heutzutage nur noch ein grober Hinweis für den Compiler ist.
Nachteile ergeben sich aber auch, primär aus der gestiegenen Code-Größe. Das bedeutet, dass die Ausführung durch potentiell kühlere Cache-Regionen läuft und das relative Sprünge und Zugriffe über größere Entfernungen arbeiten müssen und damit wahrscheinlicher nicht mehr ins Instruction Set passen.
Ich arbeite allgemein so, dass ich dem Compiler erstmal vertraue, was inline angeht. Sprich: ich mach alles inline, was ich nach Sprachstandard muss, und benutze im voll optimierten Release-Build LTCG und Auto-Inlining wie in Deinem Bild oben. Erst wenn das nicht mehr reicht und der Profiler mich auf eine kritische Ecke hinweist, schau ich mir das dann genauer an.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: inline - wann, wie und warum?
Schrompf hat erstmal recht: Bei globalen Optimierungen hat inline wirklich nur noch mit C++-Link-Verhalten zu tun, und nichts mit Kopieren von Code. inline bedeutet erstmal nur, dass jede Übersetzungseinheit (.cpp) eine eigene Kopie der Funktion bekommt, die nicht mit anderen Übersetzungseinheiten kollidieren wird (etwa wie static in C) – sonst würde jede Funktion, die in einem Header liegt, der von mindestens zwei Übersetzungseinheiten genutzt wird, einen multiply defined symbol-Fehler beim Linken verursachen.
Der Punkt, in dem ich ihn aber verbessern will, ist: Nutz PGO. Dann misst der Profiler, welche Funktionen wie oft aufgerufen werden, und optimiert nur die auf Geschwindigkeit. Alles andere optimiert er auf Größe. Ermöglicht außerdem einen riesen Satz weiterer Optimierungen, z.B. der Sprungvorhersage für oft frequentierte ifs.
Du kannst heute quasi komplett auf inline verzichten (außer für Templates, aber die sind implizit inline), alles in die CPPs schieben, die Vorteile kürzerer Kompilierzeit genießen, und kriegst durch LTCG/PGO am Ende den gleichen Code. __forceinline und declspec(noinline) sind aber tatsächlich Befehle an die Code Generation, mit der du halbwegs steuern kannst. (Visual C++ macht z.B. kein Single Call Site-Inlining, das kann man dann händisch erzwingen.)
————
Was deinen Framezeiger angeht: Wenn eine Exception geschmissen wird, muss das Programm bis zum nächsten catch abgerollt werden. Dafür braucht man den Call Stack, also welche Funktion welche aufgerufen hat. Funktionen haben ein halbes Dutzend verschiedener Calling Conventions und legen unterschiedlich viele lokale Variablen auf den Stack (manchmal sogar abhängig von Verzweigungen innerhalb der Funktion!). Um trotzdem noch durch den Stack den Weg zum Aufrufer zurückzufinden, wird eine verkettete Liste angelegt, bei der jeder Funktionaufruf ein Glied einfügt.
Die Frame Pointer Optimization verzichtet auf diesen Zeiger, falls es geht (Funktionen ohne Subaufrufe und ohne Exceptions). Außerdem wird nicht das Standardregister dafür genutzt, sondern der Compiler bringt die Verkettung an günstigster Position auf dem Stack unter. Da x86-32 nur acht Mehrzweckregister hat, und eins normalerweise den Frame Pointer hält, hast du auf einen Schlag 14 % mehr Register und das Programm wird entsprechend schneller. Dafür funktionieren Debugging Tools schlechter oder garnicht mehr (finden den Weg durch den Call Stack nicht mehr).
Unter x86-64 hat es keine Wirkung, weil Exception Handling dort anders gehandhabt wird. [Keine dynamische verkettete Liste, sondern eine globale Liste im pdata-Abschnitt, den der Compiler erzeugt. Prolog & Epilog der Funktionen sind standardisiert, so dass Calling Conventions nichts verkomplizieren.]
————
Deine Optionen sind redundant. /O2 hat /Ob2, /Oi, /Oy, und /Ot eingebaut, du kannst die undefiniert lassen.
Der Punkt, in dem ich ihn aber verbessern will, ist: Nutz PGO. Dann misst der Profiler, welche Funktionen wie oft aufgerufen werden, und optimiert nur die auf Geschwindigkeit. Alles andere optimiert er auf Größe. Ermöglicht außerdem einen riesen Satz weiterer Optimierungen, z.B. der Sprungvorhersage für oft frequentierte ifs.
Du kannst heute quasi komplett auf inline verzichten (außer für Templates, aber die sind implizit inline), alles in die CPPs schieben, die Vorteile kürzerer Kompilierzeit genießen, und kriegst durch LTCG/PGO am Ende den gleichen Code. __forceinline und declspec(noinline) sind aber tatsächlich Befehle an die Code Generation, mit der du halbwegs steuern kannst. (Visual C++ macht z.B. kein Single Call Site-Inlining, das kann man dann händisch erzwingen.)
————
Was deinen Framezeiger angeht: Wenn eine Exception geschmissen wird, muss das Programm bis zum nächsten catch abgerollt werden. Dafür braucht man den Call Stack, also welche Funktion welche aufgerufen hat. Funktionen haben ein halbes Dutzend verschiedener Calling Conventions und legen unterschiedlich viele lokale Variablen auf den Stack (manchmal sogar abhängig von Verzweigungen innerhalb der Funktion!). Um trotzdem noch durch den Stack den Weg zum Aufrufer zurückzufinden, wird eine verkettete Liste angelegt, bei der jeder Funktionaufruf ein Glied einfügt.
Die Frame Pointer Optimization verzichtet auf diesen Zeiger, falls es geht (Funktionen ohne Subaufrufe und ohne Exceptions). Außerdem wird nicht das Standardregister dafür genutzt, sondern der Compiler bringt die Verkettung an günstigster Position auf dem Stack unter. Da x86-32 nur acht Mehrzweckregister hat, und eins normalerweise den Frame Pointer hält, hast du auf einen Schlag 14 % mehr Register und das Programm wird entsprechend schneller. Dafür funktionieren Debugging Tools schlechter oder garnicht mehr (finden den Weg durch den Call Stack nicht mehr).
Unter x86-64 hat es keine Wirkung, weil Exception Handling dort anders gehandhabt wird. [Keine dynamische verkettete Liste, sondern eine globale Liste im pdata-Abschnitt, den der Compiler erzeugt. Prolog & Epilog der Funktionen sind standardisiert, so dass Calling Conventions nichts verkomplizieren.]
————
Deine Optionen sind redundant. /O2 hat /Ob2, /Oi, /Oy, und /Ot eingebaut, du kannst die undefiniert lassen.
Re: inline - wann, wie und warum?
Vielen Dank für die Antworten!
Jo, hab das C++ Icon gewählt, aber das reicht wohl nicht, ist auch ziemlich klein, werde "C++" zukünftig mit in den/die Titel packen.Schrompf hat geschrieben:Wir reden von C++, vermute ich.
Das klingt nach einer guten Vorgehensweise, werde ich so übernehmen.Schrompf hat geschrieben:Ich arbeite allgemein so, dass ich dem Compiler erstmal vertraue, was inline angeht. Sprich: ich mach alles inline, was ich nach Sprachstandard muss, und benutze im voll optimierten Release-Build LTCG und Auto-Inlining wie in Deinem Bild oben. Erst wenn das nicht mehr reicht und der Profiler mich auf eine kritische Ecke hinweist, schau ich mir das dann genauer an.
PGO klingt sehr interessant! Reicht dafür "Microsoft Visual Studio Community 2015 Update 3" oder benötige ich z.B. für das Profiling die Professional/Enterprise-Version?Krishty hat geschrieben:Der Punkt, in dem ich ihn aber verbessern will, ist: Nutz PGO. Dann misst der Profiler, welche Funktionen wie oft aufgerufen werden, und optimiert nur die auf Geschwindigkeit. Alles andere optimiert er auf Größe. Ermöglicht außerdem einen riesen Satz weiterer Optimierungen, z.B. der Sprungvorhersage für oft frequentierte ifs.
- Schrompf
- Moderator
- Beiträge: 5077
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas
- Wohnort: Dresden
- Kontaktdaten:
Re: inline - wann, wie und warum?
Das kann selbst die Community Edition schon, soweit ich weiß.
PGO habe ich seit Ewigkeiten auf dem Schirm, aber noch nie benutzt. Sollte ich mal, falls ich jemals wieder nennenswert zum Entwickeln komme.
PGO habe ich seit Ewigkeiten auf dem Schirm, aber noch nie benutzt. Sollte ich mal, falls ich jemals wieder nennenswert zum Entwickeln komme.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Re: inline - wann, wie und warum?
Hi
Ich finde das Schlüsselwort 'inline' missverständlich. Es gibt nämlich auf der einen Seite die Optimierungstechnik "inline". Das funktioniert auch genau so wie du es beschrieben hat.
Das hat aber mit dem 'inline' Schüsselwort von C++ inzwischen wenig zu tun. (Auch wenn es aus dieser Richtung kommt).
Die Inline Optimierungstechnick darf und kann der Compiler überall anwenden, egal ob inline davor steht oder nicht.
Das Schlüsselwort 'inline' erlaubt es dir die "One Definition Rule" zu umgehen.
Das interessant ist, nicht nur bei template funktionen sondern auch allen Funktionen die direkt in der Klassen definiert sind steht immer ein implizites 'inline' davor.
ist gleichwertig zu
(Steht glaub ich im Standard unter Punkt 9.3)
Bis auf globale Funktionen, gibt es aus meiner Sicht keinen Grund 'inline' zu verwenden. Es macht nur den Code weniger leserlich.
Grüße
Ich finde das Schlüsselwort 'inline' missverständlich. Es gibt nämlich auf der einen Seite die Optimierungstechnik "inline". Das funktioniert auch genau so wie du es beschrieben hat.
Das hat aber mit dem 'inline' Schüsselwort von C++ inzwischen wenig zu tun. (Auch wenn es aus dieser Richtung kommt).
Die Inline Optimierungstechnick darf und kann der Compiler überall anwenden, egal ob inline davor steht oder nicht.
Das Schlüsselwort 'inline' erlaubt es dir die "One Definition Rule" zu umgehen.
Das interessant ist, nicht nur bei template funktionen sondern auch allen Funktionen die direkt in der Klassen definiert sind steht immer ein implizites 'inline' davor.
Code: Alles auswählen
class A {
void run();
};
Code: Alles auswählen
class A {
inline void run();
};
Bis auf globale Funktionen, gibt es aus meiner Sicht keinen Grund 'inline' zu verwenden. Es macht nur den Code weniger leserlich.
Grüße
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: inline - wann, wie und warum?
Nicht ganz – §7.1.2.3: „A function defined within a class definition is a inline function [...].“
In deinem Beispiel ist die Funktion nur deklariert, nicht definiert. Sowas ist nicht implizit inline. Ich hab’s nicht getestet, aber der Compiler dürfte wegen unresolved symbols meckern, wenn du einfach inline davorschreibst. Du meinst:
ist identisch mit
In deinem Beispiel ist die Funktion nur deklariert, nicht definiert. Sowas ist nicht implizit inline. Ich hab’s nicht getestet, aber der Compiler dürfte wegen unresolved symbols meckern, wenn du einfach inline davorschreibst. Du meinst:
Code: Alles auswählen
class A {
void run() { }
};
Code: Alles auswählen
class A {
inline void run() { }
};
Re: inline - wann, wie und warum?
"Nicht ganz"? Ich liege total daneben. :oops:Krishty hat geschrieben:Nicht ganz – §7.1.2.3: „A function defined within a class definition is a inline function [...].“
...
Du hast recht. Mein Beispiel ist Fehlerhaft und mein Verweis falsch. Danke für die Korrektur. :)
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: inline - wann, wie und warum?
Naja, im Kern war die Aussage ja richtig – Funktionen in Klassen sind oft inline obwohl das Schlüsselwort nicht davorsteht. Nur die Eingrenzung mit der Definition fehlte.