Funktionsweise der profilgestützten Optimierung (PGO)

Die profilgestützte Optimierung (auch bekannt als PGO oder "Pogo") ist eine Möglichkeit zur weiteren Optimierung optimierter Builds Ihres Spiels. Hierzu werden Informationen über das Verhalten Ihres Spiels in der realen Welt verwendet. Auf diese Weise wird selten ausgeführter Code wie Fehler oder Grenzfälle von den kritischen Ausführungspfaden des Codes herabgestuft und beschleunigt.

Ein Diagramm, das die Funktionsweise von PGO visuell darstellt.

Abbildung 1: Ein Überblick über die Funktionsweise von PGO.

Zur Verwendung von PGO müssen Sie zuerst Ihren Build instrumentieren, um Profildaten zu generieren, mit denen der Compiler arbeiten kann. Anschließend führen Sie zur Übung Ihres Codes eine oder mehrere Profildatendateien aus und generieren diese. Anschließend kopieren Sie diese Dateien vom Gerät zurück und verwenden sie zusammen mit dem Compiler, um Ihre ausführbare Datei mithilfe der erfassten Profilinformationen zu optimieren.

Funktionsweise optimierter Builds ohne PGO

Bei einem Build, der ohne Verwendung von Profildaten optimiert wird, wird anhand einer Reihe von Heuristiken entschieden, wie optimierter Code generiert werden soll.

Einige werden explizit vom Entwickler signalisiert, z. B. in C++ ab Version 20 durch die Verwendung von Hinweisen für Zweigrichtungen wie [[likely]] und [[unlikely]]. Ein weiteres Beispiel wäre die Verwendung des Keywords inline oder sogar __forceinline. Im Allgemeinen ist es jedoch besser und flexibler, bei dem ersten Keyword zu bleiben. Standardmäßig gehen einige Compiler davon aus, dass der erste Abschnitt eines Zweigs (d. h. die Anweisung if und nicht der Teil else) der wahrscheinlichste Abschnitt ist. Das Optimierer kann auch Annahmen aus der statischen Analyse des Codes zur Ausführung des Codes anstellen – dieser ist jedoch in der Regel beschränkt.

Das Problem dieser Heuristik ist, dass sie dem Compiler nicht in allen Situationen richtig helfen kann, selbst bei einem umfangreichen manuellen Markup. Obwohl der generierte Code in der Regel gut optimiert ist, ist er nicht so gut, wie es der Compiler sein könnte, wenn der Compiler mehr Informationen über sein Verhalten während der Laufzeit hätte.

Profil erstellen

Wenn beim Erstellen der ausführbaren Datei PGO im instrumented-Modus aktiviert ist, wird die ausführbare Datei am Anfang jedes Codeblocks mit Code erweitert, z. B. am Anfang einer Funktion oder am Anfang jeder Verzweigung eines Zweigs. Mit diesem Code wird erfasst, wie oft Code in den Block eingegeben wird, den der Compiler später zum Generieren von optimierten Code verwenden kann.

Darüber hinaus werden noch weitere Nachverfolgungen durchgeführt, z. B. die Größe typischer Kopiervorgänge in einem Block, damit später schnelle Inline-Versionen des Vorgangs generiert werden können.

Nachdem vom Spiel eine Art repräsentativer Vorgang ausgeführt wurde, muss die ausführbare Datei eine Funktion – __llvm_profile_write_file() – aufrufen, um die Profildaten an einen anpassbaren Speicherort auf dem Gerät zu schreiben. Diese Funktion wird automatisch mit Ihrem Spiel verknüpft, wenn in Ihrer Build-Konfiguration die PGO-Instrumentierung aktiviert ist.

Die geschriebene Profildatendatei sollte dann zurück auf den Hostcomputer kopiert und vorzugsweise zusammen mit anderen Profilen aus demselben Build an einem Ort aufbewahrt werden, damit sie zusammen verwendet werden können.

Du kannst beispielsweise deinen Spielcode so ändern, dass __llvm_profile_write_file() aufgerufen wird, wenn die aktuelle Spielszene endet. Erstellen Sie dann Ihr Spiel mit aktivierter Instrumentierung, um ein Profil zu erstellen, und stellen Sie es auf Ihrem Android-Gerät bereit. Während der Ausführung werden Profildaten automatisch erfasst. Ihr QA-Entwickler geht das Spiel durch und führt verschiedene Szenarien durch (oder geht einfach nur nach seinem normalen Testpass vor).

Wenn Sie mit dem Trainieren der verschiedenen Teile Ihres Spiels fertig sind, können Sie zum Hauptmenü zurückkehren. Dadurch wird die aktuelle Spielszene beendet und die Profildaten werden ausgegeben.

Mit einem Skript können dann die Profildaten aus dem Testgerät kopiert und in ein zentrales Repository hochgeladen werden, wo sie für die spätere Verwendung erfasst werden können.

Profildaten zusammenführen

Sobald ein Profil von einem Gerät abgerufen wurde, muss es aus der vom instrumentierten Build generierten Profildatendatei in ein Format konvertiert werden, das vom Compiler verarbeitet werden kann. AGDE führt dies automatisch für alle Profildatendateien aus, die Sie Ihrem Projekt hinzufügen.

PGO wurde entwickelt, um die Ergebnisse mehrerer instrumentierter Profilausführungen zu kombinieren. AGDE übernimmt dies auch automatisch, wenn Sie mehrere Dateien in einem einzelnen Projekt haben.

Ein Beispiel dafür, wie das Zusammenführen von Profildatensätzen nützlich sein kann, nehmen wir an, Sie haben ein Lab voller QA-Entwickler, die alle in verschiedenen Leveln Ihres Spiels spielen. Jedes ihrer Playthroughs wird aufgezeichnet und dann verwendet, um Profildaten aus einem PGO-instrumentierten Build Ihres Spiels zu generieren. Durch das Zusammenführen von Profilen können Sie die Ergebnisse all dieser verschiedenen Testläufe kombinieren, um bessere Ergebnisse zu erzielen. Dabei können auch unterschiedliche Teile Ihres Codes komplett ausgeführt werden.

Noch besser ist es bei Longitudinaltests, bei denen Sie Kopien der Profildaten vom internen Release zum internen Release aufbewahren. Bei einer Neuerstellung werden alte Profildaten nicht zwangsläufig ungültig. Code ist in den meisten Fällen von Release zu Release relativ stabil, sodass Profildaten aus älteren Builds weiterhin nützlich sein können und nicht sofort veraltet sind.

Profilgestützte optimierte Builds generieren

Nachdem Sie Ihrem Projekt die Profildaten hinzugefügt haben, können Sie sie zum Erstellen der ausführbaren Datei verwenden, indem Sie PGO im Optimierungsmodus in Ihrer Build-Konfiguration aktivieren.

Dadurch wird der Optimierer des Compilers angewiesen, die zuvor erfassten Profildaten für Optimierungsentscheidungen zu verwenden.

Wann die profilgestützte Optimierung verwendet werden sollte

PGO ist nicht dafür gedacht, zu Beginn der Entwicklung oder während der täglichen Codeiteration zu ermöglichen. Bei der Entwicklung sollten Sie sich auf algorithmische und datenlayoutbasierte Optimierungen konzentrieren, da diese viel größere Vorteile bringen.

PGO kommt erst später im Entwicklungsprozess, wenn die Veröffentlichung abgeschlossen wird. Stellen Sie sich die profilgeführte Optimierung als das Sahnehäubchen vor, mit dem Sie die letzte Leistung aus Ihrem Code herausholen können, nachdem Sie bereits einige Zeit selbst mit der Optimierung Ihres Codes verbracht haben.

Erwartete Leistungssteigerung durch PGO

Dies hängt von einer Vielzahl von Faktoren ab, darunter wie umfassend und veraltet Ihre Profile sind und wie nahe am Optimum Ihr Code mit einem herkömmlichen optimierten Build gewesen wäre.

Im Allgemeinen wäre eine sehr konservative Schätzung, dass die CPU-Kosten in Schlüsselthreads um etwa 5% sinken. Die Ergebnisse können sich unterscheiden.

Instrumentierungsaufwand

Die Instrumentierung von PGO ist umfassend und wird zwar automatisch generiert, ist aber nicht kostenlos. Der Aufwand für die PGO-Instrumentierung kann je nach Codebasis variieren.

Leistungskosten der profilgestützten Instrumentierung

Bei instrumentierten Builds kann es zu einem Rückgang der Framerate kommen. In einigen Fällen – je nachdem, wie nah zu 100% Ihre CPU im Normalbetrieb ausgelastet ist – kann dieser Rückgang so groß sein, dass das normale Gameplay erschwert wird.

Wir empfehlen den meisten Entwicklern, einen semideterministischen Wiederholungsmodus für ihr Spiel zu erstellen. Diese Art von Funktionalität bietet Ihrem QA-Team die Möglichkeit, das Spiel an einer bekannten, wiederholbaren Startposition im Spiel zu starten (z. B. einem Spiel gespeichert oder einem bestimmten Testlevel) und dann seine Eingabe aufzuzeichnen. Diese vom Test-Build aufgezeichnete Eingabe kann in einen PGO-instrumentierten Build eingespeist, wiedergegeben und echte Profildaten generieren, unabhängig davon, wie lange die Verarbeitung eines einzelnen Frames dauert – selbst wenn das Spiel so langsam lief, dass es nicht spielbar war.

Diese Art von Funktionalität bietet auch andere große Vorteile, wie beispielsweise einen höheren Aufwand für Tester: Ein Tester kann seine Eingabe auf einem Gerät aufzeichnen und dann für Smoketests auf mehreren verschiedenen Arten von Geräten wiedergeben.

Ein solches Wiederholungssystem kann unter Android enorme Vorteile bieten, da es dort eine große Anzahl von Gerätevarianten gibt – und die Vorteile sind noch lange nicht dabei: Es kann auch einen zentralen Bestandteil Ihres Continuous-Integration-Build-Systems bilden, sodass Sie über Nacht regelmäßig Leistungs- und Rauchtests durchführen können.

Die Nutzereingaben sollten dabei an der am besten geeigneten Stelle im Eingabemechanismus des Spiels aufgezeichnet werden (wahrscheinlich keine direkten Touchscreen-Ereignisse, sondern deren Konsequenzen als Befehle). Diese Eingaben sollten auch eine Frame-Anzahl enthalten, die während des Spiels monoton ansteigt, sodass der Wiederholungsmechanismus während der Wiedergabe auf den entsprechenden Frame warten kann, bei dem er ein Ereignis auslösen sollte.

Im Wiedergabemodus sollte die Onlineanmeldung vermieden werden und keine Werbung eingeblendet werden. Außerdem sollte das Spiel mit Ihrer Ziel-Framerate ausgeführt werden. Sie sollten vsync deaktivieren.

Es ist nicht wichtig, dass alles (z. B. Partikelsysteme) in Ihrem Spiel absolut deterministisch wiederholbar ist, aber dieselben Aktionen sollten die gleichen Konsequenzen und Ergebnisse im Spiel liefern – das heißt, das Spiel sollte gleich sein.

Arbeitsspeicherkosten der profilgestützten Instrumentierung

Der Speicheraufwand der PGO-Instrumentierung variiert je nach der spezifischen Bibliothek, die kompiliert wird, stark. In unseren Tests wurde die Größe der ausführbaren Testdatei um das 2,2-Fache erhöht. Diese Erhöhung umfasste sowohl den zusätzlichen Code, der zur Instrumentierung der Codeblöcke erforderlich ist, als auch den Speicherplatz, der zum Speichern der Zähler benötigt wird. Diese Tests waren nicht vollständig und Ihre Erfahrung kann davon abweichen.

Wann Sie Ihre Profildaten aktualisieren oder verwerfen sollten

Sie sollten Ihre Profile bei jeder größeren Änderung an Ihrem Code (oder Spielinhalt) aktualisieren.

Was dies bedeutet, hängt genau von Ihrer Build-Umgebung und dem Ort ab, an dem Sie sich in der Entwicklung befinden.

Wie bereits erwähnt, sollten Profildaten nicht über größere Änderungen an der Build-Umgebung hinweg übertragen werden. Dies hindert Sie zwar nicht daran, den Build zu erstellen oder zu unterbrechen, reduziert aber die Leistungsvorteile bei der Verwendung von PGO, da nur sehr wenige Profildaten auf die neue Build-Umgebung anwendbar sind. Dies ist jedoch nicht der einzige Fall, bei dem Ihre Profildaten veraltet sein können.

Wir gehen davon aus, dass Sie PGO bei der Vorbereitung auf ein Release erst kurz vor dem Ende der Entwicklung einsetzen und vielleicht eine wöchentliche Aufnahme erstellen, damit leistungsorientierte Entwickler genau prüfen können, ob es kurz vor der Veröffentlichung zu keinen unerwarteten Problemen kommt.

Das ändert sich, wenn du dich dem Releasefenster näherst, wenn dein QA-Team jeden Tag einen Test durchführt und das Spiel vollständig durchläuft. In dieser Phase können Sie täglich Profile aus diesen Daten erstellen und diese nutzen, um zukünftige Builds für Leistungstests zu optimieren und Ihre eigenen Leistungsbudgets anzupassen.

Wenn Sie einen Release vorbereiten, sollten Sie die Build-Version sperren, die Sie veröffentlichen möchten, und diese dann von der QA durchlaufen lassen, um Ihre neuen Profildaten zu generieren. Mit diesen Daten erstellen Sie dann einen Build, um eine endgültige Version der ausführbaren Datei zu generieren.

Das QA-Team kann dann die optimierte Versand-Erstellung mit einem abschließenden Durchlauf durchführen, um sicherzustellen, dass es gut veröffentlicht werden kann.