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

Lilli Thompson
Lilli Thompson

Was nicht gemessen werden kann, kann auch nicht verbessert werden.

Lord Kelvin

Damit Ihre HTML5-Spiele schneller laufen, müssen Sie zuerst die Leistungsengpässe ermitteln. Das kann schwierig sein. Die Auswertung der Daten zu Frames pro Sekunde (FPS) ist ein Anfang, aber um das Gesamtbild zu sehen, müssen Sie die Feinheiten der Chrome-Aktivitäten verstehen.

Das Tool about:tracing liefert Informationen, mit denen Sie vorschnelle Lösungen zur Leistungsverbesserung vermeiden können, die im Grunde gut gemeinte Vermutungen sind. So sparen Sie viel Zeit und Energie, erhalten ein klareres Bild davon, was Chrome mit jedem Frame macht, und können diese Informationen nutzen, um Ihr Spiel zu optimieren.

Hallo about:tracing,

Das Chrome-Tool about:tracing bietet Ihnen einen Überblick über alle Chrome-Aktivitäten über einen bestimmten Zeitraum, und zwar so detailliert, dass es auf den ersten Blick überwältigend wirken kann. Viele der Funktionen in Chrome sind bereits für das Tracing eingerichtet. Sie können also about:tracing verwenden, um die Leistung zu erfassen, ohne manuelle Instrumentierung vornehmen zu müssen. (Weitere Informationen finden Sie in einem späteren Abschnitt zum manuellen Instrumentieren von JS.)

Wenn Sie die Ansicht für die Aufzeichnung aufrufen möchten, geben Sie einfach „about:tracing“ in die Omnibox (Adressleiste) von Chrome ein.

Chrome-Omnibox
Geben Sie „about:tracing“ in die Chrome-Omnibox ein.

Im Trace-Tool können Sie die Aufzeichnung starten, Ihr Spiel einige Sekunden lang ausführen und sich dann die Trace-Daten ansehen. Hier ein Beispiel für die Daten:

Einfaches Ergebnis der Fehlersuche
Einfaches Ergebnis der Fehlersuche

Ja, das ist wirklich verwirrend. Sehen wir uns an, wie Sie sie lesen.

Jede Zeile steht für einen profilierten Prozess, die linke-rechte Achse gibt die Zeit an und jedes farbige Feld ist ein instrumentierter Funktionsaufruf. Es gibt Zeilen für verschiedene Arten von Ressourcen. Die für das Game-Profiling am interessantesten sind CrGpuMain, das zeigt, was die GPU tut, und CrRendererMain. Jeder Trace enthält CrRendererMain-Zeilen für jeden geöffneten Tab während des Trace-Zeitraums, einschließlich des about:tracing-Tabs selbst.

Wenn Sie Trace-Daten lesen, müssen Sie zuerst ermitteln, welche CrRendererMain-Zeile Ihrem Spiel entspricht.

Einfaches Ergebnis der Fehlersuche hervorgehoben
Hervorgehobenes Ergebnis der einfachen Aufrufabfolge

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 sich also auf die Suche nach der Zeile machen, die häufig aktualisiert wird. Wenn Sie Ihren Code manuell mit Trace-Punkten instrumentiert haben, suchen Sie nach der Zeile mit Ihren Trace-Daten. In diesem Beispiel sieht es so aus, als würde 6516 anhand der Häufigkeit der Aktualisierungen eine Hauptschleife ausführen. Wenn Sie alle anderen Tabs schließen, bevor Sie mit der Aufzeichnung beginnen, ist es einfacher, die richtige CrRendererMain zu finden. Es kann jedoch sein, dass es weiterhin CrRendererMain-Zeilen für andere Prozesse als Ihr Spiel gibt.

Frame finden

Nachdem Sie die richtige Zeile im Tracing-Tool für Ihr Spiel gefunden haben, besteht der nächste Schritt darin, die Hauptschleife zu finden. Die Hauptschleife sieht in den Tracing-Daten wie ein sich wiederholendes Muster aus. Mit den Tasten W, A, S und D können Sie sich in den Aufzeichnungsdaten bewegen: A und D zum Bewegen nach links oder rechts (Zeitlich vor- und zurück) und W und S zum Heranzoomen und Herauszoomen der Daten. Wenn Ihr Spiel mit 60 Hz ausgeführt wird, sollte sich die Hauptschleife alle 16 Millisekunden wiederholen.

Es sieht aus wie drei Ausführungsframes.
Sieht aus wie drei Ausführungsframes

Sobald Sie den Herzschlag Ihres Spiels gefunden haben, können Sie sich genauer ansehen, was genau Ihr Code in jedem Frame tut. Zoomen Sie mit den Tasten W, A, S und D heran, bis Sie den Text in den Funktionsfeldern lesen können.

Detaillierte Informationen zu einem Ausführungsframe
Ausführungsrahmen im Detail

Diese Sammlung von Kästchen zeigt eine Reihe von Funktionsaufrufen, wobei jeder Aufruf durch ein farbiges Kästchen dargestellt wird. Jede Funktion wurde von dem Feld darüber aufgerufen. In diesem Fall sehen Sie, dass MessageLoop::RunTask RenderWidget::OnSwapBuffersComplete aufgerufen hat, das wiederum RenderWidget::DoDeferredUpdate aufgerufen hat usw. Anhand dieser Daten erhalten Sie einen vollständigen Überblick darüber, was was aufgerufen hat und wie lange jede Ausführung gedauert hat.

Aber hier wird es etwas knifflig. Die von about:tracing bereitgestellten Informationen sind die Rohfunktionsaufrufe aus dem Chrome-Quellcode. Anhand der Namen lässt sich zwar eine fundierte Vermutung darüber anstellen, was die einzelnen Funktionen tun, 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 besser lesbar ist, um wirklich herauszufinden, was passiert.

Trace-Tags hinzufügen

Glücklicherweise gibt es eine einfache Möglichkeit, Ihrem Code 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 neue Felder in der Namensaufzeichnungsdatei mit den angegebenen Tags erstellt. Wenn Sie die App also noch einmal ausführen, sehen Sie die Felder „update“ und „render“, in denen die Zeit zwischen den Start- und Endaufrufen für jedes Tag angezeigt wird.

Manuell hinzugefügte Tags
Manuell hinzugefügte Tags

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

GPU oder CPU?

Bei hardwarebeschleunigter Grafik ist eine der wichtigsten Fragen, die Sie beim Profiling stellen können: Ist dieser Code GPU- oder CPU-gebunden? Bei jedem Frame wird ein Teil der Rendering-Arbeit auf der GPU und ein Teil der Logik auf der CPU ausgeführt. Um zu verstehen, was Ihr Spiel verlangsamt, müssen Sie sehen, wie die Arbeit auf die beiden Ressourcen verteilt ist.

Suchen Sie zuerst in der Ansicht „Tracing“ nach der Zeile „CrGPUMain“. Diese gibt an, ob die GPU zu einem bestimmten Zeitpunkt ausgelastet ist.

GPU- und CPU-Traces

Sie sehen, dass jeder Frame Ihres Spiels CPU-Arbeit im CrRendererMain sowie auf der GPU verursacht. Der obige Trace zeigt einen sehr einfachen Anwendungsfall, bei dem sowohl die CPU als auch die GPU während des Großteils jedes 16-ms-Frames inaktiv sind.

Die Ansicht „Tracing“ ist besonders hilfreich, wenn ein Spiel langsam läuft und Sie nicht sicher sind, welche Ressource ausgelastet ist. Der Schlüssel zur Fehlerbehebung besteht darin, die Beziehung zwischen den GPU- und CPU-Linien zu untersuchen. Nehmen wir dasselbe Beispiel wie zuvor, fügen aber in der Update-Schleife noch 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 eine Spur, die so aussieht:

GPU- und CPU-Traces

Was können wir aus dieser Trace-Datei ableiten? Wir sehen, dass der dargestellte Frame von etwa 2.270 ms auf 2.320 ms geht, was bedeutet, dass jeder Frame etwa 50 ms in Anspruch nimmt (eine Framerate von 20 Hz). Neben dem Update-Feld sind farbige Rechtecke zu sehen, die die Renderfunktion darstellen. Der Frame wird jedoch vollständig vom Update selbst dominiert.

Im Gegensatz zur CPU ist die GPU die meiste Zeit eines Frames inaktiv. Um diesen Code zu optimieren, können Sie nach Vorgängen suchen, die im Shadercode ausgeführt werden können, und sie zur GPU verschieben, um die Ressourcen optimal zu nutzen.

Was ist, wenn der Shadercode selbst langsam ist und die GPU überlastet 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 Code-Trace mit diesem Shader aus?

GPU- und CPU-Traces bei Verwendung langsamen GPU-Codes
GPU- und CPU-Traces bei Verwendung langsamen GPU-Codes

Achten Sie auch hier auf die Dauer eines Frames. Hier geht das sich wiederholende Muster von etwa 2.750 ms bis 2.950 ms, eine Dauer von 200 ms (Framerate von etwa 5 Hz). Die Zeile „CrRendererMain“ ist fast vollständig leer. Das 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 Sie nicht genau wissen, was die niedrige Framerate verursacht, könnten Sie die Aktualisierung mit 5 Hz beobachten und versucht sein, den Spielcode zu optimieren oder die Spiellogik zu entfernen. In diesem Fall würde das überhaupt nichts bringen, da die Logik in der Game Loop nicht die Zeit in Anspruch nimmt. Dieser Trace zeigt, dass die Ausführung mehr CPU-Arbeit pro Frame im Grunde „kostenlos“ wäre, da die CPU inaktiv ist. Wenn Sie ihr also mehr Arbeit geben, hat das keine Auswirkungen auf die Dauer des Frames.

Echte Beispiele

Sehen wir uns nun an, wie Tracing-Daten aus einem echten Spiel aussehen. Eines der coolen Dinge an Spielen, die mit Open Web-Technologien erstellt wurden, ist, dass Sie sehen können, was in Ihren Lieblingsprodukten passiert. Wenn Sie Profiling-Tools testen möchten, können Sie Ihren bevorzugten WebGL-Titel aus dem Chrome Web Store auswählen und mit about:tracing ein Profil erstellen. Dies ist ein Beispiel-Trace aus dem hervorragenden WebGL-Spiel Skid Racer.

Ein echtes Spiel nachzeichnen
Ein echtes Spiel nachzeichnen

Es sieht so aus, als würde jeder Frame etwa 20 ms dauern, was einer Framerate von etwa 50 fps entspricht. Sie sehen, dass die Arbeit zwischen CPU und GPU ausgeglichen ist, aber die GPU ist die am stärksten nachgefragte Ressource. Wenn Sie sehen möchten, wie Sie echte Beispiele von WebGL-Spielen analysieren, können Sie einige der Chrome Web Store-Titel ausprobieren, die mit WebGL erstellt wurden, darunter:

Fazit

Wenn Sie möchten, dass Ihr Spiel mit 60 Hz ausgeführt wird, 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 die Arbeit zwischen ihnen verschieben, um die Leistung zu maximieren. Die about:tracing-Ansicht von Chrome ist ein unschätzbares Tool, um zu erfahren, was Ihr Code tatsächlich tut. So können Sie Ihre Entwicklungszeit maximieren, indem Sie die richtigen Probleme angehen.

Nächste Schritte

Neben der GPU können Sie auch andere Teile der Chrome-Laufzeit erfassen. Chrome Canary, die Vorabversion von Chrome, ist für die Erfassung von E/A, IndexedDB und mehreren anderen Aktivitäten instrumentiert. In diesem Chromium-Artikel finden Sie weitere Informationen zum aktuellen Status der Ereignisaufzeichnung.

Wenn Sie Webspiele entwickeln, sehen Sie sich das Video unten an. Es ist eine Präsentation des Game Developer Advocate-Teams von Google auf der GDC 2012 zur Leistungsoptimierung für Chrome-Spiele: