Eigene Vektorenklasse (Fragen, Verbesserungen)
-
@unkwnusr
Wie @SeppJ ja schon erklärt hat heisstinline
nicht automatisch dass der Compiler den Funktionsaufruf inlinen ("rauskopieren") wird. Umgekehrt heisst es auch nicht der Compiler ohneinline
kein inlining machen wird. Es ist bloss formal nötig wenn man Funktionen in Header-Files definiert.Und die Funktion im Header-File zu definieren ist das was hier mMn. Sinn macht. Denn es ermöglicht dem Compiler die Funktion zu inlinen. (Genaugenommen können viele Compiler auch Funktionen inlinen die in .cpp Files definiert sind, aber dieses Feature ist per Default nicht aktiviert.)
Und bei so einfachen Funktionen wie diesen kann Inlining verdammt viel bringen.
Beispiel einer einfachen Funktion die einen Vektor um 90° dreht (2 Koordinaten vertauschen und eine davon invertieren): https://godbolt.org/z/Ehx58K8cG
Ohne Inlining:
rot90(Vector3D_ni): # @rot90(Vector3D_ni) push rbx sub rsp, 64 movlps qword ptr [rsp + 16], xmm0 movss dword ptr [rsp + 24], xmm1 lea rbx, [rsp + 16] mov rdi, rbx call Vector3D_ni::y() const movss dword ptr [rsp + 12], xmm0 # 4-byte Spill mov rdi, rbx call Vector3D_ni::x() const xorps xmm0, xmmword ptr [rip + .LCPI1_0] movaps xmmword ptr [rsp + 48], xmm0 # 16-byte Spill mov rdi, rbx call Vector3D_ni::z() const movaps xmm2, xmm0 lea rdi, [rsp + 32] movss xmm0, dword ptr [rsp + 12] # 4-byte Reload movaps xmm1, xmmword ptr [rsp + 48] # 16-byte Reload call Vector3D_ni::Vector3D_ni(float, float, float) [complete object constructor] movsd xmm0, qword ptr [rsp + 32] # xmm0 = mem[0],zero movss xmm1, dword ptr [rsp + 40] # xmm1 = mem[0],zero,zero,zero add rsp, 64 pop rbx ret
Mit Inlining:
rot90(Vector3D): # @rot90(Vector3D) movaps xmm2, xmmword ptr [rip + .LCPI0_0] # xmm2 = [-0.0E+0,-0.0E+0,-0.0E+0,-0.0E+0] xorps xmm2, xmm0 shufps xmm2, xmm0, 212 # xmm2 = xmm2[0,1],xmm0[1,3] shufps xmm2, xmm0, 82 # xmm2 = xmm2[2,0],xmm0[1,1] movaps xmm0, xmm2 ret
Selbst wenn du nicht x86 Assembler lesen kannst, solltest du sehen dass mit Inlining hier viel weniger gemacht werden muss als ohne Inlining. Und dabei enthält die Variante ohne Inlining noch nichtmal den Code der dann noch in den aufgerufenen Funktionen steht.
-
Danke euch für die Erklärung! Habe es besser verstanden als bei google oder sonst wo.
Ich kann x86 / x64 Assembler lesen und sehe ja zudem noch den gravierenden Unterschied.
-
@hustbaer @SeppJ
Also könnte ich doch quasi, wenn ich eine Klasse "Person" habe
die Methoden wie beispielsweise getName() auch inline machen oder nicht?
Diese Methode ist ja kurz und knackig.class Person{ public: Person() = default; Person(std::string& name) : m_Name(name){} inline std::string Name(){ return m_Name; } private: std::string m_Name; }
-
Mir ist gerade noch aufgefallen, wenn ich im header folgendes deklariere
inline const float getX() const;
und im source dann folgendes definiere
const float Vector3D::getX() const{ return m_Array[0]; }
und in der Main wie folgt benutzen möchte
Vector3D vec3 = {42, 32, 22}; std::cout << vec3.getX();
bekomme ich ein unresolved external symbol .... "referenced in function main"
Heißt das, dass ich inline functions direkt in der Header-File definieren sollte?
-
Wie schon gesagt, Funktionen in der Klassendefinition sind sowieso inline:
class Person{ public: Person() = default; Person(std::string& name) : m_Name(name){} std::string Name(){ return m_Name; } private: std::string m_Name; }
Name()
ist hier schon inline.inline
ohne Code macht keinen Sinn. Dasinline
ist eine Eigenschaft des Codeblocks, es ist kein Teil der Signatur einer Klasse. Wenn du eine Funktion nur deklarieren möchtest, dann macht es überhaupt keinen Unterschied, ob sie später inline definiert ist oder nicht.void foo(); // […] inline void foo() { // Code }
Wieso man das bei einer Inlinefunktion je machen sollte, ist eine gute Frage. Vielleicht wenn man am Anfang eines Headers eine Übersichtssektion über alle Funktionen haben möchte, ohne Implementierungen dazwischen?
-
Ja genau. Es ist ja schöner, wenn man dort nur die Deklarationen hat und keine Implementierungen.
Habs aber jetzt verstanden und schon umgeändert und meine Frage wurden super beantwortet.
Vielen Dank!
-
@unkwnusr sagte in Eigene Vektorenklasse (Fragen, Verbesserungen):
@hustbaer @SeppJ
Also könnte ich doch quasi, wenn ich eine Klasse "Person" habe
die Methoden wie beispielsweise getName() auch inline machen oder nicht?
Diese Methode ist ja kurz und knackig.Da kann man sich schnell täuschen. Der C++ Code ist zwar bloss
return m_Name;
, aber der Asselblercode der da draus wird ist gar nicht "knackig": https://godbolt.org/z/1eae88nxKAlso ja, kannst du. Bringt aber eher nix.
-
@SeppJ sagte in Eigene Vektorenklasse (Fragen, Verbesserungen):
Das ist korrekt, dass dies die Absicht hinter der Existenz des Schlüsselwortes ist. Praktisch entscheidet das der Compiler aber selber schon ganz gut, und trifft seine eigene Entscheidungen, ganz unabhängig von dem
inline
Schlüsselwort.Ich denke es macht Sinn, beim
inline
-Schlüsselwort höchstens noch aus historischen Gründen dessen ursprüngliche Motivation zu erwähnenDessen de facto einzige effektive Bedeutung ist heutzutage dein "anderer Nebeneffekt": "mehrere Definitionen sind erlaubt", was
inline
nicht zu einem Optimierungs- sondern zu einem Code- und Projektstruktur-Werkzeug macht. Gerade auch die etwas neuereninline
-Variablen haben eigentlich gar nichts mehr mit dem ursprünglichen inlining zu tun. Das ist meines erachtens alles, was man als Anfänger zu wissen braucht. Auch die Größe der Funktion ist beiinline
irrelevant. Die einzige Frage ist, ob der Aufbau des Projekts an dieser Stelleinline
erfordert, z.B. weil selbst die 200-Zeilen-Funktion aus welchem Grunde auch immer im Header definiert sein soll (Header-Only Library z.B.).Will man Compiler-Inlining steuern, sollte man compiler-spezifische Werkzeuge wie
__attribute__((always_inline))
oder__attribute__((noinline))
oder auf Build-Ebene LTO und spezielle Optimierungs-Flags verwenden. Bei Ersteren sollte man dann allerdings wirklich wissen was und warum man es tut. Hier wird die "Größe" der Inline-Funktion tatsächlich relevant.
-
@Finnegan Ich stimme dir generell diesbezüglich zu, aber ich habe auch mal in die CppCoreGuidelines geschaut und finde dort https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f5-if-a-function-is-very-small-and-time-critical-declare-it-inline:
Over the last 40 years or so, we have been promised compilers that can inline better than humans without hints from humans. We are still waiting. Specifying inline (...) encourages the compiler to do a better job.
-
@hustbaer sagte in Eigene Vektorenklasse (Fragen, Verbesserungen):
Da kann man sich schnell täuschen. Der C++ Code ist zwar bloss
return m_Name;
, aber der Asselblercode der da draus wird ist gar nicht "knackig": https://godbolt.org/z/1eae88nxKAlso ja, kannst du. Bringt aber eher nix.
Da wird ja auch ein String-Kopie erzeugt, also
std::string Name(){ return std::string(m_Name); }
Bei
const std::string& Name() const { return m_Name; }
sind es nur noch zwei Assembler-Mnemonics: geänderter Code
Oder ohne Optimierung kompilieren.
-
@wob sagte in Eigene Vektorenklasse (Fragen, Verbesserungen):
@Finnegan Ich stimme dir generell diesbezüglich zu, aber ich habe auch mal in die CppCoreGuidelines geschaut und finde dort https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f5-if-a-function-is-very-small-and-time-critical-declare-it-inline:
Over the last 40 years or so, we have been promised compilers that can inline better than humans without hints from humans. We are still waiting. Specifying inline (...) encourages the compiler to do a better job.
Ja, ich muss zugeben, dass ich tatsächlich den Code der Compiler nicht im Detail kenne und weiss, ob die
inline
tatsächlich auch als "Hint" interpretieren. Wenn mir inlining oder nicht wichtig ist, verwende ich die compiler-spezifischen Attribute.In dem Sinne halte ich es aber für nicht gut, wenn
inline
zwei Bedeutungen hat. Vielleicht will man ja eineinline
-Funktion, weil sie im Header ein soll, aber man möchte nicht, dass sie geinlined wird. Ich meine mich sogar vage erinnern zu können, schonmal irgendwo eineinline __attribute__((noinline)) f()
-Funktion geschrieben zu haben
-
@Th69
Aus welchen Gründen sollte ich denn eine Methode beispielsweise so implementierenconst std::string& getName() const{}
Hier geht es sich darum, dass eine String-Referenz zurückgegeben wird.
Liegt das daran, dass "std::string" benutzt wird? Oder gibt es da eine Regel so wie "pi*Daumen"
-
Wenn du mit Implementierung wirklich meinst, dass diese leer sein sollte (weil du
{}
am Ende schreibst): Nie.Wenn du meinst, warum man eine const-Referenz zurückgeben sollte: Es ist effizient. Faustregel: Alles was groß (viele Mal die Größe eines Pointers) oder dynamisch (wie hier der String) ist, ist teuer zu kopieren. Falls der Aufrufer unbedingt eine Kopie will, kann er sie immer noch selber machen.
-
@SeppJ
Nein, die Funktion ist einfach leer für das Beispiel jetzt.
Alles klar, das hilft mir weiter, danke.
-
Und umgekehrt solltest du bei deinen Getter-Funktionen auch direkt
float
zurückgeben.
Und den Rückgabetyp beiisZero
solltest du auch noch mal anpassen.
-
@SeppJ sagte in Eigene Vektorenklasse (Fragen, Verbesserungen):
Wenn du meinst, warum man eine const-Referenz zurückgeben sollte: Es ist effizient. Faustregel: Alles was groß (viele Mal die Größe eines Pointers) oder dynamisch (wie hier der String) ist, ist teuer zu kopieren. Falls der Aufrufer unbedingt eine Kopie will, kann er sie immer noch selber machen.
Teuer ist alles zu kopieren, was größer als eine Cache Line ist. Darunter wird bei korrekter Ausrichtung die Cache Line so oder so gelesen.
Das erinnert mich an folgenden Vortrag von der CppCon 2016.
-
@Th69
Was genau ist bei dem Rückgabetypen von isZero denn falsch?
-
@unkwnusr sagte in Eigene Vektorenklasse (Fragen, Verbesserungen):
@Th69
Was genau ist bei dem Rückgabetypen von isZero denn falsch?Du gibst eine Referenz auf float zurück, isZero sollte eigentlich bool zurückliefern.
-
@john-0
Stimmt. Habe ich nicht drüber nachgedacht.