A Guide to RVO in Visual C++ 2012

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Krishty
Establishment
Beiträge: 8250
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

A Guide to RVO in Visual C++ 2012

Beitrag von Krishty »

Weil Microsoft zwar suggeriert, oft Named Return Value Optimization einzusetzen, es dann aber doch nicht tut (getestet mit Visual C++ 2012 x64):


Langsam:
    struct A { // POD-Struktur mit Gleitkommaattributen bei einer Gesamtgröße > 64 Bits:
        double x;
        double y;
    };

    A makeA(); //
Inlining aktiviert, aber die Kopie bei der Rückgabe wird nicht wegoptimiert


Schnell:
    struct B { // Selbe Struktur, aber mit leerem Standardkonstruktor (also nicht mehr POD)
        double x;
        double y;
        B() { }
    };

    B makeB(); //
Inlining aktiviert; die Kopie bei der Rückgabe wird wegoptimiert (RVO)


haha fuck this shit
    struct C { // Selbe Struktur, aber mit leerem Destruktor
        double x;
        double y;
        C() { }
        ~C() { }
    };

    C makeC(); //
Kein Inlining; Kopie bei der Rückgabe wird nicht wegoptimiert (weil Visual C++ nun Ausnahmesicherheit garantieren zu versucht)



In meinen inneren Schleifen bewirkt A üblicherweise 20–30 % mehr Maschinenbefehle als B (Details im Microsoft-Connect-Ticket – greift zu, so lange es noch da ist!). C ist, wie hier weiter erläutert, nochmal um ein Vielfaches langsamer. Interessanterweise soll es mit Visual C++ 2005 noch funktioniert haben.

Der Geschwindigkeitsverlust von A tritt auch auf, wenn man drei floats statt zwei doubles hat. Andere Größen und Datentypen beabsichtige ich am Wochenende zu testen.

Also nur damit ihr wisst, was für Datentypen ihr in euren inneren Schleifen nicht einsetzen solltet, falls ihr mit Visual C++ arbeitet – nämlich die mit Destruktor oder ohne Konstruktor. Weil das ja klar ist.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4859
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: A Guide to RVO in Visual C++ 2012

Beitrag von Schrompf »

Ja, ist klar... und immer wieder einleuchtend dank anschaulicher Video-Beispiele. Danke!
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Ingrater
Establishment
Beiträge: 103
Registriert: 18.04.2007, 21:52

Re: A Guide to RVO in Visual C++ 2012

Beitrag von Ingrater »

Hast du das Problem auch in anderen versionen von visual studio feststellen können? Ich hatte nämlich letztens auch einen Fall wo ich mich gewundert hab warum zum geier visual studio so viele mov instruktionen generiert.
Benutzeravatar
Krishty
Establishment
Beiträge: 8250
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: A Guide to RVO in Visual C++ 2012

Beitrag von Krishty »

Ich bin noch nicht dazu gekommen. Es könnte aber genau so gut Aliasing sein, weil Visual C++ viele Zugriffe, die durch this gehen, als überlappend behandelt. Nur in den einfachsten Fällen (einer der Operanden ist eine lokale Variable und der Zuweisungsoperator ist nicht überladen) klappt das mehr oder weniger wie man es erwartet.

   this->matrix = paramMatrix;

wird also für eine 4×4-Matrix 32 movs bewirken (16 Mal abwechselnd Quellspeicher zu Register und Register zu Zielspeicher); sogar, wenn die Funktion geinlinet wurde und es relativ deutlich ist, dass der Parameter nicht überlappt. Es sei denn, paramMatrix wurde by value übergeben – aber dann wird sie eben auch kopiert.

Falls einer der Operanden der movs durch rsp adressiert wird, ist es meist vermasseltes RVO (weil’s auf dem Stapelspeicher landet); sonst meist vermasselte oder ohnmächtige Aliasing Analysis. So kannst du es halbwegs erkennen.

Ein weiterer Kandidat wäre Visual Studio 2010 mit x86-SSE-Kompilat. Dort werden float und double (aber nicht __m128 oder __m128d!) grundsätzlich völlig unnötig zwischen FPU und SSE-Register hin- und hergeschoben; erst recht mit Intrinsics. Als ich zum ersten Mal mit VS 2012 kompiliert habe, war meine Anwendung >10 % kleiner und schneller weil sie das für 2012 behoben haben. Das äußert sich aber vor allem in movaps und fld / fstp.

Mit den alten Versionen arbeite ich nur noch für XP-kompatible 32-Bit-Programme, weil der Maschinentext deutlich schlechter ist als in VS 2012.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Ingrater
Establishment
Beiträge: 103
Registriert: 18.04.2007, 21:52

Re: A Guide to RVO in Visual C++ 2012

Beitrag von Ingrater »

Super vielen dank für die Ausführliche Beschreibung, ich werde mal versuchen das die Tage zu reproduzieren um herauszufinden woran es lag. Es ging dabei aber auch um Vektoroperationden die mit SSE optimiert wurden. Mir ist dabei aufgefallen, dass der Compiler sehr viele unnötige movaps instruktionen generiert hat (Im Vergleich zu meinem handgeschriebenen inline assembly)

Zwecks XP kompatibilität. Mit dem service pack für visual studio 2012 sollte das doch nicht mehr nötig sein? Es wird jetzt doch extra eine Windows XP kompatible 11er version des compilers angeboten?
Benutzeravatar
Krishty
Establishment
Beiträge: 8250
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: A Guide to RVO in Visual C++ 2012

Beitrag von Krishty »

Ingrater hat geschrieben:Super vielen dank für die Ausführliche Beschreibung, ich werde mal versuchen das die Tage zu reproduzieren um herauszufinden woran es lag. Es ging dabei aber auch um Vektoroperationden die mit SSE optimiert wurden. Mir ist dabei aufgefallen, dass der Compiler sehr viele unnötige movaps instruktionen generiert hat (Im Vergleich zu meinem handgeschriebenen inline assembly)
Das würde ich auch mal gerne sehen. (Ich habe Vektoroperationen vor zwei Jahren aufgegeben weil skalar schneller war; seitdem spiele ich immer mal wieder mit dem Gedanken, wieder zu vektorisieren.) Benutzt du die eingebauten __m128 oder was eigenes?
Ingrater hat geschrieben:Zwecks XP kompatibilität. Mit dem service pack für visual studio 2012 sollte das doch nicht mehr nötig sein? Es wird jetzt doch extra eine Windows XP kompatible 11er version des compilers angeboten?
Ja; aber ich habe dem Braten nie getraut – weil ich persönlich keine XP-Maschine besitze ist es mir einfach zu riskant, da sowas Großes zu ändern. Das aktuelle Visual Studio 2012 Update 2 hat auch tatsächlich den Fehler, dass ATL und MFC auf XP nicht mehr gehen (obwohl es vorher mit Update 1 funktionierte).
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Ingrater
Establishment
Beiträge: 103
Registriert: 18.04.2007, 21:52

Re: A Guide to RVO in Visual C++ 2012

Beitrag von Ingrater »

Ich verwende für meine hobby projekte D was leider momentan noch keine vernünftigen SSE intrinsics besitzt. Deswegen optimiere ich kritische Funktionen mit inline assembler. Kürzlich so geschehen für einen Monte Carlo Ray Tracer den ich für eine Vorlesung implementiert habe. Dadurch kann man auch durchaus siginifikante verbesserungen erziehlen überlicherweise schaffe ich mindestens faktor 2 teilweise sogar faktor 15 (besonders dann wenn float to int konvertierungen im Spiel sind die man paralellisieren kann). Letztes mal hatte ich dann versucht das ganze auch in SSE intrinsics in C++ zu schreiben um zu sehen ob der compiler eventuell besseren assembly ausspuckt als mein Hirn. Hat leider nicht geklappt.
Benutzeravatar
Krishty
Establishment
Beiträge: 8250
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: A Guide to RVO in Visual C++ 2012

Beitrag von Krishty »

Es ist tatsächlich ein Bug und sie versuchen, die Korrektur noch ins nächste Major Release zu quetschen:
https://connect.microsoft.com/VisualStudio/feedback/details/788471/no-nrvo-for-pod-with-floating-point-members hat geschrieben:Hi, thanks for the source code. The codegen differences you're seeing are indeed because we chose a slightly different optimization path due to the identification of POD versus non-POD. If you are curious, register allocation is affected and we introduce more spills -- those are the spills you see in the assembly code.

This is an optimization bug, and we will address that in a future release. It's getting pretty late in the product cycle for the next major release, but we will try to get it in.

In the mean time, if it's possible to use an empty constructor for performance.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten