Tipps zur C ++ - Optimierung auf niedriger Ebene [geschlossen]

79

Angenommen, Sie haben bereits den Algorithmus der besten Wahl. Welche einfachen Lösungen können Sie anbieten, um die letzten Tropfen der Sweet Sweet Frame Rate aus dem C ++ - Code herauszuholen?

Es versteht sich von selbst, dass diese Tipps nur für den kritischen Codeabschnitt gelten, den Sie bereits in Ihrem Profiler hervorgehoben haben. Es sollte sich jedoch um nicht strukturelle Verbesserungen auf niedriger Ebene handeln. Ich habe ein Beispiel ausgesät.

Tenpn
quelle
1
Was macht dies zu einer Frage zur Spieleentwicklung und nicht zu einer allgemeinen Programmierfrage wie dieser: stackoverflow.com/search?q=c%2B%2B+optimization
Danny Varod
@Danny - Dies könnte wahrscheinlich eine allgemeine Programmierfrage sein. Es ist sicherlich auch eine Frage im Zusammenhang mit der Programmierung von Spielen. Ich denke, es ist eine tragfähige Frage auf beiden Seiten.
Smashery
@Smashery Der einzige Unterschied zwischen beiden ist, dass für die Programmierung von Spielen bestimmte Optimierungen der Grafik-Engine-Ebene oder Optimierungen des Shader-Coders erforderlich sein können. Der C ++ - Teil ist derselbe.
Danny Varod
@Danny - Richtig, einige Fragen sind auf der einen oder anderen Seite "relevanter". aber ich möchte keine relevanten Fragen abweisen, nur weil sie auch auf einer anderen Site gestellt werden könnten.
Smashery

Antworten:

76

Optimieren Sie Ihr Datenlayout! (Dies gilt für mehr Sprachen als nur C ++)

Sie können ziemlich tief gehen, indem Sie dies speziell auf Ihre Daten, Ihren Prozessor, den guten Umgang mit Multicore usw. abstimmen. Das grundlegende Konzept lautet jedoch:

Wenn Sie Dinge in einer engen Schleife verarbeiten, möchten Sie die Daten für jede Iteration so klein wie möglich und im Speicher so nah wie möglich beieinander halten. Das heißt, das Ideal ist ein Array oder ein Vektor von Objekten (keine Zeiger), die nur die für die Berechnung erforderlichen Daten enthalten.

Auf diese Weise werden, wenn die CPU die Daten für die erste Iteration Ihrer Schleife abruft, die nächsten Dateniterationen in den Cache geladen.

Die CPU ist wirklich schnell und der Compiler ist gut. Mit weniger und schnelleren Anweisungen kann man nicht wirklich viel anfangen. Die Cache-Kohärenz ist der Ort, an dem sie sich befindet (das ist ein zufälliger Artikel, den ich gegoogelt habe - er enthält ein gutes Beispiel für die Ermittlung der Cache-Kohärenz für einen Algorithmus, der Daten nicht einfach linear durchläuft).

Andrew Russell
quelle
Es lohnt sich, das C-Beispiel auf der verknüpften Cache-Kohärenzseite auszuprobieren. Als ich zum ersten Mal davon erfuhr, war ich schockiert darüber, welchen Unterschied es macht.
Neel
9
Siehe auch die exzellenten Fallstricke der objektorientierten Programmierung (Sony R & D) ( research.scee.net/files/presentations/gcapaustralia09/… ) - und die kniffligen, aber faszinierenden CellPerformance-Artikel von Mike Acton ( cellperformance.beyond3d.com/articles/ index.html ). Auch Noel Llopis 'Games from Within-Blog geht häufig auf dieses Thema ein ( gamesfromwithin.com ). Ich kann die Pitfalls-Folien nicht genug empfehlen ...
Leander
2
Ich warne nur davor, "die Daten für jede Iteration so klein wie möglich und im Speicher so nah wie möglich zusammen zu halten" . Der Zugriff auf nicht ausgerichtete Daten kann zu einer Verlangsamung führen. In diesem Fall liefert die Polsterung bessere Ergebnisse. Die Reihenfolge der Daten ist ebenfalls wichtig, da gut geordnete Daten zu einer geringeren Auffüllung führen können. Scott Mayers kann das besser erklären als ich :)
Jonathan Connell
+1 zur Sony-Präsentation. Ich habe das bereits gelesen und es ist wirklich sinnvoll, Daten auf Plattformebene zu optimieren, wobei zu berücksichtigen ist, ob Daten in Blöcke aufgeteilt und richtig ausgerichtet werden sollen.
ChrisC
84

Ein sehr, sehr niedriger Tipp, der sich jedoch als nützlich erweisen kann:

Die meisten Compiler unterstützen explizite bedingte Hinweise. GCC hat eine Funktion namens __builtin_expect, mit der Sie den Compiler über den Wert eines Ergebnisses informieren können. GCC kann diese Daten verwenden, um die Bedingungen so zu optimieren, dass sie im erwarteten Fall so schnell wie möglich ausgeführt werden. Im unerwarteten Fall ist die Ausführung etwas langsamer.

if(__builtin_expect(entity->extremely_unlikely_flag, 0)) {
  // code that is rarely run
}

Ich habe eine 10-20% ige Beschleunigung bei richtiger Verwendung gesehen.

ZorbaTHut
quelle
1
Ich würde zweimal abstimmen, wenn ich könnte.
Tenpn
10
+1, Der Linux-Kernel nutzt dies ausgiebig für Mikrooptimierungen im Scheduler-Code und es macht einen signifikanten Unterschied in bestimmten Codepfaden.
Greyfade
2
Leider scheint es in Visual Studio kein gutes Äquivalent zu geben. stackoverflow.com/questions/1440570/…
mmyers
1
Bei welcher Frequenz müsste der erwartete Wert normalerweise der richtige sein, um Leistung zu erzielen? 49/50 mal? Oder 999999/1000000 mal?
Douglas
36

Das Erste, was Sie verstehen müssen, ist die Hardware, auf der Sie ausgeführt werden. Wie geht es mit Verzweigungen um? Was ist mit Caching? Hat es einen SIMD-Befehlssatz? Wie viele Prozessoren kann es verwenden? Muss es die Prozessorzeit mit irgendetwas anderem teilen?

Möglicherweise lösen Sie dasselbe Problem auf sehr unterschiedliche Weise - selbst die Wahl des Algorithmus sollte von der Hardware abhängen. In einigen Fällen kann O (N) langsamer als O (NlogN) ausgeführt werden (abhängig von der Implementierung).

Als groben Überblick über die Optimierung möchte ich zunächst genau untersuchen, welche Probleme und welche Daten Sie lösen möchten. Dann optimieren Sie das. Wenn Sie extreme Leistung wünschen, dann vergessen Sie generische Lösungen - Sie können alles, was nicht zu Ihrem am häufigsten verwendeten Fall passt, in Sonderfällen ausführen.

Dann profilieren. Profil, Profil, Profil. Betrachten Sie die Speichernutzung, die Verzweigungsstrafen, den Funktionsaufruf-Overhead und die Pipeline-Auslastung. Finden Sie heraus, was Ihren Code verlangsamt. Es ist wahrscheinlich Datenzugriff (ich habe einen Artikel mit dem Titel "The Latency Elephant" über den Overhead des Datenzugriffs geschrieben - google es. Ich kann hier keine zwei Links posten, da ich nicht genug "Reputation" habe) Optimieren Sie dann Ihr Datenlayout ( schöne, große, flache, homogene Arrays sind fantastisch ) und den Datenzugriff (Prefetch, wo möglich).

Wenn Sie den Overhead des Speichersubsystems minimiert haben, versuchen Sie herauszufinden, ob Anweisungen jetzt der Engpass sind (hoffentlich), und sehen Sie sich dann die SIMD-Implementierungen Ihres Algorithmus an - SoA-Implementierungen (Structure-of-Arrays) können sehr datenintensiv sein Befehlscache effizient. Wenn SIMD für Ihr Problem nicht geeignet ist, sind möglicherweise Codierungen auf Intrinsics- und Assembler-Ebene erforderlich.

Wenn Sie noch mehr Geschwindigkeit benötigen, gehen Sie parallel. Wenn Sie den Vorteil haben, auf einer PS3 zu laufen, sind die SPUs Ihre Freunde. Benutze sie, liebe sie. Wenn Sie bereits eine SIMD-Lösung geschrieben haben, profitieren Sie massiv von SPU.

Und dann noch ein paar mehr. Test in Spielszenarien - ist dieser Code immer noch der Engpass? Können Sie die Art und Weise ändern, wie dieser Code auf einer höheren Ebene verwendet wird, um seine Verwendung zu minimieren (dies sollte eigentlich Ihr erster Schritt sein)? Können Sie Berechnungen auf mehrere Frames verschieben?

Erfahren Sie auf jeder Plattform so viel wie möglich über die verfügbare Hardware und die verfügbaren Profiler. Gehen Sie nicht davon aus, dass Sie den Engpass kennen - finden Sie ihn mit Ihrem Profiler. Und stellen Sie sicher, dass Sie eine Heuristik haben, um festzustellen, ob Sie Ihr Spiel tatsächlich schneller gemacht haben.

Und dann nochmal profilieren.

Tony Albrecht
quelle
31

Erster Schritt: Denken Sie sorgfältig über Ihre Daten in Bezug auf Ihre Algorithmen nach. O (log n) ist nicht immer schneller als O (n). Einfaches Beispiel: Eine Hash-Tabelle mit nur wenigen Schlüsseln wird häufig besser durch eine lineare Suche ersetzt.

Zweiter Schritt: Sehen Sie sich die erzeugte Baugruppe an. C ++ bringt eine Menge impliziter Code-Generierung in die Tabelle. Manchmal schleicht es sich an dich heran, ohne dass du es weißt.

Vorausgesetzt aber, es ist wirklich eine Zeit, in der alles auf den Punkt gebracht wird: Profil. Ernsthaft. Das zufällige Anwenden von "Leistungstricks" schadet ebenso wie hilft.

Dann hängt alles von Ihren Engpässen ab.

Datencachefehler => Optimieren Sie Ihr Datenlayout. Hier ist ein guter Ausgangspunkt: http://gamesfromwithin.com/data-oriented-design

Code - Cache fehlt => Schauen Sie sich virtuelle Funktionsaufrufe, übermäßige Aufrufliste Tiefe usw. Eine häufige Ursache für schlechte Leistung ist der Irrglaube , dass Basisklassen müssen virtuell sein.

Andere gängige C ++ - Leistungseinbußen:

  • Übermäßige Zuordnung / Freigabe. Wenn es um die Leistung geht, rufen Sie nicht die Laufzeitumgebung auf. Je.
  • Konstruktion kopieren. Vermeiden Sie, wo immer Sie können. Wenn es sich um eine konstante Referenz handeln kann, machen Sie eine.

Alle oben genannten Punkte sind beim Betrachten der Baugruppe sofort ersichtlich, siehe oben;)

Rachel Blum
quelle
19

Entfernen Sie unnötige Zweige

Auf einigen Plattformen und bei einigen Compilern können Verzweigungen Ihre gesamte Pipeline verwerfen, sodass selbst unbedeutende if () -Blöcke teuer sein können.

Die PowerPC-Architektur (PS3 / x360) bietet die Gleitkomma-Auswahlanweisung fsel. Dies kann anstelle einer Verzweigung verwendet werden, wenn die Blöcke einfache Zuweisungen sind:

float result = 0;
if (foo > bar) { result = 2.0f; }
else { result = 1.0f; }

Wird:

float result = fsel(foo-bar, 2.0f, 1.0f);

Wenn der erste Parameter größer oder gleich 0 ist, wird der zweite Parameter zurückgegeben, andernfalls der dritte.

Der Preis für den Verlust der Verzweigung ist, dass sowohl der if {} - als auch der else {} -Block ausgeführt werden. Wenn also eine teure Operation ausgeführt wird oder ein NULL-Zeiger dereferenziert wird, ist diese Optimierung nicht geeignet.

Manchmal hat Ihr Compiler diese Arbeit bereits ausgeführt. Überprüfen Sie daher zuerst Ihre Assembly.

Hier finden Sie weitere Informationen zu branching und fsel:

http://assemblyrequired.crashworks.org/tag/intrinsics/

Tenpn
quelle
float result = (foo> bar)? 2.f: 1.f
knight666
3
@ knight666: Das wird immer noch irgendwo einen Zweig produzieren, den ein Langfinger "wenn" getan hätte. Ich sage es so, weil auf ARM zumindest kleine Sequenzen wie diese mit bedingten Anweisungen implementiert werden können, die keine Verzweigung erfordern.
Chrisbtoo
1
@ knight666 Wenn du Glück hast, kann der Compiler das in eine Fsel verwandeln, aber es ist nicht sicher. FWIW, ich würde dieses Snippet normalerweise mit einem tertiären Operator schreiben und es dann später optimieren, wenn der Profiler zustimmt.
Am
Auf IA32 hast du stattdessen CMOVcc.
Skizz
Siehe auch blueraja.com/blog/285/… (Beachten Sie, dass in diesem Fall der Compiler in der Lage sein sollte, dies selbst zu optimieren, sodass Sie sich normalerweise keine Sorgen machen müssen)
BlueRaja - Danny Pflughoeft
16

Vermeiden Sie unter allen Umständen Speicherzugriffe, insbesondere zufällige.

Das ist das Wichtigste, was bei modernen CPUs optimiert werden muss. Sie können in der Zeit, in der Sie auf Daten aus dem RAM warten, eine Menge Arithmetik und sogar viele falsch vorhergesagte Verzweigungen ausführen.

Sie können diese Regel auch umgekehrt lesen: Führen Sie zwischen den Speicherzugriffen so viele Berechnungen wie möglich durch.

Axel Gneiting
quelle
11

Entfernen Sie unnötige virtuelle Funktionsaufrufe

Der Versand einer virtuellen Funktion kann sehr langsam sein. Dieser Artikel gibt eine gute Erklärung, warum. Vermeiden Sie Funktionen, die viele, viele Male pro Frame aufgerufen werden, wenn möglich.

Sie können dies auf verschiedene Arten tun. Manchmal können Sie die Klassen einfach umschreiben, damit sie nicht vererbt werden müssen. Vielleicht stellt sich heraus, dass MachineGun die einzige Unterklasse von Weapon ist und Sie können sie zusammenführen.

Sie können Vorlagen verwenden, um den Laufzeit-Polymorphismus durch den Kompilierungs-Polymorphismus zu ersetzen. Dies funktioniert nur, wenn Sie den Untertyp Ihrer Objekte zur Laufzeit kennen und eine umfangreiche Umschreibung durchführen können.

Tenpn
quelle
9

Mein Grundprinzip ist: Mach nichts, was nicht nötig ist .

Wenn Sie festgestellt haben, dass eine bestimmte Funktion ein Engpass ist, können Sie die Funktion optimieren oder versuchen, den Aufruf zunächst zu unterbinden.

Dies bedeutet nicht unbedingt, dass Sie einen schlechten Algorithmus verwenden. Dies kann bedeuten, dass Sie alle Frames berechnen, die beispielsweise für kurze Zeit zwischengespeichert (oder vollständig vorberechnet) wurden.

Ich probiere diesen Ansatz immer aus, bevor ich mich um eine wirklich einfache Optimierung bemühe.

mmyers
quelle
2
Diese Frage setzt voraus, dass Sie bereits alle strukturellen Dinge erledigt haben, die Sie können.
Tenpn
2
Es tut. Aber oft nimmst du an, du hast es und du hast es nicht. Also, jedes Mal, wenn eine teure Funktion optimiert werden muss, fragen Sie sich, ob Sie diese Funktion aufrufen müssen.
Rachel Blum
2
... aber manchmal kann es sogar schneller sein, die Berechnung durchzuführen, selbst wenn Sie das Ergebnis später wegwerfen, anstatt es zu verzweigen.
Tenpn
9

Verwenden Sie SIMD (per SSE), falls Sie dies noch nicht getan haben. Gamasutra hat einen schönen Artikel dazu . Sie können den Quellcode aus der vorgestellten Bibliothek am Ende des Artikels herunterladen.

Peter Mortensen
quelle
6

Minimieren Sie die Abhängigkeitsketten, um die CPU-Pipeline besser nutzen zu können.

In einfachen Fällen kann der Compiler dies für Sie tun, wenn Sie das Abrollen der Schleife aktivieren. Dies ist jedoch häufig nicht der Fall, insbesondere wenn es sich um Floats handelt, da die Neuanordnung der Ausdrücke das Ergebnis ändert.

Beispiel:

float *data = ...;
int length = ...;

// Slow version
float total = 0.0f;
int i;
for (i=0; i < length; i++)
{
  total += data[i]
}

// Fast version
float total1, total2, total3, total4;
for (i=0; i < length-3; i += 4)
{
  total1 += data[i];
  total2 += data[i+1];
  total3 += data[i+2];
  total4 += data[i+3];
}
for (; i < length; i++)
{
  total += data[i]
}
total += (total1 + total2) + (total3 + total4);
Adam
quelle
4

Übersehen Sie Ihren Compiler nicht - wenn Sie gcc unter Intel verwenden, können Sie leicht einen Leistungsgewinn erzielen, indem Sie beispielsweise auf den Intel C / C ++ Compiler umsteigen. Wenn Sie auf eine ARM-Plattform abzielen, lesen Sie den kommerziellen Compiler von ARM. Wenn Sie mit dem iPhone arbeiten, hat Apple lediglich zugelassen, dass Clang ab dem iOS 4.0 SDK verwendet wird.

Ein Problem, auf das Sie wahrscheinlich bei der Optimierung stoßen werden, insbesondere beim x86, ist, dass viele intuitive Dinge bei modernen CPU-Implementierungen gegen Sie arbeiten. Leider ist es für die meisten von uns längst nicht mehr möglich, den Compiler zu optimieren. Der Compiler kann Anweisungen im Stream basierend auf seinem eigenen internen Wissen über die CPU planen. Darüber hinaus kann die CPU Anweisungen auf der Grundlage ihrer eigenen Anforderungen neu planen. Auch wenn Sie sich eine optimale Methode zum Anordnen überlegen, ist die Wahrscheinlichkeit groß, dass der Compiler oder die CPU sich das schon ausgedacht und diese Optimierung bereits durchgeführt hat.

Mein bester Rat wäre, die Optimierungen auf niedriger Ebene zu ignorieren und sich auf die übergeordneten zu konzentrieren. Der Compiler und die CPU können Ihren Algorithmus nicht von einem O (n ^ 2) zu einem O (1) -Algorithmus ändern, egal wie gut sie werden. Sie müssen sich genau ansehen, was Sie tun möchten, und einen besseren Weg finden, dies zu tun. Lassen Sie den Compiler und die CPU sich Gedanken über die niedrige Stufe machen, und konzentrieren Sie sich auf die mittleren bis hohen Stufen.

Dennis Munsie
quelle
Ich verstehe, was Sie sagen, aber irgendwann haben Sie O (logN) erreicht, und Sie werden nicht mehr von strukturellen Veränderungen profitieren, bei denen die Optimierungen auf niedriger Ebene zum Tragen kommen und Sie gewinnen können diese zusätzliche halbe Millisekunde.
Tenpn
1
Siehe meine Antwort zu: O (log n). Wenn Sie eine halbe Millisekunde suchen, müssen Sie möglicherweise auch auf die höhere Ebene schauen. Das sind 3% Ihrer Bildzeit!
Rachel Blum
4

Das Schlüsselwort restricted ist möglicherweise nützlich, insbesondere in Fällen, in denen Sie Objekte mit Zeigern bearbeiten müssen. Auf diese Weise kann der Compiler davon ausgehen, dass das Objekt, auf das verwiesen wird, nicht auf andere Weise geändert wird, was wiederum eine aggressivere Optimierung ermöglicht, z. B. Teile des Objekts in Registern zu halten oder Lese- und Schreibvorgänge effektiver neu zu ordnen.

Eine gute Sache an dem Schlüsselwort ist, dass es ein Hinweis ist, den Sie einmal anwenden können, um die Vorteile zu sehen, ohne Ihren Algorithmus neu zu ordnen. Die schlechte Seite ist, dass wenn Sie es an der falschen Stelle verwenden, Sie möglicherweise Datenbeschädigung sehen. Normalerweise ist es jedoch recht einfach zu erkennen, wo es legitim ist, es zu verwenden - eines der wenigen Beispiele, bei denen der Programmierer vernünftigerweise mehr wissen muss, als der Compiler mit Sicherheit annehmen kann, weshalb das Schlüsselwort eingeführt wurde.

Technisch gesehen gibt es in C ++ keine Einschränkung, aber für die meisten C ++ - Compiler sind plattformspezifische Entsprechungen verfügbar.

Siehe auch: http://cellperformance.beyond3d.com/articles/2006/05/demystifying-the-restrict-keyword.html

Kylotan
quelle
2

Const alles!

Je mehr Informationen Sie dem Compiler über die Daten geben, desto besser sind die Optimierungen (zumindest nach meiner Erfahrung).

void foo(Bar * x) {...;}

wird;

void foo(const Bar * const x) {...;}

Der Compiler weiß jetzt, dass sich der Zeiger x nicht ändern wird und dass sich auch die Daten, auf die er zeigt, nicht ändern werden.

Der andere zusätzliche Vorteil ist, dass Sie die Anzahl versehentlicher Fehler reduzieren können, indem Sie sich selbst (oder andere) davon abhalten, Dinge zu ändern, die sie nicht sollten.

Sheredom
quelle
Und dein Code-Freund wird dich lieben!
Tenpn
4
constverbessert keine Compiler-Optimierungen. Richtig, der Compiler kann besseren Code generieren, wenn er weiß, dass sich eine Variable nicht ändert, aber constkeine ausreichende Garantie bietet.
deft_code
3
Nee. 'einschränken' ist weitaus nützlicher als 'const'. Siehe gamedev.stackexchange.com/questions/853/...
Justicle
+1 Leute sagen, dass konstante Hilfe falsch ist ... infoq.com/presentations/kixeye-scalability
NoSenseEtAl
2

In den meisten Fällen können Sie am besten die Leistung steigern, indem Sie Ihren Algorithmus ändern. Je weniger allgemein die Implementierung ist, desto näher kommt man dem Metall.

Vorausgesetzt, das wurde getan ...

Wenn es sich in der Tat um wirklich kritischen Code handelt, versuchen Sie, Speicherlesevorgänge zu vermeiden, und vermeiden Sie die Berechnung von Daten, die vorberechnet werden können (obwohl keine Nachschlagetabellen vorhanden sind, da sie gegen Regel 1 verstoßen). Wissen Sie, was Ihr Algorithmus macht, und schreiben Sie ihn so, dass der Compiler ihn auch kennt. Überprüfen Sie die Baugruppe, um sicherzustellen, dass dies der Fall ist.

Vermeiden Sie Cache-Fehler. Batch-Prozess so viel wie möglich. Vermeiden Sie virtuelle Funktionen und andere Indirektionen.

Letztendlich alles messen. Die Regeln ändern sich ständig. Was vor 3 Jahren den Code beschleunigte, verlangsamt ihn jetzt. Ein schönes Beispiel ist "Verwenden Sie doppelte mathematische Funktionen anstelle von Float-Versionen". Ich hätte das nicht bemerkt, wenn ich es nicht gelesen hätte.

Ich habe vergessen: Lassen Sie Ihre Variablen nicht von Standardkonstruktoren initialisieren. Wenn Sie darauf bestehen, erstellen Sie zumindest auch Konstruktoren, die dies nicht tun. Beachten Sie die Dinge, die in den Profilen nicht angezeigt werden. Wenn Sie einen unnötigen Zyklus pro Codezeile verlieren, wird in Ihrem Profiler nichts angezeigt, aber insgesamt gehen viele Zyklen verloren. Wieder wissen, was Ihr Code tut. Machen Sie Ihre Kernfunktion schlank statt narrensicher. Narrensichere Versionen können bei Bedarf aufgerufen werden, werden aber nicht immer benötigt. Vielseitigkeit kommt zu einem Preis - Leistung ist eins.

Bearbeitet, um zu erklären, warum es keine Standardinitialisierung gibt: Viel Code sagt: Vector3 bla; bla = DoSomething ();

Die Initialisierung im Konstruktor ist Zeitverschwendung. Auch in diesem Fall ist die verschwendete Zeit gering (wahrscheinlich wird der Vektor gelöscht). Wenn Ihre Programmierer dies jedoch gewohnheitsmäßig tun, summiert sich dies. Außerdem erzeugen viele Funktionen einen temporären Operator (denken Sie an überladene Operatoren), der sofort auf Null initialisiert und zugewiesen wird. Versteckte verlorene Zyklen, die zu klein sind, um einen Spitzenwert in Ihrem Profiler zu sehen, aber die Zyklen über Ihre gesamte Codebasis verteilen. Manche Leute machen auch viel mehr mit Konstruktoren (was offensichtlich ein Nein-Nein ist). Ich habe Multi-Millisekunden-Gewinne von einer nicht verwendeten Variablen gesehen, bei der der Konstruktor zufällig etwas zu schwer war. Sobald der Konstruktor Nebenwirkungen verursacht, kann der Compiler ihn nicht mehr deaktivieren. Wenn Sie also nicht den obigen Code verwenden, bevorzuge ich entweder einen nicht initialisierenden Konstruktor oder, wie gesagt,

Vector3 bla (noInit); bla = doSomething ();

Kaj
quelle
/ Nicht / Initialisieren Sie Ihre Mitglieder in Konstruktoren? Wie hilft das?
24.07.10
Siehe bearbeiteten Beitrag. Passte nicht in das Kommentarfeld.
Kaj
const Vector3 = doSomething()? Dann kann die Rückgabewertoptimierung ansetzen und wahrscheinlich die eine oder andere Aufgabe auslösen.
Am
1

Reduzieren Sie die Auswertung von Booleschen Ausdrücken

Dieser ist wirklich verzweifelt, da es sich um eine sehr subtile, aber gefährliche Änderung an Ihrem Code handelt. Wenn Sie jedoch eine Bedingung haben, die übermäßig oft ausgewertet wird, können Sie den Aufwand für die boolesche Auswertung verringern, indem Sie stattdessen bitweise Operatoren verwenden. Damit:

if ((foo && bar) || blah) { ... } 

Wird:

if ((foo & bar) | blah) { ... }

Verwenden Sie stattdessen Integer-Arithmetik. Wenn Ihre foos und bars Konstanten sind oder vor if () ausgewertet werden, ist dies möglicherweise schneller als die normale Boolesche Version.

Als Bonus hat die arithmetische Version weniger Verzweigungen als die reguläre Boolesche Version. Welches ist ein anderer Weg, um zu optimieren .

Der große Nachteil ist, dass Sie die faule Bewertung verlieren - der gesamte Block wird bewertet, so dass Sie nicht tun können foo != NULL & foo->dereference(). Aus diesem Grund ist es fraglich, ob dies schwierig beizubehalten ist, weshalb der Kompromiss möglicherweise zu groß ist.

Tenpn
quelle
1
Das ist ein ziemlich ungeheurer Kompromiss aus Gründen der Leistung, vor allem, weil es nicht sofort offensichtlich ist, dass es beabsichtigt war.
Bob Somers
Ich stimme Ihnen fast vollständig zu. Ich habe gesagt, es war verzweifelt!
Am
3
Würde dies nicht auch den Kurzschluss unterbrechen und die Verzweigungsvorhersage unzuverlässiger machen?
Egon
1
Wenn foo 2 und bar 1 ist, verhält sich der Code überhaupt nicht gleich. Das ist, und nicht eine frühe Bewertung, der größte Nachteil, den ich denke.
1
Acutally, booleans in C ++ sind guarenteed 0 oder 1 sein, so lange , wie Sie tun dies nur mit bools Sie sicher sind. Mehr: altdevblogaday.org/2011/04/18/understanding-your-bool-type
tenpn
1

Behalten Sie Ihre Stapelverwendung im Auge

Alles, was Sie dem Stapel hinzufügen, ist ein zusätzlicher Push und eine zusätzliche Konstruktion, wenn eine Funktion aufgerufen wird. Wenn viel Stapelspeicher benötigt wird, kann es manchmal nützlich sein, Arbeitsspeicher vorab zuzuweisen, und wenn auf der Plattform, auf der Sie arbeiten, schneller Arbeitsspeicher zur Verfügung steht - umso besser!

Neilogd
quelle