
Leistungsprofilierungstipps für Spieleentwickler
Eine reibungslose Leistung ist entscheidend für die Schaffung immersiver Spielerlebnisse. Durch die Profilierung und Verfeinerung der Leistung Ihres Spiels für eine breite Palette von Plattformen und Geräten können Sie Ihre Spielerbasis erweitern und Ihre Erfolgschancen erhöhen.
Diese Seite skizziert einen allgemeinen Profilierungsworkflow für Spieleentwickler. Es ist ein Auszug aus dem E-Book, Ultimative Anleitung zur Profilierung von Unity-Spielen, das kostenlos heruntergeladen werden kann. Das E-Book wurde sowohl von externen als auch von internen Unity-Experten in den Bereichen Spieleentwicklung, Profilierung und Optimierung erstellt.
Lesen Sie weiter, um nützliche Ziele für die Profilierung, häufige Leistungsengpässe wie CPU- oder GPU-Bindung und wie Sie diese Situationen genauer identifizieren und untersuchen können, zu erfahren.

Setzen Sie ein Frame-Budget
Die Messung der Bildrate Ihres Spiels in Bildern pro Sekunde (fps) ist nicht ideal, um konsistente Erlebnisse für Ihre Spieler zu liefern. Betrachten Sie das folgende vereinfachte Szenario:
Während der Laufzeit rendert Ihr Spiel 59 Frames in 0,75 Sekunden. Das nächste Frame benötigt jedoch 0,25 Sekunden zum Rendern. Die durchschnittliche Bildrate von 60 fps klingt gut, aber in Wirklichkeit werden die Spieler einen Ruckeleffekt bemerken, da das letzte Frame ein Viertel einer Sekunde zum Rendern benötigt.
Dies ist einer der Gründe, warum es wichtig ist, ein spezifisches Zeitbudget pro Frame anzustreben. Dies gibt Ihnen ein solides Ziel, auf das Sie hinarbeiten können, wenn Sie Ihr Spiel profilieren und optimieren, und letztendlich schafft es ein reibungsloseres und konsistenteres Erlebnis für Ihre Spieler.
Jeder Frame hat ein Zeitbudget basierend auf Ihrer Ziel-FPS. Eine Anwendung, die auf 30 FPS abzielt, sollte immer weniger als 33,33 ms pro Frame benötigen (1000 ms / 30 FPS). Ebenso lässt ein Ziel von 60 FPS 16,66 ms pro Frame (1000 ms / 60 FPS) übrig.
Sie können dieses Budget während nicht-interaktiver Sequenzen überschreiten, zum Beispiel beim Anzeigen von UI-Menüs oder beim Laden von Szenen, jedoch nicht während des Spiels. Selbst ein einzelner Frame, der das Ziel-Frame-Budget überschreitet, verursacht Ruckler.
Hinweis: Eine konstant hohe Bildrate in VR-Spielen ist entscheidend, um Übelkeit oder Unbehagen bei den Spielern zu vermeiden. Ohne dies riskieren Sie, während der Zertifizierung Ihres Spiels von der Plattforminhaber abgelehnt zu werden.
Bilder pro Sekunde: Eine irreführende Kennzahl
Eine gängige Methode, wie Gamer die Leistung messen, ist die Bildrate oder Bilder pro Sekunde. Es wird jedoch empfohlen, stattdessen die Frame-Zeit in Millisekunden zu verwenden. Um zu verstehen, warum, schauen Sie sich das obige Diagramm von FPS versus Frame-Zeit an.
Betrachten Sie diese Zahlen:
1000 ms/sec / 900 FPS = 1,111 ms pro Frame
1000 ms/sec / 450 FPS = 2,222 ms pro Frame
1000 ms/sec / 60 FPS = 16,666 ms pro Frame
1000 ms/sec / 56,25 FPS = 17,777 ms pro Frame
Wenn Ihre Anwendung mit 900 FPS läuft, entspricht das einer Bildzeit von 1,111 Millisekunden pro Bild. Bei 450 FPS sind das 2,222 Millisekunden pro Bild. Das stellt einen Unterschied von nur 1,111 Millisekunden pro Bild dar, obwohl die Bildrate um die Hälfte zu sinken scheint.
Wenn Sie die Unterschiede zwischen 60 FPS und 56,25 FPS betrachten, entspricht das 16,666 Millisekunden pro Bild und 17,777 Millisekunden pro Bild, jeweils. Das stellt ebenfalls 1,111 Millisekunden zusätzlich pro Bild dar, aber hier fühlt sich der Rückgang der Bildrate prozentual viel weniger dramatisch an.
Deshalb verwenden Entwickler die durchschnittliche Bildzeit, um die Spielgeschwindigkeit zu benchmarken, anstatt FPS.
Machen Sie sich keine Sorgen um FPS, es sei denn, Sie fallen unter Ihre Zielbildrate. Konzentrieren Sie sich auf die Bildzeit, um zu messen, wie schnell Ihr Spiel läuft, und bleiben Sie dann innerhalb Ihres Bildbudgets.
Lesen Sie den Originalartikel „Robert Dunlops FPS versus Bildzeit“ für weitere Informationen.

Herausforderungen im mobilen Bereich
Thermische Kontrolle ist eines der wichtigsten Bereiche, die bei der Entwicklung von Anwendungen für mobile Geräte optimiert werden müssen. Wenn die CPU oder GPU zu lange mit voller Leistung arbeiten, aufgrund ineffizienter Designs, werden diese Chips heiß. Um Schäden an den Chips (und möglicherweise das Verbrennen der Hände eines Spielers!) zu vermeiden, wird das Betriebssystem die Taktrate des Geräts reduzieren, um es abkühlen zu lassen, was zu Bildstottern und einer schlechten Benutzererfahrung führt. Diese Leistungsreduzierung ist als thermisches Drosseln bekannt.
Höhere Bildraten und erhöhte Codeausführung (oder DRAM-Zugriffsoperationen) führen zu einem erhöhten Batterieverbrauch und Wärmeentwicklung. Schlechte Leistung kann auch ganze Segmente von niedrigeren mobilen Geräten ausschließen, was zu verpassten Marktchancen und damit zu geringeren Verkaufszahlen führen kann.
Wenn Sie sich mit dem Problem der Wärmeentwicklung befassen, berücksichtigen Sie das Budget, mit dem Sie als systemweites Budget arbeiten können.
Bekämpfen Sie thermisches Drosseln und Batterieverbrauch, indem Sie eine frühe Profilierungstechnik nutzen, um Ihr Spiel von Anfang an zu optimieren. Stellen Sie Ihre Projekteinstellungen für die Hardware Ihrer Zielplattform ein, um thermischen Problemen und Batterieverbrauch entgegenzuwirken.
Passen Sie die Bildbudgets auf mobilen Geräten an
Eine Leerlaufzeit von etwa 35 % pro Bild ist die typische Empfehlung, um thermische Probleme bei Geräten über längere Spielzeiten zu bekämpfen. Dies gibt mobilen Chips Zeit, sich abzukühlen, und hilft, übermäßigen Batterieverbrauch zu verhindern. Bei einer Zielbildzeit von 33,33 ms pro Bild (für 30 fps) beträgt das typische Bildbudget für mobile Geräte ungefähr 22 ms pro Bild.
Die Berechnung sieht folgendermaßen aus: (1000 ms / 30) * 0.65 = 21.66 ms
Um 60 fps auf mobilen Geräten mit derselben Berechnung zu erreichen, wäre eine Zielbildzeit von (1000 ms / 60) * 0.65 = 10.83 ms erforderlich. Dies ist auf vielen mobilen Geräten schwierig zu erreichen und würde die Batterie doppelt so schnell entladen wie das Anvisieren von 30 fps. Aus diesen Gründen zielen die meisten mobilen Spiele auf 30 fps statt auf 60 ab. Verwenden Sie Application.targetFrameRate, um diese Einstellung zu steuern, und beziehen Sie sich auf den Abschnitt „Setzen Sie ein Bildbudget“ im E-Book für weitere Details zur Bildzeit.
Die Frequenzskalierung auf mobilen Chips kann es schwierig machen, Ihre Budgetzuweisungen für die Bild-Leerlaufzeit beim Profiling zu identifizieren. Ihre Verbesserungen und Optimierungen können einen netto positiven Effekt haben, aber das mobile Gerät könnte die Frequenz herunterskalieren und dadurch kühler laufen. Verwenden Sie benutzerdefinierte Werkzeuge wie FTrace oder Perfetto, um die Frequenzen mobiler Chips, Leerlaufzeiten und Skalierungen vor und nach Optimierungen zu überwachen.
Solange Sie innerhalb Ihres gesamten Bildzeitbudgets für Ihre Ziel-fps (33,33 ms für 30 fps) bleiben und sehen, dass Ihr Gerät weniger arbeitet oder niedrigere Temperaturen aufzeichnet, um diese Bildrate aufrechtzuerhalten, sind Sie auf dem richtigen Weg.
Ein weiterer Grund, um Spielraum im Bildbudget auf mobilen Geräten zu schaffen, ist, um reale Temperaturschwankungen zu berücksichtigen. An einem heißen Tag wird ein mobiles Gerät heiß und hat Schwierigkeiten, Wärme abzuführen, was zu thermischer Drosselung und schlechter Spielleistung führen kann. Einen Prozentsatz des Bildbudgets beiseite zu legen, hilft, solche Szenarien zu vermeiden.

Reduzieren Sie Speicherzugriffsoperationen
Der Zugriff auf DRAM ist typischerweise ein energiehungriger Vorgang auf mobilen Geräten. Arms Optimierungsratgeber für Grafikinhalt auf mobilen Geräten besagt, dass der Zugriff auf LPDDR4-Speicher ungefähr 100 Pikojoule pro Byte kostet.
Reduzieren Sie die Anzahl der Speicherzugriffsoperationen pro Bild durch:
- Reduzierung der Bildrate
- Reduzierung der Bildschirmauflösung, wo möglich
- Verwendung einfacher Netze mit reduziertem Vertex-Zähler und Attributgenauigkeit
- Verwendung von Texturkompression und Mipmapping
Wenn Sie sich auf Geräte konzentrieren müssen, die Arm- oder Arm Mali-Hardware nutzen, enthält das Arm Mobile Studio-Tool (insbesondere der Streamline Performance Analyzer) einige großartige Leistungszähler zur Identifizierung von Speicherbandbreitenproblemen. Die Zähler sind für jede Arm-GPU-Generation aufgelistet und erklärt, zum Beispiel Mali-G78. Beachten Sie, dass das GPU-Profiling im Mobile Studio Arm Mali erfordert.
Hardware-Tiers für Benchmarking festlegen
Zusätzlich zur Verwendung plattformspezifischer Profiling-Tools sollten Sie Tiers oder ein Gerät mit den niedrigsten Spezifikationen für jede Plattform und Qualitätsstufe, die Sie unterstützen möchten, festlegen und dann die Leistung für jede dieser Spezifikationen profilieren und optimieren.
Als Beispiel, wenn Sie mobile Plattformen anvisieren, könnten Sie entscheiden, drei Tiers mit Qualitätskontrollen zu unterstützen, die Funktionen basierend auf der Zielhardware ein- oder ausschalten. Sie optimieren dann für die niedrigste Gerätespezifikation in jedem Tier. Als weiteres Beispiel, wenn Sie ein Spiel sowohl für PlayStation 4 als auch für PlayStation 5 entwickeln, stellen Sie sicher, dass Sie auf beiden profilieren.
Für einen vollständigen Leitfaden zur mobilen Optimierung werfen Sie einen Blick auf Optimieren Sie die Leistung Ihres mobilen Spiels. Dieses E-Book enthält viele Tipps und Tricks, die Ihnen helfen, die thermische Drosselung zu reduzieren und die Akkulaufzeit für mobile Geräte, die Ihre Spiele ausführen, zu erhöhen.
Von der Hoch- zur Niedrigprofilierung
Ein Top-to-Bottom-Ansatz funktioniert gut beim Profiling, beginnend mit deaktiviertem Deep Profiling. Verwenden Sie diesen hochrangigen Ansatz, um Daten zu sammeln und Notizen zu machen, welche Szenarien unerwünschte verwaltete Zuweisungen oder zu viel CPU-Zeit in den Kernbereichen Ihrer Spielschleife verursachen.
Sie müssen zuerst die Aufrufstapel für GC.Alloc-Marker sammeln. Wenn Sie mit diesem Prozess nicht vertraut sind, finden Sie einige Tipps und Tricks im Abschnitt „Wiederkehrende Speicherzuweisungen über die Anwendungslebensdauer lokalisieren“ in Ultimativer Leitfaden zum Profiling von Unity-Spielen.
Wenn die gemeldeten Aufrufstapel nicht detailliert genug sind, um die Quelle der Zuweisungen oder anderer Verlangsamungen zu verfolgen, können Sie eine zweite Profiling-Sitzung mit aktiviertem Deep Profiling durchführen, um die Quelle der Zuweisungen zu finden.
Wenn Sie Notizen zu den „Übeltätern“ der Frame-Zeit sammeln, stellen Sie sicher, dass Sie notieren, wie sie im Vergleich zum Rest des Frames abschneiden. Dieser relative Einfluss wird durch das Aktivieren von Deep Profiling beeinflusst.
Lesen Sie mehr über Deep Profiling in Ultimativer Leitfaden zum Profiling von Unity-Spielen.
Profil früh
Die besten Ergebnisse beim Profiling werden erzielt, wenn Sie frühzeitig im Entwicklungszyklus Ihres Projekts beginnen.
Profilieren Sie früh und oft, damit Sie und Ihr Team ein „Leistungsprofil“ für das Projekt verstehen und verinnerlichen. Wenn die Leistung einbricht, können Sie leicht erkennen, wann etwas schiefgeht und das Problem beheben.
Die genauesten Profiling-Ergebnisse stammen immer von der Ausführung und dem Profiling von Builds auf Zielgeräten, zusammen mit der Nutzung plattformspezifischer Werkzeuge, um die Hardwaremerkmale jeder Plattform zu analysieren. Diese Kombination bietet Ihnen einen ganzheitlichen Überblick über die Anwendungsleistung auf all Ihren Zielgeräten.

Identifizieren von Performance-Problemen
Laden Sie die druckbare PDF-Version dieses Diagramms hier herunter.
Auf einigen Plattformen ist es einfach zu bestimmen, ob Ihre Anwendung CPU- oder GPU-gebunden ist. Wenn Sie beispielsweise ein iOS-Spiel aus Xcode ausführen, zeigt das FPS-Panel ein Balkendiagramm mit der gesamten CPU- und GPU-Zeit, sodass Sie sehen können, welches höher ist. Die CPU-Zeit umfasst die Zeit, die mit Warten auf VSync verbracht wird, das auf mobilen Geräten immer aktiviert ist.
Auf einigen Plattformen kann es jedoch schwierig sein, GPU-Zeitdaten zu erhalten. Glücklicherweise zeigt der Unity Profiler genügend Informationen, um die Standorte von Leistungsengpässen zu identifizieren. Das obige Flussdiagramm veranschaulicht den anfänglichen Profiling-Prozess, wobei die folgenden Abschnitte detaillierte Informationen zu jedem Schritt bieten. Sie präsentieren auch Profiler-Aufnahmen aus echten Unity-Projekten, um die Arten von Dingen zu veranschaulichen, auf die man achten sollte.
Um ein ganzheitliches Bild aller CPU-Aktivitäten zu erhalten, einschließlich der Zeiten, in denen sie auf die GPU wartet, verwenden Sie die Zeitachsenansicht im CPU-Modul des Profilers. Machen Sie sich mit den üblichen Profiler-Markern vertraut, um Aufnahmen korrekt zu interpretieren. Einige der Profiler-Markierungen können je nach Zielplattform unterschiedlich erscheinen, daher sollten Sie Zeit damit verbringen, Aufnahmen Ihres Spiels auf jeder Ihrer Zielplattformen zu erkunden, um ein Gefühl dafür zu bekommen, wie eine „normale“ Aufnahme für Ihr Projekt aussieht.
Die Leistung eines Projekts wird durch den Chip und/oder den Thread begrenzt, der am längsten dauert. Das ist der Bereich, auf den Sie Ihre Optimierungsanstrengungen konzentrieren sollten. Stellen Sie sich beispielsweise ein Spiel mit einem Ziel-Frame-Zeitbudget von 33,33 ms und aktiviertem VSync vor:
- Wenn die CPU-Bildzeit (ohne VSync) 25 ms beträgt und die GPU-Zeit 20 ms beträgt, kein Problem! Sie sind CPU-gebunden, aber alles liegt im Budget, und Optimierungen werden die Bildrate nicht verbessern (es sei denn, Sie bringen sowohl CPU als auch GPU unter 16,66 ms und springen auf 60 fps).
- Wenn die CPU-Bildzeit 40 ms beträgt und die GPU 20 ms beträgt, sind Sie CPU-gebunden und müssen die CPU-Leistung optimieren. Die Optimierung der GPU-Leistung wird nicht helfen; tatsächlich möchten Sie möglicherweise einige der CPU-Arbeiten auf die GPU verlagern, indem Sie beispielsweise Compute-Shader anstelle von C#-Code für einige Dinge verwenden, um die Dinge auszugleichen.
- Wenn die CPU-Bildzeit 20 ms beträgt und die GPU 40 ms beträgt, sind Sie GPU-gebunden und müssen die GPU-Arbeiten optimieren.
- Wenn CPU und GPU beide bei 40 ms liegen, sind Sie durch beide gebunden und müssen beide unter 33,33 ms optimieren, um 30 fps zu erreichen.
Sehen Sie sich diese Ressourcen an, die das CPU- oder GPU-Binding weiter erkunden:

Sind Sie innerhalb der Frame-Planung?
Das Profilieren und Optimieren Ihres Projekts frühzeitig und häufig während der Entwicklung hilft Ihnen sicherzustellen, dass alle CPU-Threads Ihrer Anwendung und die gesamte GPU-Bildzeit innerhalb des Bildbudgets liegen.
Oben ist ein Bild eines Profiler-Captures aus einem Unity-Mobilspiel, das von einem Team entwickelt wurde, das kontinuierliches Profiling und Optimierung durchgeführt hat. Das Spiel zielt auf 60 fps auf Hochleistungsmobiltelefonen und 30 fps auf mittel- / niedrigleistungsfähigen Telefonen, wie dem in diesem Capture.
Beachten Sie, wie fast die Hälfte der Zeit im ausgewählten Frame von dem gelben WaitForTargetfps-Profiler-Marker belegt ist. Die Anwendung hat Application.targetFrameRate auf 30 fps gesetzt, und VSync ist aktiviert. Die tatsächliche Verarbeitungsarbeit im Hauptthread endet bei etwa 19 ms, und die restliche Zeit wird damit verbracht, auf den Rest der 33,33 ms zu warten, bevor der nächste Frame beginnt. Obwohl diese Zeit mit einem Profiler-Marker dargestellt wird, ist der Haupt-CPU-Thread während dieser Zeit im Wesentlichen untätig, was es der CPU ermöglicht, sich abzukühlen und eine minimale Batterieleistung zu verwenden.
Der Marker, auf den Sie achten sollten, könnte auf anderen Plattformen oder wenn VSync deaktiviert ist, unterschiedlich sein. Das Wichtige ist zu überprüfen, ob der Hauptthread innerhalb Ihres Bildbudgets oder genau auf Ihrem Bildbudget läuft, mit einer Art Marker, der anzeigt, dass die Anwendung auf VSync wartet und ob die anderen Threads eine Leerlaufzeit haben.
Leerlaufzeit wird durch graue oder gelbe Profiler-Marker dargestellt. Der Screenshot oben zeigt, dass der Render-Thread in Gfx.WaitForGfxCommandsFromMainThread untätig ist, was Zeiten anzeigt, in denen er das Senden von Draw Calls an die GPU in einem Frame abgeschlossen hat und auf weitere Draw Call-Anfragen von der CPU im nächsten wartet. Ähnlich, obwohl der Job Worker 0-Thread etwas Zeit in Canvas.GeometryJob verbringt, ist er die meiste Zeit im Leerlauf. Dies sind alles Anzeichen für eine Anwendung, die bequem innerhalb des Frame-Budgets liegt.
Wenn Ihr Spiel im Frame-Budget liegt
Wenn Sie innerhalb des Frame-Budgets sind, einschließlich aller Anpassungen, die am Budget vorgenommen wurden, um den Batterieverbrauch und das thermische Drosseln zu berücksichtigen, haben Sie die Leistungsprofilierung bis zum nächsten Mal abgeschlossen – herzlichen Glückwunsch. Erwägen Sie, den Speicherprofiler auszuführen, um sicherzustellen, dass die Anwendung auch innerhalb ihres Speicherbudgets liegt.
Das obige Bild zeigt ein Spiel, das bequem innerhalb des ~22 ms Frame-Budgets läuft, das für 30 fps erforderlich ist. Beachten Sie das Warten auf Targetfps, das die Haupt-Thread-Zeit bis VSync und die grauen Leerlaufzeiten im Render-Thread und im Worker-Thread polstert. Beachten Sie auch, dass das VBlank-Intervall beobachtet werden kann, indem man sich die Endzeiten von Gfx.Present Frame für Frame ansieht, und dass Sie eine Zeitskala im Timeline-Bereich oder auf dem Zeitmaßstab oben zeichnen können, um von einem dieser Zeitpunkte zum nächsten zu messen.

CPU-gebunden
Wenn Ihr Spiel nicht innerhalb des CPU-Frame-Budgets liegt, besteht der nächste Schritt darin, zu untersuchen, welcher Teil der CPU der Engpass ist – mit anderen Worten, welcher Thread am meisten beschäftigt ist. Der Zweck der Profilierung besteht darin, Engpässe als Ziele für die Optimierung zu identifizieren; wenn Sie auf Vermutungen angewiesen sind, können Sie Teile des Spiels optimieren, die keine Engpässe sind, was zu wenig oder keiner Verbesserung der Gesamtleistung führt. Einige "Optimierungen" könnten sogar die Gesamtleistung Ihres Spiels verschlechtern.
Es ist selten, dass die gesamte CPU-Last der Engpass ist. Moderne CPUs haben eine Reihe von verschiedenen Kernen, die in der Lage sind, unabhängig und gleichzeitig zu arbeiten. Verschiedene Threads können auf jedem CPU-Kern ausgeführt werden. Eine vollständige Unity-Anwendung verwendet eine Reihe von Threads für verschiedene Zwecke, aber die Threads, die am häufigsten zur Auffindung von Leistungsproblemen verwendet werden, sind:
- Der Haupt-Thread: Hier führen standardmäßig alle Spiel-Logik/Skripte ihre Arbeit aus und hier wird die meiste Zeit für Funktionen und Systeme wie Physik, Animation, UI und Rendering aufgewendet.
- Der Render-Thread: Während des Renderprozesses untersucht der Haupt-Thread die Szene und führt Kamera-Culling, Tiefensortierung und Draw-Call-Batching durch, was zu einer Liste von Dingen führt, die gerendert werden sollen. Diese Liste wird an den Render-Thread übergeben, der sie von Unitys interner plattformunabhängiger Darstellung in die spezifischen Grafik-API-Aufrufe übersetzt, die erforderlich sind, um die GPU auf einer bestimmten Plattform zu instruieren.
- Die Job-Arbeiter-Threads: Entwickler können das C# Job-System nutzen, um bestimmte Arten von Arbeiten auf Arbeiter-Threads zu planen, was die Arbeitslast des Haupt-Threads reduziert. Einige von Unitys Systemen und Funktionen nutzen ebenfalls das Job-System, wie Physik, Animation und Rendering.
Haupt-Thread
Das obige Bild zeigt, wie die Dinge in einem Projekt aussehen könnten, das durch den Haupt-Thread gebunden ist. Dieses Projekt läuft auf einem Meta Quest 2, das normalerweise Frame-Budgets von 13,88 ms (72 fps) oder sogar 8,33 ms (120 fps) anvisiert, da hohe Bildraten wichtig sind, um Übelkeit in VR-Geräten zu vermeiden. Es ist jedoch klar, dass dieses Projekt in Schwierigkeiten ist, selbst wenn dieses Spiel 30 fps anvisiert.
Obwohl der Render-Thread und die Arbeiter-Threads dem Beispiel ähneln, das innerhalb des Frame-Budgets liegt, ist der Haupt-Thread während des gesamten Frames eindeutig mit Arbeit beschäftigt. Selbst unter Berücksichtigung der geringen Menge an Profiler-Overhead am Ende des Frames ist der Haupt-Thread über 45 ms beschäftigt, was bedeutet, dass dieses Projekt Bildraten von weniger als 22 fps erreicht. Es gibt keinen Marker, der zeigt, dass der Haupt-Thread untätig auf VSync wartet; er ist während des gesamten Frames beschäftigt.
Die nächste Phase der Untersuchung besteht darin, die Teile des Frames zu identifizieren, die die längste Zeit in Anspruch nehmen, und zu verstehen, warum das so ist. In diesem Frame benötigt PostLateUpdate.FinishFrameRendering 16,23 ms, mehr als das gesamte Frame-Budget. Eine genauere Untersuchung zeigt, dass es fünf Instanzen eines Markers namens Inl_RenderCameraStack gibt, was darauf hinweist, dass fünf Kameras aktiv sind und die Szene rendern. Da jede Kamera in Unity die gesamte Render-Pipeline aufruft, einschließlich Culling, Sortierung und Batching, besteht die höchste Priorität für dieses Projekt darin, die Anzahl der aktiven Kameras idealerweise auf nur eine zu reduzieren.
BehaviourUpdate, der Marker, der alle MonoBehaviour Update()-Methoden umfasst, benötigt 7,27 ms, und die magentafarbenen Abschnitte der Zeitachse zeigen, wo Skripte verwalteten Heap-Speicher zuweisen. Der Wechsel zur Hierarchieansicht und das Filtern durch Eingabe von GC.Alloc in die Suchleiste zeigt, dass die Zuweisung dieses Speichers in diesem Frame etwa 0,33 ms dauert. Das ist jedoch eine ungenaue Messung der Auswirkungen, die die Speicherzuweisungen auf die CPU-Leistung haben.
GC.Alloc Marker werden tatsächlich nicht gemessen, indem die Zeit von einem Begin- zu einem Endpunkt gemessen wird. Um ihren Overhead gering zu halten, werden sie nur als ihr Begin-Zeitstempel aufgezeichnet, plus die Größe ihrer Zuweisung. Der Profiler schreibt ihnen eine minimale Zeit zu, um sicherzustellen, dass sie sichtbar sind. Die tatsächliche Zuweisung kann länger dauern, insbesondere wenn ein neuer Speicherbereich vom System angefordert werden muss. Um die Auswirkungen klarer zu sehen, platzieren Sie Profiler-Markierungen um den Code, der die Zuweisung vornimmt, und im tiefen Profiling geben die Lücken zwischen den magentafarbenen GC.Alloc-Proben in der Timeline-Ansicht einen Hinweis darauf, wie lange sie möglicherweise gedauert haben.
Darüber hinaus kann die Zuweisung neuer Speicher negative Auswirkungen auf die Leistung haben, die schwerer zu messen und direkt zuzuordnen sind:
- Die Anforderung neuer Speicher vom System kann das Energiebudget auf einem mobilen Gerät beeinflussen, was dazu führen könnte, dass das System die CPU oder GPU verlangsamt.
- Der neue Speicher muss wahrscheinlich in den L1-Cache der CPU geladen werden und verdrängt damit vorhandene Cache-Zeilen.
- Inkrementelle oder synchrone Garbage Collection kann direkt oder mit einer Verzögerung ausgelöst werden, wenn der vorhandene freie Speicherplatz im verwalteten Speicher schließlich überschritten wird.
Zu Beginn des Frames summieren sich vier Instanzen von Physics.FixedUpdate auf 4,57 ms. Später benötigen LateBehaviourUpdate (Aufrufe von MonoBehaviour.LateUpdate()) 4 ms, und Animator benötigen etwa 1 ms.
Um sicherzustellen, dass dieses Projekt sein gewünschtes Frame-Budget und seine Rate erreicht, müssen alle diese Probleme des Hauptthreads untersucht werden, um geeignete Optimierungen zu finden. Die größten Leistungsgewinne werden durch die Optimierung der Dinge erzielt, die am längsten dauern.
Die folgenden Bereiche sind oft fruchtbare Stellen, um in Projekten, die an den Hauptthread gebunden sind, nach Optimierungen zu suchen:
- Physik
- MonoBehaviour-Skriptaktualisierungen
- Garbage-Zuweisung und/oder -Sammlung
- Kameraculling und Rendering
- Schlechte Draw-Call-Batching
- UI-Aktualisierungen, Layouts und Neuaufbauten
- Animation
Je nach dem Problem, das Sie untersuchen möchten, können auch andere Tools hilfreich sein:
- Für MonoBehaviour-Skripte, die lange dauern, aber nicht genau zeigen, warum das der Fall ist, fügen Sie Profiler-Markierungen zum Code hinzu oder versuchen Sie tiefes Profiling, um den vollständigen Aufrufstapel zu sehen.
- Für Skripte, die verwalteten Speicher zuweisen, aktivieren Sie Zuweisungsaufrufstapel, um genau zu sehen, woher die Zuweisungen kommen. Alternativ aktivieren Sie Deep Profiling oder verwenden Sie Project Auditor, das Codeprobleme nach Speicher gefiltert anzeigt, damit Sie alle Codezeilen identifizieren können, die zu verwalteten Zuweisungen führen.
- Verwenden Sie den Frame Debugger, um die Ursachen für schlechte Draw-Call-Batching zu untersuchen.
Für umfassende Tipps zur Optimierung Ihres Spiels laden Sie diese Unity-Expertenleitfäden kostenlos herunter:

CPU-gebunden: Render-Thread
Der obige Screenshot zeigt ein Projekt, das durch seinen Render-Thread gebunden ist. Dies ist ein Konsolenspiel mit einer isometrischen Ansicht und einem Ziel-Frame-Budget von 33,33 ms.
Die Profiler-Aufnahme zeigt, dass der Haupt-Thread wartet, bis der Render-Thread bereit ist, bevor das Rendering für den aktuellen Frame beginnen kann, wie durch den Gfx.WaitForPresentOnGfxThreadmarker angezeigt. Der Render-Thread reicht immer noch Draw-Call-Befehle vom vorherigen Frame ein und ist nicht bereit, neue Draw-Calls vom Haupt-Thread zu akzeptieren; der Render-Thread verbringt Zeit in Camera.Render.
Sie können den Unterschied zwischen Markern, die sich auf den aktuellen Frame beziehen, und Markern aus anderen Frames erkennen, da letztere dunkler erscheinen. Sie können auch sehen, dass der Render-Thread über 100 ms benötigt, um den aktuellen Frame zu verarbeiten, sobald der Haupt-Thread fortfahren und Draw-Calls für den Render-Thread ausgeben kann, was auch während des nächsten Frames einen Engpass verursacht.
Weitere Untersuchungen zeigten, dass dieses Spiel eine komplexe Rendering-Konfiguration hatte, die neun verschiedene Kameras und viele zusätzliche Durchläufe aufgrund von Ersatz-Shadern umfasste. Das Spiel renderte auch über 130 Punktlichter mit einem Forward-Rendering-Pfad, was für jedes Licht mehrere zusätzliche transparente Draw-Calls hinzufügen kann. Insgesamt führten diese Probleme zu über 3000 Draw-Calls pro Frame.
Die folgenden sind häufige Ursachen, die für Projekte, die durch den Render-Thread gebunden sind, untersucht werden sollten:
- Schlechtes Draw-Call-Batching, insbesondere bei älteren Grafik-APIs wie OpenGL oder DirectX 11
- Zu viele Kameras. Es sei denn, Sie erstellen ein Split-Screen-Multiplayer-Spiel, ist die Wahrscheinlichkeit hoch, dass Sie nur eine aktive Kamera haben sollten.
- Schlechtes Culling, was dazu führt, dass zu viele Dinge gezeichnet werden. Untersuchen Sie die Frustum-Dimensionen und Culling-Layer-Masken Ihrer Kamera. Erwägen Sie, Occlusion Culling zu aktivieren. Vielleicht sollten Sie sogar Ihr eigenes einfaches Occlusion Culling-System erstellen, basierend auf dem, was Sie über die Anordnung von Objekten in Ihrer Welt wissen. Sehen Sie sich an, wie viele schattenwerfende Objekte in der Szene vorhanden sind – das Schatten-Culling erfolgt in einem separaten Durchgang zum "regulären" Culling.
Das Rendering Profiler-Modul zeigt eine Übersicht über die Anzahl der Draw-Call-Batches und SetPass-Aufrufe in jedem Frame. Das beste Werkzeug zur Untersuchung, welche Draw-Call-Batches Ihr Render-Thread an die GPU sendet, ist der Frame Debugger.

CPU-gebunden: Arbeiter-Threads
Projekte, die durch CPU-Threads außerhalb der Haupt- oder Render-Threads gebunden sind, sind nicht sehr häufig. Es kann jedoch auftreten, wenn Ihr Projekt den Data-Oriented Technology Stack (DOTS) verwendet, insbesondere wenn Arbeiten vom Haupt-Thread in Worker-Threads unter Verwendung des C# Job Systems verschoben werden.
Die oben gezeigte Aufnahme stammt aus dem Spielmodus im Editor und zeigt ein DOTS-Projekt, das eine Partikelfluid-Simulation auf der CPU ausführt.
Es sieht auf den ersten Blick nach einem Erfolg aus. Die Worker-Threads sind eng gepackt mit Burst-kompilierten Jobs, was darauf hinweist, dass eine große Menge an Arbeit vom Haupt-Thread verschoben wurde. In der Regel ist dies eine sinnvolle Entscheidung.
In diesem Fall sind jedoch die Frame-Zeit von 48,14 ms und der graue WaitForJobGroupID-Marker von 35,57 ms im Haupt-Thread Anzeichen dafür, dass nicht alles gut ist. WaitForJobGroupID zeigt an, dass der Haupt-Thread Jobs geplant hat, die asynchron auf Worker-Threads ausgeführt werden sollen, aber er benötigt die Ergebnisse dieser Jobs, bevor die Worker-Threads mit deren Ausführung fertig sind. Die blauen Profiler-Marker unter WaitForJobGroupID zeigen, dass der Haupt-Thread Jobs ausführt, während er wartet, um sicherzustellen, dass die Jobs schneller abgeschlossen werden.
Obwohl die Jobs Burst-kompiliert sind, leisten sie immer noch viel Arbeit. Vielleicht sollte die räumliche Abfrage-Struktur, die von diesem Projekt verwendet wird, um schnell zu finden, welche Partikel nahe beieinander sind, optimiert oder gegen eine effizientere Struktur ausgetauscht werden. Oder die räumlichen Abfrage-Jobs können für das Ende des Frames geplant werden, anstatt für den Anfang, wobei die Ergebnisse erst zu Beginn des nächsten Frames benötigt werden. Vielleicht versucht dieses Projekt, zu viele Partikel zu simulieren. Eine weitere Analyse des Codes der Jobs ist erforderlich, um die Lösung zu finden, daher kann das Hinzufügen von feinkörnigen Profiler-Markern helfen, ihre langsamsten Teile zu identifizieren.
Die Jobs in Ihrem Projekt sind möglicherweise nicht so parallelisiert wie in diesem Beispiel. Vielleicht haben Sie nur einen langen Job, der in einem einzelnen Worker-Thread ausgeführt wird. Das ist in Ordnung, solange der Zeitraum zwischen der Planung des Jobs und dem Zeitpunkt, zu dem er abgeschlossen sein muss, lang genug ist, damit der Job ausgeführt werden kann. Wenn nicht, werden Sie sehen, dass der Hauptthread stockt, während er auf den Abschluss des Jobs wartet, wie im obigen Screenshot.
Häufige Ursachen für Synchronisationspunkte und Engpässe bei Worker-Threads sind:
- Jobs, die nicht vom Burst-Compiler kompiliert werden
- Lang laufende Jobs auf einem einzelnen Worker-Thread, anstatt über mehrere Worker-Threads parallelisiert zu werden
- Unzureichende Zeit zwischen dem Punkt im Frame, an dem ein Job geplant wird, und dem Punkt, an dem das Ergebnis benötigt wird
- Mehrere "Synchronisationspunkte" in einem Frame, die erfordern, dass alle Jobs sofort abgeschlossen werden
Sie können die Flow Events Funktion in der Timeline-Ansicht des CPU Usage Profiler-Moduls verwenden, um zu untersuchen, wann Jobs geplant werden und wann ihre Ergebnisse vom Hauptthread erwartet werden. Für weitere Informationen zum Schreiben effizienter DOTS-Codes siehe den DOTS Best Practices Leitfaden.

GPU-abhängig
Ihre Anwendung ist GPU-gebunden, wenn der Hauptthread viel Zeit in Profiler-Markern wie Gfx.WaitForPresentOnGfxThread verbringt und Ihr Render-Thread gleichzeitig Marker wie Gfx.PresentFrame oder .WaitForLastPresent. anzeigt.
Die folgende Aufnahme wurde auf einem Samsung Galaxy S7 mit der Vulkan-Grafik-API gemacht. Obwohl ein Teil der Zeit, die in Gfx.PresentFrame in diesem Beispiel verbracht wird, möglicherweise mit dem Warten auf VSync zusammenhängt, zeigt die extreme Länge dieses Profiler-Markierungs an, dass der Großteil dieser Zeit mit dem Warten auf die GPU verbracht wird, um das vorherige Frame zu rendern.
In diesem Spiel haben bestimmte Gameplay-Ereignisse die Verwendung eines Shaders ausgelöst, der die Anzahl der vom GPU gerenderten Draw-Calls verdreifachte. Häufige Probleme, die bei der Profilerstellung der GPU-Leistung untersucht werden sollten, sind:
- Teure Vollbild-Nachbearbeitungseffekte, einschließlich häufiger Übeltäter wie Ambient Occlusion und Bloom
- Teure Fragment-Shader verursacht durch:
- Verzweigungslogik
- Verwendung von voller Float-Präzision anstelle von halber Präzision
- Übermäßige Verwendung von Registern, die die Wellenfrontbelegung von GPUs beeinflussen
- Überzeichnung in der transparenten Render-Warteschlange, verursacht durch ineffiziente UI, Partikelsysteme oder Nachbearbeitungseffekte
- Übermäßig hohe Bildschirmauflösungen, wie sie in 4K-Displays oder Retina-Displays auf mobilen Geräten zu finden sind
- Mikro-Dreiecke, die durch dichte Mesh-Geometrie oder einen Mangel an LODs verursacht werden, was ein besonderes Problem bei mobilen GPUs darstellt, aber auch PC- und Konsolen-GPUs betreffen kann
- Cache-Fehlzugriffe und verschwendete GPU-Speicherbandbreite, verursacht durch unkomprimierte Texturen oder hochauflösende Texturen ohne Mipmaps
- Geometrie- oder Tessellations-Shader, die möglicherweise mehrmals pro Frame ausgeführt werden, wenn dynamische Schatten aktiviert sind
Wenn Ihre Anwendung GPU-gebunden zu sein scheint, können Sie den Frame-Debugger verwenden, um schnell zu verstehen, welche Draw-Call-Batches an die GPU gesendet werden. Dieses Tool kann jedoch keine spezifischen GPU-Zeitinformationen bereitstellen, sondern nur, wie die gesamte Szene aufgebaut ist.
Der beste Weg, die Ursache von GPU-Engpässen zu untersuchen, besteht darin, einen GPU-Capture von einem geeigneten GPU-Profiling-Tool zu analysieren. Welches Tool Sie verwenden, hängt von der Zielhardware und der gewählten Grafik-API ab.

Laden Sie das E-Book, Ultimative Anleitung zum Profiling von Unity-Spielen, kostenlos herunter, um alle Tipps und Best Practices zu erhalten.