Ich muss große Summen von 3D-Vektoren berechnen, und ein Vergleich der Verwendung einer Vektorklasse mit überladenem Operator + und Operator * im Vergleich zur Aufsummierung separater Komponenten zeigt einen Leistungsunterschied von etwa einem Faktor drei. Ich weiß nehme an, dass der Unterschied auf die Konstruktion von Objekten in den überlasteten Operatoren zurückzuführen sein muss.
Wie kann man die Konstruktion vermeiden und die Leistung verbessern?
Ich bin besonders verwirrt, weil das folgende afaik im Grunde der Standardweg ist, und ich würde erwarten, dass der Compiler dies optimiert. Im wirklichen Leben werden die Summen nicht innerhalb einer Schleife, sondern in ziemlich großen Ausdrücken (einige Dutzend MBs insgesamt vor ausführbar), die verschiedene Vektoren summieren, ausgeführt, weshalb Operator + unten verwendet wird.
%Vor%Jede Hilfe wird sehr geschätzt.
BEARBEITEN: Danke euch allen für eure tollen Inputs, ich habe die Performance jetzt auf gleichem Niveau. @ Dimas und vor allem @ Xeos Antwort hat es geschafft. Ich wünschte, ich könnte mehr als eine Antwort "akzeptiert" markieren. Ich werde einige der anderen Vorschläge auch testen.
Dieser Artikel hat eine wirklich gute Argumentation dazu Operatoren wie +
, -
, *
, /
optimieren.
Implementiere die operator+
als freie Funktion wie folgt in operator+=
:
Beachten Sie, dass der lhs
Vektor eine Kopie und keine Referenz ist. Dies erlaubt dem Compiler, Optimierungen wie zum Beispiel die Eliminierung von Kopien vorzunehmen
Die allgemeine Regel, die der Artikel vermittelt: Wenn Sie eine Kopie benötigen, tun Sie es in den Parametern, damit der Compiler optimieren kann. Der Artikel verwendet nicht dieses Beispiel, sondern die operator=
für das copy-and-swap-Idiom.
Ich habe die meisten Optimierungen, die hier vorgeschlagen werden, implementiert und mit der Leistung eines Funktionsaufrufs wie
verglichen %Vor%Wiederholte Ausführung derselben Schleife mit einigen Milliarden Vektor-Summationen für jede Methode in alternierender Reihenfolge führte nicht zu den versprochenen Verstärkungen.
Im Fall der Member-Funktion, die von bbtrb gepostet wurde, benötigte diese Methode 50% mehr Zeit als der Funktionsaufruf isSumOf()
.
Freie, nicht Mitglied Operator + (Xeo) -Methode benötigt bis zu verdoppeln die Zeit (100% mehr) der is SumOf()
-Funktion.
Ich bin mir der Tatsache bewusst, dass dies kein repräsentativer Test war, aber da ich keinerlei Leistungsgewinne durch die Verwendung von Operatoren reproduzieren konnte. Ich schlage vor, sie zu vermeiden, wenn möglich.
Normalerweise sieht Operator + wie folgt aus:
%Vor%mit einem geeignet definierten Konstruktor. Dies ermöglicht dem Compiler Rückgabewertoptimierung .
Aber wenn Sie für IA32 kompilieren, dann wäre SIMD eine Überlegung wert, zusammen mit Änderungen an den Algorithmen, um die SIMD-Natur zu nutzen. Andere Prozessoren können SIMD-Anweisungen haben.
Ich denke, der Leistungsunterschied wird hier durch die Compiler-Optimierung verursacht. Das Hinzufügen von Elementen von Arrays in einer Schleife kann vom Compiler vektorisiert werden. Moderne CPUs haben Anweisungen zum Hinzufügen mehrerer Zahlen in einem einzelnen Takt, wie SSE, SSE2 usw. Dies scheint eine wahrscheinliche Erklärung für den Unterschied von Faktor 3 zu sein, den Sie sehen.
Mit anderen Worten, das Hinzufügen entsprechender Elemente von zwei Arrays in einer Schleife kann im Allgemeinen schneller sein als das Hinzufügen entsprechender Member einer Klasse. Wenn Sie den Vektor als Array innerhalb Ihrer Klasse und nicht als x, y und z darstellen, erhalten Sie möglicherweise die gleiche Beschleunigung für Ihre überladenen Operatoren.
Funktionieren die Implementierungen Ihres Vector-Operators direkt in der Header-Datei oder befinden sie sich in einer separaten cpp-Datei? In der Header-Datei würden sie typischerweise in einem optimierten Build inline sein. Wenn sie jedoch in einer anderen Übersetzungseinheit kompiliert werden, sind sie oft nicht (abhängig von Ihren Build-Einstellungen). Wenn die Funktionen nicht inline sind, wird der Compiler nicht in der Lage sein, die Art der Optimierung auszuführen, nach der Sie suchen.
Sehen Sie sich in solchen Fällen die Demontage an. Selbst wenn Sie nicht viel über Assembler-Code wissen, ist es in der Regel ziemlich einfach herauszufinden, was in einfachen Fällen wie diesen anders ist.
Wenn man sich irgendeinen reellen Matrixcode anschaut, dann tun der Operator + und der Operator + = das nicht.
Wegen des damit verbundenen Kopierens führen sie ein Pseudoobjekt in den Ausdruck ein und machen nur dann die eigentliche Arbeit, wenn die Zuweisung ausgeführt wird. Wenn Sie eine solche faule Auswertung verwenden, können auch NULL-Operationen während der Ausdruckauswertung entfernt werden:
%Vor%Das ist natürlich viel komplexer, als ich hier in diesem vereinfachten Beispiel dargestellt habe. Wenn Sie jedoch eine Bibliothek verwenden, die für Matrixoperationen entwickelt wurde, ist dies bereits für Sie erledigt worden.
Implementiere die operator+()
wie folgt:
und fügen Sie den inline
-Operator in Ihrer Klassendefinition hinzu (dies vermeidet die Stack-Pushs und -Pops der Rückgabeadress- und Methodenargumente für jeden Methodenaufruf, wenn der Compiler dies für nützlich hält).
Fügen Sie dann diesen Konstruktor hinzu:
%Vor% Damit können Sie einen neuen Vektor sehr effizient konstruieren (wie Sie es in meinem operator+()
Vorschlag tun würden!)!
Sie haben:
%Vor%Rollen Sie diese Art von Schleifen ab! Das Ausführen von nur einer one -Operation (wie es für die Verwendung der SSE2 / 3-Erweiterungen oder ähnlichem optimiert wäre) in einer sehr großen -Schleife ist sehr ineffizient. Sie sollten eher so etwas tun:
%Vor%(Beachten Sie, dass dieser Code nicht getestet wurde und möglicherweise einen "off-by-one" -Fehler enthält ...)
Beachten Sie, dass Sie verschiedene Dinge fragen, weil die Daten nicht im Speicher auf die gleiche Weise angeordnet sind. Bei der Verwendung von Vektor-Array sind die Koordinaten verschachtelt "x1, y1, z1, x2, y2, z2, ...", während mit den Doppel-Arrays haben Sie "x1, x2, ..., y1, y2, ... z1 , z2 ... ". Ich nehme an, dies könnte sich auf Compiler-Optimierungen auswirken oder darauf, wie das Caching es behandelt.
Tags und Links c++ operator-overloading