Tipps zur CPU- und GPU-Optimierung

In diesem Dokument erfahren Sie, wie Sie die Spieleleistung optimieren, indem Sie Tools verwenden, um CPU- und GPU-Engpässe zu identifizieren und zu beheben.

CPU-Optimierung

Wenn die Analyse ergibt, dass das Spiel CPU-gebunden ist, sind weitere Untersuchungen erforderlich. Dazu müssen die spezifischen Threads oder APIs identifiziert werden, die Engpässe verursachen und die FPS reduzieren.

Für die CPU-Optimierung ist eine universelle Lösung in der Regel nicht effektiv. Stattdessen müssen Sie die anspruchsvollste Arbeitslast basierend auf dem Spiel oder der Szene ermitteln und dann die entsprechende Logik und die entsprechenden Funktionen optimieren.

Tools zum Tracing des Timings von Spiel-Engines

Die folgenden Tools können Sie bei dieser Analyse unterstützen:

Unreal-Statistiken

Das Unreal Insight Tool erleichtert die Analyse von Timing-Trace-Informationen für einzelne Threads, aus denen ein Frame besteht.

Zur Veranschaulichung: Der GameThread beansprucht in der Regel den größten Teil der CPU-Zeit, was hauptsächlich auf die Tick Time zurückzuführen ist. Außerdem wird ein erheblicher Teil der Tick Time durch Aufgaben verbraucht, die mit FActorComponentTickFunction verknüpft sind.

Um FActorComponentTick zu optimieren, müssen Berechnungen ausgeschlossen und Culling für Charaktere und Objekte implementiert werden, die sich außerhalb des Sichtfelds der Kamera befinden. Außerdem können Sie die Leistung weiter verbessern, indem Sie LOD-basierte (Level of Detail) Animationen verwenden.

Unreal Insight-Ablaufverfolgungszeitachse mit Ausführungszeiten für GameThread, RenderThread und RHIThread
Unreal-Insight-Trace mit GameThread, RenderThread und RHIThread (zum Vergrößern klicken).

Unity Profiler (Unity)

Die Analyse mit dem Unity Profiler zeigt, dass der Main Thread über 45 ms in Anspruch nimmt.PostLateUpdate.FinishFrameRendering beansprucht 16, 23 ms und ist damit der zeitaufwendigste Vorgang. Dabei werden mehrere Aufrufe von Inl_RenderCameraStack beobachtet. Es ist ratsam, die Notwendigkeit aktivierter Kameras zu prüfen und sie entsprechend zu optimieren.

Unity Profiler-Zeitachse mit dem Hauptthread, der auf Gfx.WaitForPresentOnGfxThread wartet
GPU-gebundenes Beispiel für Unity Profiler (zum Vergrößern klicken).

Profiling-Tools auf Systemebene

Verwenden Sie die folgenden Profiling-Tools:

Perfetto

Mithilfe von Perfetto-Traces können Sie die CPU-Kernzuweisungen und Ausführungsdetails der einzelnen Threads auf einem Android-Gerät ermitteln. So können Sie Leistungsengpässe identifizieren, indem Sie Daten zur Threadausführung analysieren.

CPU-Overhead-Fall

Der Trace zeigt, dass die Arbeitslast im GameThread und RenderThread Verzögerungen in der QueuePresent des RHI-Threads verursacht, was zu einem CPU-gebundenen Szenario basierend auf VSync führt.

Perfetto-Trace mit Ausführungszeiten für GameThread, RenderThread und RHIThread
Perfetto-Traces mit CPU-Ausführungsdetails (zum Vergrößern klicken)

GPU-Overhead-Fall

Der Trace zeigt, dass die GPU-Fertigstellung selbst 25 ms überschreitet, was auf ein GPU-gebundenes Szenario hindeutet.

Perfetto-Trace, der zeigt, dass der GPU-Abschlussblock auf den GPU-Abschluss wartet
Perfetto-Traces mit Details zum GPU-Overhead (zum Vergrößern klicken).

Simpleperf

Mit simpleperf lassen sich die Funktionen mit der höchsten aktuellen CPU-Nutzung ermitteln. Für optimale Ergebnisse empfiehlt es sich, diese Funktionen zu sortieren, um zuerst die mit der höchsten Nutzung zu priorisieren und zu beheben.

Simpleperf-Ausgabe mit Funktionen mit der höchsten CPU-Auslastung
Simpleperf-CPU-Profiling: Analysieren der Hierarchie von Funktionsaufrufen und der Ressourcennutzung (zum Vergrößern klicken)

Mit Simpleperf können Sie Daten zu Funktionen untersuchen, die die meiste CPU-Zeit in Anspruch nehmen. Um die CPU-Nutzung zu optimieren, sollten Sie mit den Funktionen beginnen, die die meiste CPU-Zeit in Anspruch nehmen. In diesem Beispiel wird die meiste CPU von USkeletalMeshComponent verwendet, das mit der Animation in ActorComponentTickFunctions verknüpft ist.

GPU-Optimierung

Wenn die Analyse ergibt, dass das Spiel GPU-gebunden ist, sind weitere Untersuchungen erforderlich. Dazu sind verschiedene Tools und Techniken zur GPU-Optimierung und -Analyse erforderlich.

Um die GPU zu optimieren, verwenden Sie einen Frame-Debugger, um die Rendering-Pipeline und die Draw-Aufrufe für jede Szene zu analysieren. Außerdem müssen Sie die GPU-Architektur und das Pipelineverhalten genau kennen, um unnötige Vorgänge oder Bereiche zu identifizieren, die optimiert werden können.

In den folgenden Abschnitten werden Methoden und Tools zur GPU-Optimierung erläutert.

Unnötige RenderPasses entfernen

Um die Rendering-Leistung zu verbessern und die GPU-Arbeitslast zu reduzieren, sollten Sie unnötige Rendering-Durchläufe entfernen. Dazu gehören alle Render-Passes, denen Draw Calls fehlen oder deren Ausgabe nicht im endgültigen Frame verwendet wird.

Verwenden Sie einen GPU-Debugger wie RenderDoc, um die Rendering-Pipeline zu analysieren und Optimierungsmöglichkeiten zu ermitteln.

  1. Keine Draw Calls:Prüfen Sie, ob der Render-Pass Draw Calls enthält. Wenn keine Draw-Aufrufe vorhanden sind, entfernen Sie die Karte bzw. das Ticket.

  2. Nicht verwendete Ausgabe:Prüfen Sie, ob nachfolgende Durchgänge auf die Ausgaben des Renderdurchgangs zugreifen oder diese anzeigen, z. B. Farbe oder Tiefe. Wenn nicht, entfernen Sie die Karte.

  3. Zusammenführbare Karten/Tickets:Ermitteln Sie Karten/Tickets, die Sie zusammenführen können:

    • Gleicher Framebuffer oder gleiche Anhänge
    • Kompatible Lade- oder Speichervorgänge
    • Keine Abhängigkeitsbarrieren dazwischen
RenderDoc-Ereignisbrowser mit Vulkan-Render-Passes und Draw-Aufrufen
RenderPass- und GPU-Befehlssequenz in RenderDoc (zum Vergrößern klicken)

Lade- oder Speichervorgänge minimieren

Lade- oder Speichervorgänge sind ressourcenintensiv, da sie viel Speicherplatz benötigen. Minimieren Sie unnötige Load-Store-Vorgänge. Führen Sie diese Aktionen nur aus, wenn Anhänge in einem RenderPass erforderlich sind. Andernfalls ersetzen Sie sie durch Clear- oder Don't care-Vorgänge, um den Aufwand zu reduzieren.

Optimieren

Verwenden Sie einen GPU-Debugger wie RenderDoc, um die Rendering-Pipeline zu analysieren und die folgenden Optimierungsmöglichkeiten zu ermitteln:

  1. Laden:Wenn für eine Render-Pass-Anlage keine Daten aus einem vorherigen Pass oder einer vorherigen Anlage verwendet werden, ist kein Ladevorgang erforderlich. In solchen Fällen kann die Verwendung von Don't care oder Clear den Aufwand verringern.

  2. Speichern:Wenn eine Render-Pass-Anlage nach dem aktuellen Render-Pass nicht verwendet wird, ist der Speichervorgang nicht erforderlich. Verwenden Sie in solchen Fällen entweder Don't care oder Clear.

  3. Ersetzen:Ermitteln Sie, ob die aktuellen Einstellungen für das Laden oder Speichern durch Clear oder Don't Care ersetzt werden können, ohne dass sich dies auf den endgültigen Frame auswirkt.

RenderDoc-Ereignisbrowser und Ressourceninspektor, die das Bildlayout und die Rendering-Durchläufe analysieren
RenderDoc-Analyse der Rendering-Pipeline (zum Vergrößern klicken).

Verwerfen vermeiden, um Early-Z zu aktivieren

Early-Z verbessert die Leistung auf mobilen Plattformen. Eine discard-Anweisung in einem Shader deaktiviert jedoch automatisch Early-Z. Wenn die discard-Anweisung nicht unbedingt erforderlich ist, entfernen Sie sie.

Beschleunigung in der Frühphase

Durch diese Optimierung werden die Fragment-Shader-Vorgänge erheblich reduziert und die GPU-Leistung verbessert.

Early-Z Tiefen- und Stencil-Tests

Tabelle mit Leistungsmesswerten für CPU und GPU, wenn Early-Z aktiviert bzw. deaktiviert ist
Auswirkungen der Early-Z-Beschleunigung auf die Leistung (zum Vergrößern klicken).

Optimieren

Verwenden Sie einen GPU-Debugger wie RenderDoc, um die Rendering-Pipeline zu analysieren und die folgenden Optimierungsmöglichkeiten zu ermitteln:

  1. Verwendung von discard in Fragment-Shadern:Das Keyword discard verhindert, dass die GPU vorzeitige Tiefentests durchführt, da die Sichtbarkeit des Fragments nicht im Voraus bekannt ist.

  2. Änderung von gl_FragDepth:Wenn gl_FragDepth dynamisch geändert wird, ändert sich die Tiefe eines Fragments. Dadurch wird die Early-Z-Optimierung deaktiviert, da die endgültige Tiefe vor der Fragmentverarbeitung unbekannt ist.

  3. Alpha-to-Coverage aktiviert:Wenn „Alpha-to-Coverage“ aktiviert ist (wird häufig beim MSAA-Rendering verwendet), hängt die Fragmentabdeckung von den Alphawerten ab. Dies kann die Tiefenprüfung verzögern und Early-Z deaktivieren.

Vergleich der Fragmente pro Pixel mit und ohne das Shader-Keyword „discard“
RenderDoc-GPU-Debugger für die Analyse (zum Vergrößern klicken).

Texturformat optimieren

Durch die optimale Auswahl des Texturformats wird der Arbeitsspeicherverbrauch reduziert, die Bandbreiteneffizienz gesteigert und die Rendering-Leistung verbessert. Durch die Verwendung von Formaten mit übermäßig hoher Präzision können GPU-Ressourcen verschwendet werden, ohne dass dies visuelle Vorteile bringt.

Optimieren

Verwenden Sie einen GPU-Debugger wie RenderDoc, um die Rendering-Pipeline zu analysieren und die folgenden Optimierungsmöglichkeiten zu ermitteln:

  1. D24S8 anstelle von D32S8 für Depth-Stencil-Puffer verwenden:Die Verwendung von D24S8 für Depth-Stencil-Puffer reduziert den Speicherverbrauch im Vergleich zu D32S8 um 20 %. Bei den meisten Anwendungen ist der Unterschied in der visuellen Qualität kaum oder gar nicht wahrnehmbar.
  2. ASTC-Komprimierung für Farbtexturen verwenden:Die ASTC-Komprimierung reduziert die Speichernutzung für Texturen erheblich – um das bis zu Achtfache im Vergleich zu unkomprimierten Formaten – und sorgt gleichzeitig für eine hohe visuelle Qualität.
  3. Halbfließkomma-Formate anstelle von Fließkomma-Formaten verwenden:Verwenden Sie R16F oder RG16F, um die Speicherbandbreite und den Speicherverbrauch zu reduzieren. Diese Formate eignen sich gut für die Nachbearbeitung von Puffern.

Geometrie optimieren

Durch die Minimierung der geometrischen Komplexität wird die Rendering-Leistung verbessert, insbesondere auf Mobilgeräten mit begrenzten GPU-Funktionen. Dazu gehören die Verwendung einer geringeren Anzahl von Eckpunkten und Dreiecken, das Zusammenfassen von Objekten, um die Anzahl der Zeichenaufrufe zu verringern, und das Entfernen von nicht gerenderter oder unnötiger Geometrie. Techniken wie die Vereinfachung von Meshs, LOD (Level of Detail) und das Entfernen von Objekten, die sich außerhalb des Sichtfelds befinden oder verdeckt sind, können die GPU-Arbeitslast erheblich reduzieren und die Framerate erhöhen.

Optimieren

Verwenden Sie Profilerstellungstools und GPU-Debugger wie RenderDoc, Android GPU Inspector oder andere Leistungsanalysetools, um leistungsbezogene Engpässe im Zusammenhang mit der Geometrie zu identifizieren.

  1. Anzahl der Dreiecke reduzieren:Verwenden Sie möglichst wenige Polygone, insbesondere für kleine oder weit entfernte Objekte.

  2. Detaillierungsgrad (Level of Detail, LOD) verwenden: Je nach Entfernung der Kamera werden automatisch einfachere Meshes verwendet.

  3. Kleine Meshes zusammenführen:Statische Objekte konsolidieren, um die Anzahl der Draw Calls und den CPU-Overhead zu verringern.

  4. Frustum- und Occlusion Culling:Vermeiden Sie das Rendern von Objekten, die sich außerhalb des Sichtfelds befinden oder durch andere Elemente verdeckt werden.

Unnötige Anhänge entfernen

Render-Pass-Anhänge (z. B. Farbe, Tiefe, Schablone) verbrauchen Arbeitsspeicherbandbreite und GPU-Ressourcen, auch wenn sie nicht verwendet werden. Wenn Sie unnötige oder redundante Anhänge entfernen, wird die Leistung verbessert und der Stromverbrauch gesenkt, insbesondere auf mobilen Plattformen.

Optimieren

Verwenden Sie Profilerstellungstools und GPU-Debugger wie RenderDoc, Android GPU Inspector oder andere Leistungsanalysetools, um leistungsbezogene Engpässe im Zusammenhang mit der Geometrie zu identifizieren.

  1. Tatsächliche Nutzung prüfen:Gibt es Draw-Aufrufe oder Shader, die in das oder aus dem Attachment schreiben oder lesen?
  2. Frame-Ausgabe analysieren:Verwenden Sie RenderDoc oder vergleichbare Tools, um festzustellen, ob der Anhang zum endgültigen Bild beiträgt.
  3. Temporäre oder Dummy-Anhänge verwenden:Temporäre Anhänge oder ein „Don’t Care“-Speichervorgang sollten für temporäre Daten verwendet werden, die keine dauerhafte Speicherung erfordern.

Shader-Präzision optimieren

Wenn Sie in Shadern eine zu hohe Genauigkeit verwenden (z. B. highp anstelle von mediump oder lowp), erhöht sich die GPU-Arbeitslast, der Stromverbrauch und der Registerdruck, insbesondere bei mobilen GPUs. Wenn Sie für Variablen (z. B. Positionen, Farben, UVs) die niedrigste angemessene Genauigkeit verwenden, können Sie die Leistung verbessern, ohne dass dies visuell erkennbar ist.

Tabelle zum Vergleichen von CPU- und GPU-Leistungsmesswerten bei Verwendung von „mediump“ im Vergleich zu „highp“ für die Shader-Präzision
Leistungsauswirkungen der Shader-Präzision (zum Vergrößern klicken).

Optimieren

Verwenden Sie Profilerstellungstools und GPU-Debugger wie RenderDoc, Android GPU Inspector oder andere Leistungsanalysetools, um leistungsbezogene Engpässe im Zusammenhang mit der Geometrie zu ermitteln.

  1. Shader-Code prüfen:Untersuchen Sie die Shader-Variablen und prüfen Sie, ob hohe Präzision nur bei Bedarf verwendet wird, z. B. für Berechnungen für Tiefe oder Bildschirmkoordinaten. Verwenden Sie eine mittlere oder niedrige Präzision für Farben, UV-Koordinaten oder Werte, die keine hohe Präzision erfordern.

  2. GPU-Debugger verwenden:Mit Diagnosetools wie RenderDoc oder Profilern für mobile GPUs (z. B. AGI, Mali/GPU Inspector) lässt sich eine erhöhte Registernutzung oder Shader-Stalls erkennen, die mit Problemen bei der Genauigkeit zusammenhängen.

Mali Varying Usage-Profiler mit 16-Bit-Interpolation neben Shader-Code mit „mediump“
Beispiel für Profiling-Tools und GPU-Debugger (zum Vergrößern klicken).

Backface Culling aktivieren

Das Rendern von Dreiecken, die von der Kamera abgewandt sind (Rückseiten), ist für feste Objekte oft unnötig.

Optimieren

Die Verwendung von VK_CULL_MODE_NONE kann sich negativ auf die Leistung auswirken, da die GPU sowohl Vorder- als auch Rückseiten rendern muss, was den Rendering-Aufwand erhöht.

Vulkan-Befehlsprotokoll mit vkCmdSetCullMode, das auf VK_CULL_MODE_NONE festgelegt ist
Debug-Logs mit Backface Culling (zum Vergrößern klicken)

Überschneidung in UI-Szenen minimieren

Entfernen Sie unnötige Draw-Aufrufe und Render-Passes, insbesondere in UI-Szenen, um die Rendering-Leistung zu verbessern und die GPU-Arbeitslast zu reduzieren. In einer UI-Szene, in der die gesamte Welt gerendert wird, bevor die UI auf dem Bildschirm überlagert wird, ist das Rendern der Welt beispielsweise überflüssig.

Optimieren

Verwenden Sie einen GPU-Debugger wie RenderDoc, um die Rendering-Pipeline zu analysieren und die folgenden Optimierungsmöglichkeiten zu ermitteln:

  1. Prüfen Sie, ob es unnötige Overdraws gibt. Prüfen Sie in Benutzeroberflächenkontexten, in denen der gesamte Bildschirm gerendert wird, ob vorherige Rendering-Durchläufe nicht unnötig überzeichnet werden.
  2. Aktivieren Sie Tiefentests und das Entfernen von verdeckten Polygonen, um die Leistung zu optimieren.
  3. Erwägen Sie, die Reihenfolge der Darstellung von vorn nach hinten zu ändern.
RenderDoc-Ereignisbrowser und Textur-Viewer, in denen ein unnötiger Overdraw-Rendering-Pass identifiziert wird
Beispiel zum Eliminieren überflüssiger Draw-Aufrufe und Render-Durchläufe (zum Vergrößern klicken).