gibt Inline-Funktionen nach Wert zurück

7

Ich implementiere einige mathematische Typen und möchte die Operatoren optimieren, um die Menge an Speicher zu minimieren, die erstellt, zerstört und kopiert wird. Um zu demonstrieren, zeige ich Ihnen einen Teil meiner Quaternion-Implementierung.

%Vor%

Ich möchte wissen, wie sich die beiden folgenden Implementierungen voneinander unterscheiden. Ich habe eine + = Implementierung, die direkt vor Ort arbeitet, wo kein Speicher erstellt wird, aber bei Operationen auf höherer Ebene, die Quaternionen verwenden, ist es sinnvoll, + und nicht + = zu verwenden.

%Vor%

und

%Vor%

Mein c ++ ist vollkommen autodidaktisch. Wenn es zu einigen Optimierungen kommt, bin ich nicht sicher, was ich tun soll, weil ich nicht genau weiß, wie der Compiler mit diesen Dingen umgeht. Wie werden diese Mechanismen auch in Nicht-Inline-Implementierungen übersetzt?

Jede andere Kritik an meinem Code ist zu begrüßen.

    
Mark 21.08.2009, 19:08
quelle

4 Antworten

10

Ihr erstes Beispiel ermöglicht es dem Compiler, etwas "Return Value Optimization" (RVO) genannt zu verwenden.

Im zweiten Beispiel kann der Compiler möglicherweise etwas namens "Named Return Value Optimization" (NRVO) verwenden. Diese 2 Optimierungen sind eindeutig eng miteinander verbunden.

Einige Details von Microsofts Implementierung von NRVO finden Sie hier:

Beachten Sie, dass der Artikel angibt, dass die NRVO-Unterstützung mit VS 2005 (MSVC 8.0) gestartet wurde. Es wird nicht speziell gesagt, ob das gleiche für RVO gilt oder nicht, aber ich glaube, dass MSVC RVO-Optimierungen vor Version 8.0 verwendet hat.

Dieser Artikel über Move Constructors von Andrei Alexandrescu enthält gute Informationen darüber, wie RVO funktioniert (und wann und warum Compiler nicht funktionieren) benutze es).

Einschließlich dieses Bits:

  

Sie werden enttäuscht sein zu hören, dass jeder Compiler und oft jede Compiler-Version eigene Regeln zum Erkennen und Anwenden von RVO hat. Einige wenden RVO nur auf Funktionen an, die unbenannte Provisorien zurückgeben (die einfachste Form von RVO). Die anspruchsvolleren Anwendungen wenden RVO auch an, wenn ein benanntes Ergebnis von der Funktion zurückgegeben wird (das sogenannte Named RVO oder NRVO).

     

Im Wesentlichen können Sie beim Schreiben von Code darauf zählen, dass RVO portabel auf Ihren Code angewendet wird, abhängig davon, wie Sie den Code genau schreiben (unter einer sehr fließenden Definition von "genau"), die Phase des Mondes und die Größe deiner Schuhe.

Der Artikel wurde 2003 geschrieben und die Compiler sollten jetzt sehr verbessert werden; hoffentlich ist die Phase des Mondes weniger wichtig, wenn der Compiler RVO / NRVO verwendet (vielleicht ist es am Tag der Woche). Wie oben erwähnt, scheint MS NRVO bis 2005 nicht implementiert zu haben. Vielleicht hat jemand, der an dem Compiler bei Microsoft arbeitet, ein neues Paar komfortablerer Schuhe bekommen, die eine halbe Nummer größer sind als zuvor.

Ihre Beispiele sind so einfach, dass ich erwarten würde, dass beide gleichwertigen Code mit neueren Compiler-Versionen erzeugen.

    
Michael Burr 21.08.2009, 20:18
quelle
6

Zwischen den beiden Implementierungen, die Sie vorgestellt haben, gibt es wirklich keinen Unterschied. Jeder Compiler, der irgendwelche Optimierungen durchführt, wird Ihre lokalen Variablen optimieren.

Was den + = Operator betrifft, ist wahrscheinlich eine etwas kompliziertere Diskussion darüber, ob Ihre Quaternions unveränderliche Objekte sein sollen oder nicht, erforderlich ... Ich würde immer dazu führen, Objekte wie diese als unveränderliche Objekte zu erzeugen. (Aber andererseits bin ich auch eher ein gemanagter Coder)

    
LorenVS 21.08.2009 19:16
quelle
2

Wenn diese beiden Implementierungen beim Aktivieren der Optimierung nicht denselben Assemblercode generieren, sollten Sie einen anderen Compiler verwenden. :) Und ich glaube nicht, dass es wichtig ist, ob die Funktion inline ist oder nicht.

Übrigens, beachte, dass __forceinline sehr unportabel ist. Ich würde einfach den einfachen alten Standard inline verwenden und den Compiler entscheiden lassen.

    
Dima 21.08.2009 19:24
quelle
2

Der derzeitige Konsens besteht darin, dass Sie zuerst alle Ihre? = Operatoren implementieren, die keine neuen Objekte erstellen. Abhängig davon, ob die Ausnahmesicherheit ein Problem ist (in Ihrem Fall ist es wahrscheinlich nicht) oder ein Ziel kann die Definition von? = Operator unterschiedlich sein. Danach implementieren Sie den Operator? als eine freie Funktion in Bezug auf den? = Operator mit Vorbeilauf-Wert Semantik.

%Vor%

Dies hat die folgenden Vorteile:

  • Nur eine Implementierung der Logik. Wenn sich die Klasse ändert, müssen Sie nur operator = und operator re-implementieren? passt sich automatisch an.
  • Der Operator für freie Funktionen ist symmetrisch in Bezug auf implizite Compilerkonvertierungen
  • Es ist die effizienteste Implementierung von Operator? Sie können in Bezug auf Kopien
  • finden

Effizienz des Bedieners?

Wenn Sie den Operator anrufen? Bei zwei Elementen muss ein drittes Objekt erstellt und zurückgegeben werden. Mit dem obigen Ansatz wird die Kopie im Methodenaufruf durchgeführt. So wie es ist, kann der Compiler die Kopie freigeben, wenn Sie ein temporäres Objekt übergeben. Beachten Sie, dass dies so gelesen werden sollte, dass der Compiler weiß , dass er die Kopie "herausnehmen kann", nicht, weil "der Compiler die Kopie" kopieren wird. Die Laufleistung hängt von verschiedenen Compilern ab, und sogar derselbe Compiler kann in verschiedenen Kompilierungsläufen unterschiedliche Ergebnisse liefern (aufgrund verschiedener Parameter oder Ressourcen, die dem Optimierer zur Verfügung stehen).

Im folgenden Code wird ein temporäres Objekt mit der Summe von a und b erstellt. Dieses temporäre Objekt muss erneut an operator+ zusammen mit c übergeben werden, um ein zweites temporäres Objekt mit dem endgültigen Ergebnis zu erstellen :

%Vor%

Wenn operator+ nach Wert-Semantik übergeben wurde, kann der Compiler die Kopie als pass-by-value ausgeben (der Compiler weiß, dass das temporäre Objekt direkt nach dem zweiten operator+ -Aufruf zerstört wird und kein a erstellen muss andere Kopie zu übergeben)

Auch wenn operator? im Code als Ein-Zeilen-Funktion ( Q operator+( Q lhs, Q const & rhs ) { return lhs+=rhs; } ) implementiert werden könnte, sollte dies nicht der Fall sein. Der Grund dafür ist, dass der Compiler nicht wissen kann, ob die von operator?= zurückgegebene Referenz tatsächlich eine Referenz auf dasselbe Objekt ist oder nicht. Indem die Anweisung return explizit das Objekt lhs annimmt, weiß der Compiler, dass die zurückgegebene Kopie entfernt werden kann.

Symmetrie in Bezug auf Typen

Wenn es eine implizite Konvertierung vom Typ T in den Typ Q gibt und Sie jeweils zwei Instanzen t und q haben, dann erwarten Sie (t+q) und (q+t) beide abrufbar. Wenn Sie operator+ als Memberfunktion in Q implementieren, kann der Compiler das t -Objekt nicht in ein temporäres Q -Objekt konvertieren und später (Q(t)+q) aufrufen, da es keine Typkonvertierungen im linke Seite, um eine Mitgliedsfunktion aufzurufen. Bei einer Memberfunktion wird die Implementierung t+q nicht kompiliert.

Beachten Sie, dass dies auch für Operatoren gilt, die nicht arithmetisch symmetrisch sind, wir sprechen über Typen. Wenn Sie eine T von einer Q subtrahieren können, indem Sie die T auf eine Q hochstufen, dann gibt es keinen Grund, eine Q von einer T nicht mit einer anderen automatischen Aktion zu subtrahieren.

    
quelle

Tags und Links