Ich habe einen optimierten Code für einen Algorithmus geschrieben, der einen Mengenvektor berechnet. Ich habe es vor und nach verschiedenen Versuchen zeitlich abgestimmt, um die in der Funktion berechneten Daten aus der Funktion zu bekommen. Ich denke, dass die besondere Art der Berechnung oder die Art des Mengenvektors nicht relevant ist. Es folgt ein Überblick über den Code, Timings und Details.
Der gesamte Code wurde mit den folgenden Flags kompiliert:
g ++ -Wall -Wextra -Werror -std = c ++ 11 -pedantische -O3
Ich habe eine Klasse wie folgt:
%Vor%Und ein Haupt wie folgt:
%Vor%Hier sind einige relevante Details zu den Daten:
All dies erfordert genau 5 Minuten und 48 Sekunden zum Ausführen. Natürlich kann ich das Array innerhalb der Klassenfunktion drucken, und das habe ich gemacht, aber ich werde den Code öffentlich veröffentlichen, und einige Anwendungsfälle können auch etwas anderes als das Drucken des Vektors beinhalten. In diesem Fall muss ich die Funktionssignatur ändern, um die Daten tatsächlich an den Benutzer zu senden. Hier entsteht das Problem. Dinge, die ich versucht habe:
Erstellen Sie einen Vektor in main und übergeben Sie ihn als Referenz:
%Vor%Dies erfordert 7 Minuten 30 Sekunden. Wenn Sie den Aufruf von std :: fill entfernen, wird dies nur um 15 Sekunden reduziert, sodass die Diskrepanz nicht berücksichtigt wird.
Erstellen Sie einen Vektor innerhalb der doWork-Funktion und geben Sie ihn zurück, wobei Sie die Bewegungssemantik ausnutzen. Da dies für jedes Ergebnis eine dynamische Zuweisung erfordert, habe ich nicht erwartet, dass dies schnell erfolgt. Seltsamerweise ist es nicht viel langsamer. 7 Minuten 40 Sekunden.
Rückgabe des std :: -Arrays, das sich derzeit in doWork befindet, nach Wert. Natürlich muss dies die Daten bei der Rückkehr kopieren, da das Stapel-Array keine Bewegungssemantik unterstützt. 7 Minuten 30 Sekunden
Übergeben Sie ein std :: array durch Verweis.
%Vor%Ich würde erwarten, dass dies in etwa dem Original entspricht. Die Daten werden in der Hauptfunktion auf den Stapel gelegt und durch Verweis auf doWork übergeben, der sie füllt. 7 Minuten 20 Sekunden. Dieser hemmt mich wirklich.
Ich habe nicht versucht, Zeiger in doWork zu übergeben, da dies der Übergabe durch Referenz entsprechen sollte.
Eine Lösung besteht natürlich darin, zwei Versionen der Funktion zu haben: eine, die lokal druckt und eine, die zurückkehrt. Der Roadblock ist, dass ich ALLEN Code duplizieren müsste, weil das ganze Problem hier ist, dass ich die Ergebnisse aus einer Funktion nicht effizient erhalten kann.
Ich bin also verwirrt. Ich verstehe, dass jede dieser Lösungen eine zusätzliche Dereferenzierung für jeden Zugriff auf das Array / Vektor innerhalb der Arbeit erfordert, aber diese zusätzlichen Dereferenzierungen sind im Vergleich zu der großen Anzahl anderer schneller Operationen und mühsamer datenabhängiger Verzweigungen sehr trivial.
Ich begrüße irgendwelche Ideen, um dies zu erklären. Mein einziger Gedanke ist, dass der Code vom Compiler optimiert wird, so dass einige ansonsten notwendige Komponenten der Berechnung im ursprünglichen Fall weggelassen werden, weil der Compiler erkennt, dass es nicht notwendig ist. Dies scheint jedoch in mehreren Punkten kontraindiziert zu sein:
Ist doWork unabhängig von main kompiliert und optimiert, und wenn also die Funktion verpflichtet ist, die Daten in irgendeiner Form zurückzugeben, kann sie nicht sicher sein, ob sie verwendet wird oder nicht?
Ist meine Methode des Profilierens ohne Drucken, die die Kosten des Druckes vermeiden sollte, um die relativen Unterschiede in verschiedenen Methoden zu betonen, fehlerhaft?
Ich bin dankbar für jedes Licht, das du vergießen kannst.
Was ich tun würde, ist ein paar Mal anzuhalten und zu sehen, was es die meiste Zeit macht. Wenn ich Ihren Code betrachte, würde ich annehmen, dass die meiste Zeit entweder in a) die innerste Schleife geht, insbesondere die Indexberechnung, oder 2) die Zuweisung von std::array
.
Wenn die Größe von counts
immer 40 ist, würde ich einfach
Das wird auf dem Stack zugewiesen, was keine Zeit kostet, und memset
braucht keine Zeit im Vergleich zu dem, was Sie sonst noch tun.
Wenn die Größe zur Laufzeit variiert, dann mache ich eine statische Zuweisung wie folgt:
%Vor% Auf diese Weise ist counts
immer groß genug und
Der Punkt ist 1) do new
so oft wie möglich, und 2) halten Sie die Indizierung in counts
so einfach wie möglich.
Aber zuerst würde ich die Pausen machen, nur um sicher zu gehen. Nachdem ich es repariert hatte, würde ich das wieder tun, um zu sehen, was ich als nächstes reparieren könnte.
Compiler-Optimierungen sind ein Ort, den Sie betrachten müssen, aber es gibt noch einen weiteren Ort, an dem Sie suchen müssen. Änderungen, die Sie im Code vorgenommen haben, können das Cache-Layout stören. Wenn der Speicher, der dem Array zugewiesen ist, sich jedes Mal in einem anderen Teil des Speichers befindet, kann sich die Anzahl der Cache-Misses in Ihrem System erhöhen, was wiederum die Leistung beeinträchtigt. Sie können sich Hardware-Leistungsindikatoren auf Ihrer CPU ansehen, um eine bessere Schätzung zu treffen.
Es gibt Zeiten, in denen unorthodoxe Lösungen anwendbar sind, und dies kann eine sein. Haben Sie darüber nachgedacht, das Array global zu machen?
Dennoch ist der entscheidende Vorteil, den lokale Variablen haben, dass der Optimierer alle Zugriffe darauf finden kann, indem er nur Informationen aus der Funktion verwendet. Das erleichtert die Registerzuweisung erheblich.
Eine static
Variable innerhalb der Funktion ist fast gleich, aber in Ihrem Fall würde die Adresse dieses Stack-Arrays entkommen und den Optimierer erneut schlagen.
Tags und Links optimization c++ performance pass-by-reference function-calls