Im Allgemeinen, wie oft und wann sollte ich meinen Code optimieren?


13

Im "normalen" Business-Programmierbetrieb wird der Optimierungsschritt oft erst dann ausgeführt, wenn er wirklich benötigt wird. Das heißt, Sie sollten nicht optimieren, bis es wirklich benötigt wird.

Denken Sie daran, was Donald Knuth sagte: "Wir sollten in 97% der Fälle kleine Wirkungsgrade vergessen: Vorzeitige Optimierung ist die Wurzel allen Übels."

Wann ist die Zeit zu optimieren, um sicherzustellen, dass ich keine Mühe verschwenden. Soll ich es ein Methodenlevel machen? Klassenstufe? Modulebene?

Auch was soll meine Messung der Optimierung? Zecken? Bildrate? Gesamtzeit?

Antworten:


18

Wo ich gearbeitet habe, verwenden wir immer mehrere Ebenen der Profilerstellung. Wenn Sie ein Problem feststellen, blättern Sie einfach ein bisschen weiter nach unten, bis Sie herausgefunden haben, was los ist:

  • Der "menschliche Profiler", auch bekannt als " nur spielen" ; Fühlt es sich gelegentlich langsam oder "hitch" an? Ruckelige Animationen bemerken? (Beachten Sie als Entwickler, dass Sie für einige Arten von Leistungsproblemen sensibler und für andere weniger empfänglich sind. Planen Sie zusätzliche Tests entsprechend.)
  • Schalten Sie die FPS-Anzeige ein , bei der es sich um eine durchschnittliche FPS von 5 Sekunden im Schiebefenster handelt. Sehr wenig Aufwand zum Berechnen und Anzeigen.
  • Aktivieren Sie die Profilleisten , bei denen es sich nur um eine Reihe von Quads (ROYGBIV-Farben) handelt, die verschiedene Teile des Frames darstellen (z. B. Leerzeichen, Preframe, Aktualisierung, Kollision, Rendern, Postframe), indem Sie einen einfachen Stoppuhr-Timer um jeden Codeabschnitt verwenden . Um zu betonen, was wir wollen, setzen wir einen Balken mit einer Bildschirmbreite, der für einen 60-Hz-Zielrahmen repräsentativ ist. So ist es wirklich leicht zu erkennen, ob Sie beispielsweise 50% unter dem Budget (nur einen halben Balken) oder 50% über dem Budget ( Die Leiste wird zu anderthalb Leisten. Es ist auch ziemlich einfach zu erkennen, was im Allgemeinen den größten Teil des Frames ausmacht: Rot = Rendern, Gelb = Aktualisieren usw.
  • Erstellen Sie einen speziellen instrumentierten Build , der "Stoppuhr" -ähnlichen Code um jede Funktion einfügt. (Beachten Sie, dass Sie dabei unter Umständen einen massiven Performance-, Dcache- und Icache-Schlag erleiden müssen. Dies ist auf jeden Fall aufdringlich. Wenn Sie jedoch keinen ordnungsgemäßen Sampling-Profiler oder keine angemessene Unterstützung für die CPU haben, ist dies eine akzeptable Option. Sie können auch clever sein über das Aufzeichnen eines Minimums an Daten bei der Eingabe / Ausgabe von Funktionen und das spätere Wiederherstellen von Aufrufen.) Als wir unsere erstellten, ahmten wir einen Großteil des Ausgabeformats von gprof nach .
  • Führen Sie am besten einen Stichproben-Profiler aus . VTune und CodeAnalyst sind für x86 und x64 verfügbar. Sie haben verschiedene Simulations- oder Emulationsumgebungen, die Ihnen hier möglicherweise Daten liefern.

(Es gibt eine lustige Geschichte aus der GDC eines Grafikprogrammierers aus dem vergangenen Jahr, der vier Bilder von sich selbst gemacht hat - fröhlich, gleichgültig, genervt und wütend - und in der Ecke der internen Builds ein entsprechendes Bild basierend auf der Framerate angezeigt hat Inhaltsersteller lernten schnell, komplizierte Shader nicht für alle ihre Objekte und Umgebungen zu aktivieren: Sie würden den Programmierer verärgern.

Beachten Sie, dass Sie auch lustige Dinge tun können, wie die "Profilleisten" fortlaufend grafisch darstellen, so dass Sie Spitzenmuster ("wir verlieren alle 7 Bilder ein Bild") oder ähnliches sehen können.

Zur Beantwortung Ihrer Frage direkt, aber: in meiner Erfahrung, während es verlockend (und oft belohnt - ich in der Regel etwas lernen) einzelne Funktionen / Module zur Optimierung der Anzahl von Befehlen oder icache oder dCache Leistung neu zu schreiben, und wir tatsächlich brauchten zu tun Dies ist manchmal der Fall, wenn wir ein besonders unangenehmes Leistungsproblem haben. Die überwiegende Mehrheit der Leistungsprobleme, mit denen wir uns regelmäßig befassen, hängt vom Design ab . Beispielsweise:

  • Sollten wir im RAM zwischenspeichern oder die "Angriffs" -Status-Animations-Frames für den Player von der Festplatte neu laden? Wie wäre es mit jedem Feind? Wir haben keinen RAM, um sie alle zu erledigen, aber das Laden von Festplatten ist teuer! Sie können das Hitching sehen, wenn 5 oder 6 verschiedene Feinde gleichzeitig hereinkommen! (Okay, wie wäre es mit taumelndem Laichen?)
  • Führen wir eine einzelne Operation für alle Partikel oder für alle Operationen für ein einzelnes Partikel aus? (Dies ist ein Icache / Dcache-Kompromiss, und die Antwort ist nicht immer klar.) Wie wäre es, wenn Sie alle Partikel auseinander ziehen und die Positionen zusammen speichern (die berühmte "Struktur von Arrays") und alle Partikeldaten an einem Ort aufbewahren (" Array von Strukturen ").

Sie hören es, bis es in irgendwelchen Informatikkursen auf Universitätsniveau unangenehm wird, aber: es dreht sich wirklich alles um die Datenstrukturen und Algorithmen. Wenn Sie etwas Zeit mit dem Entwurf von Algorithmen und Datenflüssen verbringen, werden Sie im Allgemeinen mehr fürs Geld bekommen. (Vergewissern Sie sich, dass Sie die hervorragenden Fallstricke objektorientierter Programmierung von einem Sony Developer Services-Kollegen gelesen haben, um sich hier einen Überblick zu verschaffen .) Das "fühlt" sich nicht nach Optimierung an. Die meiste Zeit wird mit einem Whiteboard oder UML-Tool verbracht oder es werden viele Prototypen erstellt, anstatt den aktuellen Code schneller laufen zu lassen. Aber es lohnt sich in der Regel viel mehr.

Und noch eine nützliche Heuristik: Wenn Sie sich dem "Kern" Ihrer Engine nähern, lohnt es sich möglicherweise, zusätzliche Anstrengungen und Experimente zu unternehmen, um diese zu optimieren (z. B. diese Matrixmultiplikationen zu vektorisieren!). Je weiter Sie vom Kern entfernt sind, desto weniger sollten Sie sich darüber Gedanken machen, es sei denn, eines Ihrer Profilerstellungstools teilt Ihnen etwas anderes mit.


6
  1. Verwenden Sie die richtigen Datenstrukturen und Algorithmen im Voraus.
  2. Optimieren Sie erst, wenn Sie ein Profil erstellt haben und genau wissen, wo sich Ihre Hot Spots befinden.
  3. Mach dir keine Sorgen, schlau zu sein. Der Compiler macht bereits alle kleinen Tricks, an die Sie denken ("Oh! Ich muss mit vier multiplizieren! Ich verschiebe zwei nach links!")
  4. Achten Sie auf Cache-Fehler.

1
Sich auf den Compiler zu verlassen, ist nur bis zu einem gewissen Punkt sinnvoll. Ja, es werden einige Gucklochoptimierungen durchgeführt, an die Sie nicht gedacht hätten (und die ohne Assemblierung nicht möglich wären), aber es ist unwissend, was Ihr Algorithmus tun soll, so dass es keine intelligenten Optimierungen durchführen kann. Außerdem wären Sie überrascht, wie viele Zyklen Sie gewinnen können, wenn Sie kritischen Code in Assemblys oder Intrinsics implementieren ... wenn Sie wissen, was Sie tun. Compiler sind nicht so schlau, wie man es sich vorstellt. Sie wissen nichts, was Sie tun, es sei denn, Sie sagen es ihnen überall ausdrücklich (z. B. wenn Sie religiös einschränken).
Kaj

1
Und ich muss noch einmal erwähnen, dass Sie, wenn Sie nur nach Hot Spots suchen, eine Menge Zyklen verpassen, weil Sie keine durcheinander geratenen Zyklen finden (zum Beispiel Smartpointers ... Dereferenzen, die nirgendwo auftauchen und nie auftauchen) als Hotspot, weil Ihr gesamtes Programm quasi ein Hotspot ist).
Kaj

1
Ich bin mit beiden Ihrer Punkte einverstanden, aber ich würde das meiste davon unter "Verwenden der richtigen Datenstrukturen und Algorithmen" zusammenfassen. Wenn Sie überall nachgezählte intelligente Zeiger herumreichen und durch das Zählen Blutungszyklen verursachen, haben Sie definitiv die falsche Datenstruktur ausgewählt.
Munificent

5

Denken Sie aber auch an "vorzeitige Pessimisierung". Es ist zwar nicht erforderlich, in jeder Codezeile Hardcore zu betreiben, aber es gibt Gründe dafür, zu erkennen, dass Sie tatsächlich an einem Spiel arbeiten, das Auswirkungen auf die Echtzeitleistung hat.
Während Sie alle angewiesen haben, die Hotspots zu messen und zu optimieren, zeigt Ihnen diese Technik keine Leistung, die an verborgenen Orten verloren geht. Wenn zum Beispiel jede '+' - Operation in Ihrem Code doppelt so lange dauert wie sie sollte, wird sie nicht als Hotspot angezeigt und Sie werden sie daher niemals optimieren oder gar realisieren, da sie jedoch im gesamten Code verwendet wird Platzieren Sie es könnte Sie eine Menge Leistung kosten. Sie wären überrascht, wie viele dieser Zyklen verrinnen, ohne jemals entdeckt zu werden. Also sei dir bewusst, was du tust.
Abgesehen davon neige ich dazu, mich regelmäßig zu profilieren, um eine Vorstellung davon zu bekommen, was da ist und wie viel Zeit pro Frame verbleibt. Für mich ist die Zeit pro Frame die logischste, da sie mir direkt sagt, wo ich mit Framerate-Zielen bin. Versuchen Sie auch herauszufinden, wo sich Peaks befinden und was sie verursacht. Ich bevorzuge eine stabile Framerate gegenüber einer hohen Framerate mit Spikes.


Das scheint mir so falsch. Sicher, mein '+' kann bei jedem Aufruf doppelt so lange dauern, aber das ist wirklich nur in einer engen Schleife von Bedeutung. Innerhalb einer engen Schleife kann das Ändern eines einzelnen "+" mehr Größenordnungen bewirken als das Ändern eines "+" außerhalb der Schleife. Warum an eine Zehntel-Mikrosekunde denken, wenn eine Millisekunde gespart werden kann?
Wilduck

1
Dann verstehst du die Idee hinter dem Rinnsalverlust nicht. '+' (nur als Beispiel) wird hunderttausend Mal pro Frame aufgerufen, nicht nur in engen Schleifen. Wenn dies jedes Mal einige Zyklen verliert, wenn Sie viele Zyklen auf der ganzen Linie verloren haben, wird es jedoch nie als Hotspot angezeigt, da die Aufrufe gleichmäßig auf Ihre Codebasis / Ihren Ausführungspfad verteilt sind. Sie sprechen also nicht von einer Zehntel-Mikrosekunde, sondern vom Tausendfachen dieser Zehntel-Mikrosekunden, was sich auf mehrere Millisekunden summiert. Nachdem ich mich für die niedrig hängenden Früchte entschieden hatte (enge Schleifen), gewann ich mehr als einmal Millisekunden auf diese Weise.
Kaj

Es ist wie ein Hahn, der tropft. Warum sollten Sie sich Sorgen machen, diesen kleinen Tropfen zu retten? - "Wenn Ihr Wasserhahn mit einer Geschwindigkeit von einem Tropfen pro Sekunde tropft, können Sie damit rechnen, 2700 Gallonen pro Jahr zu verschwenden."
Kaj

Oh, ich denke, es war nicht klar, dass ich meinte, wenn der Operator + überladen war, so dass es jedes '+' im Code betreffen würde - Sie würden in der Tat nicht jedes '+' im Code optimieren wollen. Ich schätze, ein schlechtes Beispiel ... Ich meinte es als Beispiel für "Kernfunktionalität, die überall dort aufgerufen wird, wo die Implementierung möglicherweise langsamer als angenommen ist, insbesondere wenn sie durch Überladen von Operatoren oder andere verschleierte C ++ - Konstrukte verborgen wird".
Kaj

3

Sobald ein Spiel veröffentlicht werden kann (entweder final oder beta) oder es merklich langsam ist, ist es wahrscheinlich die beste Zeit, um Ihre App zu profilieren . Natürlich können Sie den Profiler jederzeit ausführen. Aber ja, vorzeitige Optimierung ist die Wurzel allen Übels. Unbegründete Optimierung auch; Sie benötigen aktuelle Daten, um zu zeigen, dass ein Teil des Codes langsam ist, bevor Sie versuchen sollten, ihn zu "optimieren". Ein Profiler erledigt das für Sie.

Wenn Sie keinen Profiler kennen, lernen Sie ihn! Hier ist ein guter Blog-Beitrag , der die Nützlichkeit eines Profilers demonstriert.

Bei der Optimierung des Spielcodes geht es hauptsächlich darum, die für jeden Frame erforderlichen CPU-Zyklen zu reduzieren. Eine Möglichkeit, dies zu tun, besteht darin, jede Routine beim Schreiben zu optimieren und sicherzustellen, dass sie so schnell wie möglich ist. Es ist jedoch allgemein bekannt, dass 90% der CPU-Zyklen in 10% des Codes verbracht werden. Das bedeutet, dass die Ausrichtung Ihrer gesamten Optimierungsarbeit auf diese Engpassroutinen das 10-fache der Wirkung hat, alles gleichmäßig zu optimieren. Wie identifizieren Sie diese Routinen? Profiling macht es einfach.

Wenn Ihr kleines Spiel mit 200 FPS läuft, obwohl es einen ineffizienten Algorithmus enthält, haben Sie dann wirklich einen Grund zur Optimierung? Sie sollten eine gute Vorstellung von den Spezifikationen Ihres Zielcomputers haben und sicherstellen, dass das Spiel auf diesem Computer gut läuft, aber alles, was darüber hinausgeht, ist (vermutlich) verschwendete Zeit, die besser für das Codieren oder Polieren des Spiels aufgewendet werden könnte.


Während die niedrig hängenden Früchte in der Tat dazu neigen, in 10% des Codes zu liegen und am Ende leicht durch Profiling erfasst zu werden, werden Sie bei der reinen Profilerstellung die Routinen verpassen, die oft aufgerufen werden, aber nur wenig haben Jedes Mal ein bisschen schlechter Code - sie werden nicht in Ihrem Profil angezeigt, aber sie bluten viele Zyklen pro Anruf aus. Es summiert sich wirklich.
Kaj

@ Kaj, gute Profiler summieren alle Hunderte von einzelnen Ausführungen des schlechten Algorithmus und zeigen Ihnen die Summe. Als nächstes werden Sie sagen: "Aber was ist, wenn Sie 10 schlechte Methoden hatten und alle mit 1/10 der Frequenz aufgerufen haben?" Wenn Sie Ihre ganze Zeit mit diesen 10 Methoden verbringen, werden Sie all die niedrig hängenden Früchte vermissen, wo Sie einen viel größeren Knall für Ihr Geld bekommen werden.
John McDonald

2

Ich finde es nützlich, Profile einzubauen. Auch wenn Sie nicht aktiv optimieren, ist es gut, eine Vorstellung davon zu haben, was Ihre Leistung zu einem bestimmten Zeitpunkt einschränkt. Viele Spiele haben eine Art überlagerndes HUD, das eine einfache grafische Darstellung (normalerweise nur ein farbiger Balken) anzeigt, wie lange verschiedene Teile der Spielschleife jeden Frame benötigen.

Es wäre eine schlechte Idee, die Leistungsanalyse und -optimierung zu spät durchzuführen. Wenn Sie das Spiel bereits erstellt haben und 200% Ihres CPU-Budgets überschreiten und dies durch Optimierung nicht finden können, sind Sie fertig.

Sie müssen beim Schreiben die Budgets für Grafik, Physik usw. kennen. Sie können das nicht tun, wenn Sie keine Ahnung haben, wie Ihre Leistung aussehen wird, und Sie können das nicht erraten, ohne zu wissen, was Ihre Leistung ist und wie viel Schlappheit es geben könnte.

Bauen Sie also vom ersten Tag an einige Leistungsstatistiken ein.

Auch hier ist es wahrscheinlich am besten, nicht zu spät zu warten, damit Sie nicht die Hälfte Ihres Motors überarbeiten müssen. Auf der anderen Seite sollten Sie sich nicht zu sehr mit der Optimierung von Dingen beschäftigen, um jeden Zyklus zu optimieren, wenn Sie der Meinung sind, dass Sie den Algorithmus morgen vollständig ändern könnten oder wenn Sie keine echten Spieldaten durchlaufen haben.

Nehmen Sie die niedrig hängenden Früchte mit, packen Sie die großen Dinge regelmäßig an, und es sollte Ihnen gut gehen.


Wenn Sie den Ingame-Profiler erweitern (dem ich voll und ganz zustimme), können Sie den Ingame-Profiler so erweitern, dass er mehrere Balken (für mehrere Frames) anzeigt. Auf diese Weise können Sie das Spielverhalten mit Spitzen korrelieren und Engpässen begegnen, die bei Ihrer durchschnittlichen Erfassung nicht auftreten mit einem Profiler.
Kaj

2

Wenn er sich Knuths Zitat in seinem Kontext ansieht, erklärt er weiter, dass wir es optimieren sollten, aber mit Werkzeugen wie einem Profiler.

Sie sollten Ihre Anwendung ständig profilieren und speichern, nachdem die grundlegende Architektur festgelegt wurde.

Durch die Profilerstellung können Sie nicht nur die Geschwindigkeit erhöhen, sondern auch Fehler finden. Wenn Ihr Programm plötzlich die Geschwindigkeit drastisch ändert, liegt dies normalerweise an einem Fehler. Wenn Sie kein Profil erstellen, bleibt dies möglicherweise unbemerkt.

Der Trick bei der Optimierung besteht darin, dies von Grund auf zu tun. Warten Sie nicht bis zur letzten Minute. Stellen Sie sicher, dass Sie mit dem Design Ihres Programms die Leistung erhalten, die Sie benötigen, ohne sich auf böse Tricks in der inneren Schleife einzulassen.


1

Für mein Projekt wende ich normalerweise einige SEHR benötigte Optimierungen in meiner Grundmaschine an. Zum Beispiel implementiere ich immer gerne eine solide SIMD-Implementierung mit SSE2 und 3DNow! Dies stellt sicher, dass meine Gleitkomma-Mathematik genau dort ist, wo ich sie haben möchte. Eine weitere bewährte Methode besteht darin, Optimierungen beim Codieren zur Gewohnheit zu machen, anstatt zurückzukehren. Meistens sind diese kleinen Übungen genauso zeitaufwendig wie das, was Sie ohnehin programmiert haben. Stellen Sie vor dem Codieren einer Funktion sicher, dass Sie nach den effizientesten Methoden suchen.

Fazit: Meiner Meinung nach ist es SCHWER, Ihren Code effizienter zu machen, nachdem er bereits gelutscht hat.


0

Ich würde sagen, der einfachste Weg wäre, Ihren gesunden Menschenverstand zu benutzen - wenn etwas so aussieht, als würde es langsam laufen, dann schauen Sie es sich an. Überprüfen Sie, ob es sich um einen Engpass handelt.
Verwenden Sie einen Profiler, um zu sehen, welche Geschwindigkeitsfunktionen ausgeführt werden und wie oft sie aufgerufen werden.
Es macht absolut keinen Sinn, etwas zu optimieren oder Zeit damit zu verbringen, etwas zu optimieren, das es nicht benötigt.


0

Wenn Ihr Code langsam ausgeführt wird, führen Sie einen Profiler aus und überprüfen Sie, was genau dazu führt, dass er langsamer ausgeführt wird. Sie können auch proaktiv sein und bereits einen Profiler ausführen, bevor Sie Leistungsprobleme bemerken.

Sie möchten optimieren, wenn Ihre Framerate auf einen Punkt fällt, unter dem das Spiel zu leiden beginnt. Ihr wahrscheinlichster Schuldiger wird sein, dass Ihre CPU zu stark ausgelastet ist (100%).


Ich würde sagen, die GPU ist genauso wahrscheinlich wie die CPU. In der Tat ist es durchaus möglich, dass die CPU in der Hälfte des Frames stark und die GPU in der anderen Hälfte stark gebunden ist. Dummes Profiling kann sogar eine Auslastung von weniger als 100% anzeigen. Stellen Sie sicher, dass Ihre Profilerstellung fein genug ist, um dies zu zeigen (aber nicht so fein, dass es aufdringlich ist!)
JasonD

0

Sie sollten Ihren Code optimieren ... so oft Sie möchten.

Was ich in der Vergangenheit getan habe, ist, das Spiel mit aktivierter Profilerstellung ununterbrochen laufen zu lassen (zumindest immer einen Frameraten-Zähler auf dem Bildschirm). Wenn das Spiel langsam wird (z. B. unter Ihrer Zielframerate auf Ihrem Minispezifikationscomputer), schalten Sie den Profiler ein und prüfen Sie, ob Hotspots angezeigt werden.

Manchmal ist es nicht der Code. Viele der Probleme, auf die ich in der Vergangenheit gestoßen bin, waren gpu-orientiert (zugegeben, das war auf dem iPhone). Probleme mit der Füllrate, zu viele Zeichenaufrufe, nicht genügend Geometrie, ineffiziente Shader ...

Abgesehen von ineffizienten Algorithmen für schwierige Probleme (z. B. Pfadfindung, Physik) bin ich nur selten auf Probleme gestoßen, bei denen der Code selbst der Schuldige war. Und diese schwierigen Probleme sollten Dinge sein, mit denen Sie sich viel Mühe geben, um den richtigen Algorithmus zu finden und sich nicht um kleinere Dinge zu kümmern.


0

Für mich ist das am besten vorbereitete Datenmodell zu befolgen. Und die Optimierung - vor dem Hauptschritt nach vorne. Ich meine, bevor ich anfange, etwas Großes Neues zu implementieren. Ein weiterer Grund für die Optimierung ist, wenn ich die Kontrolle über Ressourcen verliere, die App viel CPU-Last / GPU-Last oder Speicher benötigt und ich nicht weiß, warum :) oder es ist zu viel.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.