Profilerstellung für Ihr WebGL-Spiel mit dem Flag about:tracing

Thompson
Lilli Thompson

Wenn du sie nicht messen kannst, kannst du sie auch nicht verbessern.

Lord Kelvin

Damit Ihre HTML5-Spiele schneller laufen, müssen Sie zunächst Leistungsengpässe ausfindig machen. Das kann jedoch schwierig sein. Die Auswertung der Daten pro Sekunde (Frames per Second, fps) ist erst einmal ein Anfang, aber um ein Gesamtbild zu erhalten, müssen Sie die Nuancen der Chrome-Aktivitäten verstehen.

Das Tool about:tracing hilft Ihnen dabei, unvorhergesehene Problemumgehungen zur Verbesserung der Leistung zu vermeiden. So sparst du viel Zeit und Energie und erhältst ein klareres Bild davon, was Chrome mit jedem Frame macht, und diese Informationen nutzen diese Informationen, um dein Spiel zu optimieren.

Hallo über:Tracing

Das Tracing-Tool von Chrome verschafft Ihnen einen Einblick in alle Aktivitäten von Chrome über einen bestimmten Zeitraum und ist so detailliert, dass Sie das vielleicht auf den ersten Blick überfordern. Viele Funktionen in Chrome sind für das Tracing standardmäßig eingerichtet. Sie können also ohne manuelle Instrumentierung about:tracing verwenden, um Ihre Leistung zu verfolgen. (Siehe einen späteren Abschnitt zur manuellen Instrumentierung deines JS.)

Um die Tracing-Ansicht anzuzeigen, geben Sie einfach "about:tracing" in die Omnibox (Adressleiste) von Chrome ein.

Chrome-Omnibox
„about:tracing“ in die Omnibox eingeben

Mit dem Tracing-Tool können Sie die Aufzeichnung starten, Ihr Spiel einige Sekunden lang ausführen und sich dann die Trace-Daten ansehen. Die Daten könnten beispielsweise so aussehen:

Einfaches Tracing-Ergebnis
Einfaches Tracing-Ergebnis

Ja, das ist verwirrend. Schauen wir uns an, wie er zu lesen ist.

Jede Zeile steht für einen Prozess, für den ein Profil erstellt wird, die linke rechte Achse zeigt die Zeit an und jedes farbige Kästchen ist ein instrumentierter Funktionsaufruf. Es gibt Zeilen für eine Reihe verschiedener Arten von Ressourcen. Am interessantesten für die Profilerstellung in Spielen sind CrGpuMain, das zeigt, was die GPU (Grafikprozessor) tut, und CrRendererMain. Jeder Trace enthält CrRendererMain-Zeilen für jeden geöffneten Tab während des Trace-Zeitraums (einschließlich des Tabs about:tracing selbst).

Beim Lesen von Trace-Daten besteht Ihre erste Aufgabe darin, zu ermitteln, welche CrRendererMain-Zeile Ihrem Spiel entspricht.

Einfaches Tracing-Ergebnis hervorgehoben
Einfaches Tracing-Ergebnis hervorgehoben

In diesem Beispiel sind die beiden Kandidaten 2216 und 6516. Leider gibt es derzeit keine ausgefeilte Möglichkeit, Ihre Anwendung auszuwählen. Sie müssen lediglich nach der Zeile suchen, die viele regelmäßige Updates durchführt. Wenn Sie Ihren Code manuell mit Trace-Punkten instrumentiert haben, suchen Sie nach der Zeile, die Ihre Trace-Daten enthält. In diesem Beispiel sieht es so aus, als würde 6516 eine Hauptschleife mit der Frequenz der Aktualisierungen durchführen. Falls Sie alle anderen Tabs schließen, bevor Sie den Trace starten, ist es einfacher, das richtige CrRendererMain zu finden. Es kann aber trotzdem vorkommen, dass CrRendererMain-Zeilen für andere Prozesse als Ihr Spiel enthalten sind.

Rahmen finden

Wenn Sie im Tracing-Tool die richtige Zeile für Ihr Spiel gefunden haben, suchen Sie als Nächstes die Hauptschleife aus. Die Hauptschleife sieht in den Tracing-Daten wie ein sich wiederholendes Muster aus. Sie können in den Ablaufdaten navigieren, indem Sie die Tasten W, A, S, D verwenden: A und D bewegen sich nach links oder rechts (im zeitlichen Verlauf vor und zurück) und mit W und S können Sie heran- und herauszoomen. Bei einer 60-Hz-Frequenz würden Sie davon ausgehen, dass Ihre Hauptschleife alle 16 Millisekunden wiederholt wird.

Sieht aus wie drei Ausführungsframes
Sieht aus wie drei Ausführungsframes

Sobald Sie den Herzschlag Ihres Spiels ermittelt haben, können Sie untersuchen, was genau Ihr Code in jedem Frame tut. Zoomen Sie mit W, A, S, D heran, bis Sie den Text in den Funktionsfeldern lesen können.

Tief im Ausführungs-Frame
Ausführungsframe genauer unter die Lupe nehmen

Diese Sammlung von Feldern enthält eine Reihe von Funktionsaufrufen, wobei jeder Aufruf durch ein farbiges Feld dargestellt wird. Jede Funktion wurde über das Feld darüber aufgerufen. In diesem Fall können Sie also sehen, dass MessageLoop::RunTask RenderWidget::OnSwapBuffersComplete genannt wird, was wiederum RenderWidget::DoDeferredUpdate und so weiter heißt. Durch Lesen dieser Daten erhalten Sie einen vollständigen Überblick darüber, wie und wie lange jede Ausführung gedauert hat.

Ab hier wird es etwas fixierter. Die durch about:tracing angegebenen Informationen sind die Rohfunktionsaufrufe aus dem Chrome-Quellcode. Sie können anhand des Namens Vermutungen darüber anstellen, was jede Funktion tut, aber die Informationen sind nicht gerade nutzerfreundlich. Es ist hilfreich, den Gesamtfluss des Frames zu sehen, aber Sie benötigen etwas, das für Menschen lesbarer ist, um herauszufinden, was passiert.

Trace-Tags hinzufügen

Zum Glück gibt es eine benutzerfreundliche Möglichkeit, Ihrem Code eine manuelle Instrumentierung hinzuzufügen, um Trace-Daten zu erstellen: console.time und console.timeEnd.

console.time("update");
update();
console.timeEnd("update");
console.time("render");
update();
console.timeEnd("render");

Mit dem Code oben werden im Namen der Tracing-Ansicht neue Felder mit den angegebenen Tags erstellt. Wenn Sie die App also noch einmal ausführen, werden die Felder "update" und "render" angezeigt, in denen die Zeit angegeben ist, die zwischen dem Start- und dem Ende-Aufruf für jedes Tag verstrichen ist.

Tags manuell hinzugefügt
Tags manuell hinzugefügt

Damit können Sie visuell lesbare Tracing-Daten erstellen, um Hotspots in Ihrem Code zu verfolgen.

GPU oder CPU?

Bei hardwarebeschleunigten Grafiken kann eine der wichtigsten Fragen während der Profilerstellung lauten: Ist dieser Code GPU-gebunden oder CPU-gebunden? Mit jedem Frame führen Sie etwas Rendering-Arbeit auf der GPU und etwas Logik auf der CPU aus. Um zu verstehen, was Ihr Spiel langsam macht, müssen Sie prüfen, wie die Arbeit auf die beiden Ressourcen verteilt ist.

Suchen Sie zunächst in der Tracing-Ansicht die Zeile CrGPUMain. Sie gibt an, ob die GPU zu einem bestimmten Zeitpunkt ausgelastet ist.

GPU- und CPU-Traces

Sie können sehen, dass jeder Frame Ihres Spiels CPU-Leistung in CrRendererMain und in der GPU verursacht. Der obige Trace zeigt einen sehr einfachen Anwendungsfall, bei dem sowohl CPU als auch GPU für die meisten 16-ms-Frames inaktiv sind.

Die Nachverfolgungsansicht ist wirklich hilfreich, wenn Sie ein Spiel haben, das langsam läuft, und Sie sich nicht sicher sind, welche Ressource Sie am Limit einsetzen. Für das Debugging ist es wichtig zu wissen, wie die GPU- und CPU-Linien zusammenhängen. Nehmen wir das gleiche Beispiel wie zuvor, aber fügen Sie in der Updateschleife ein wenig zusätzliche Arbeit hinzu.

console.time("update");
doExtraWork();
update(Math.min(50, now - time));
console.timeEnd("update");

console.time("render");
render();
console.timeEnd("render");

Jetzt sehen Sie einen Trace, der wie folgt aussieht:

GPU- und CPU-Traces

Was sagt uns dieser Trace? Wie wir sehen, wechselt der abgebildete Frame von etwa 2.270 ms auf 2.320 ms, was bedeutet, dass jeder Frame etwa 50 ms dauert (eine Framerate von 20 Hz). Neben dem Update-Feld sind mehrere farbige Felder für die Rendering-Funktion zu sehen, aber im Frame wird das Update allein dominiert.

Im Gegensatz zu den Abläufen auf der CPU ist die GPU immer noch bei den meisten Frames ungenutzt. Zur Optimierung dieses Codes können Sie nach Vorgängen suchen, die mit Shader-Code ausgeführt werden können, und sie auf die GPU verschieben, um die Ressourcen optimal zu nutzen.

Was passiert, wenn der Shader-Code selbst langsam ist und die GPU überarbeitet ist? Was wäre, wenn wir die unnötige Arbeit von der CPU entfernen und stattdessen etwas Arbeit in den Fragment-Shader-Code einfügen? Hier ist ein unnötig teurer Fragment-Shader:

#ifdef GL_ES
precision highp float;
#endif
void main(void) {
  for(int i=0; i<9999; i++) {
    gl_FragColor = vec4(1.0, 0, 0, 1.0);
  }
}

Wie sieht ein Trace von Code aus, der diesen Shader verwendet?

GPU- und CPU-Traces bei Verwendung von langsamem GPU-Code
GPU- und CPU-Traces bei Verwendung von langsamem GPU-Code

Notieren Sie sich erneut die Dauer eines Frames. Hier reicht das sich wiederholende Muster von etwa 2.750 ms auf 2.950 ms, was 200 ms entspricht (Framerate von etwa 5 Hz). Die Zeile CrRendererMain ist fast vollständig leer. Dies bedeutet, dass die CPU die meiste Zeit inaktiv ist, während die GPU überlastet ist. Dies ist ein sicheres Zeichen dafür, dass Ihre Shader zu schwer sind.

Wenn du nicht genau nachvollziehen konntest, was die niedrige Framerate verursacht hat, könntest du das 5-Hz-Update beobachten und verlockend sein, in den Spielcode einzusteigen und die Spiellogik zu optimieren oder zu entfernen. In diesem Fall wäre das absolut nicht gut, denn die Logik in der Spielschleife ist nicht das, was Zeit isst. Dieser Trace deutet sogar darauf hin, dass jeder Frame im Prinzip "kostenlos" wäre, da die CPU im Leerlauf steht. Wenn Sie ihm also mehr Arbeit geben, wirkt sich dies nicht auf die Dauer des Frames aus.

Reale Beispiele

Schauen wir uns nun an, wie das Nachverfolgen von Daten in einem echten Spiel aussieht. Das Tolle an Spielen, die mit offenen Webtechnologien erstellt wurden, ist, dass man sehen kann, was bei seinen Lieblingsprodukten passiert. Wenn Sie Profilerstellungstools testen möchten, können Sie Ihren bevorzugten WebGL-Titel aus dem Chrome Web Store auswählen und ein Profil dafür mit about:tracing erstellen. Dies ist ein Beispiel-Trace aus dem ausgezeichneten WebGL-Spiel Skid Racer.

Nachzeichnen eines echten Spiels
Nachzeichnen eines echten Spiels

Offenbar dauert jeder Frame etwa 20 ms, was bedeutet, dass die Framerate etwa 50 fps beträgt. Wie Sie sehen, ist die Arbeit zwischen CPU und GPU ausgeglichen, aber die GPU ist die am meisten gefragte Ressource. Wenn Sie echte Beispiele für WebGL-Spiele erstellen möchten, probieren Sie einige der mit WebGL erstellten Chrome Web Store-Titel aus:

Fazit

Wenn Ihr Spiel mit 60 Hz laufen soll, müssen alle Vorgänge für jeden Frame in 16 ms CPU- und 16 ms GPU-Zeit passen. Sie haben zwei Ressourcen, die parallel genutzt werden können, und Sie können Ihre Arbeit zwischen ihnen verlagern, um die Leistung zu maximieren. Die „about:tracing“-Ansicht in Chrome ist ein unschätzbares Tool, um einen Einblick in die tatsächliche Funktionsweise Ihres Codes zu erhalten und die Entwicklungszeit zu maximieren, indem Sie die richtigen Probleme angehen.

Nächste Schritte

Neben der GPU können Sie auch andere Teile der Chrome-Laufzeit verfolgen. Chrome Canary, die frühe Version von Chrome, dient zum Verfolgen von E/A-Vorgängen, IndexedDB und verschiedenen anderen Aktivitäten. Weitere Informationen zum aktuellen Status der Nachverfolgung von Ereignissen finden Sie in diesem Chromium-Artikel.

Wenn du ein Web-Game-Entwickler bist, solltest du dir das folgende Video ansehen. Es ist eine Präsentation des Game Developer Advocate-Teams von Google auf der GDC 2012 zur Leistungsoptimierung für Chrome-Spiele: