Das Tool GPU-Rendering für Profil gibt die relative Zeit an, die für jede Phase der Rendering-Pipeline zum Rendern des vorherigen Frames benötigt wird. So können Sie Engpässe in der Pipeline identifizieren und die Rendering-Leistung Ihrer App optimieren.
Auf dieser Seite wird kurz erläutert, was in den einzelnen Pipelinephasen passiert, und es werden Probleme behandelt, die zu Engpässen führen können. Bevor Sie diese Seite lesen, sollten Sie sich mit den Informationen unter GPU-Rendering-Geschwindigkeit analysieren vertraut machen. Um zu verstehen, wie alle Phasen zusammenpassen, kann es hilfreich sein, sich anzusehen, wie die Rendering-Pipeline funktioniert.
Visuelle Darstellung
Das Tool „GPU-Rendering für Profil“ zeigt Phasen und ihre relativen Zeiten in Form eines Diagramms an: einem farbcodierten Histogramm. Abbildung 1 zeigt ein Beispiel für eine solche Anzeige.
Jedes Segment jeder vertikalen Säule im Diagramm „GPU-Rendering profilieren“ stellt eine Phase der Pipeline dar und wird im Balkendiagramm mit einer bestimmten Farbe hervorgehoben. Abbildung 2 zeigt eine Legende für die Bedeutung der einzelnen angezeigten Farben.
Wenn Sie wissen, was die einzelnen Farben bedeuten, können Sie bestimmte Aspekte Ihrer App optimieren, um die Rendering-Leistung zu verbessern.
Phasen und ihre Bedeutung
In diesem Abschnitt wird beschrieben, was in den einzelnen Phasen passiert, und es werden mögliche Ursachen für Engpässe genannt.
Eingabebehandlung
In der Phase der Eingabeverarbeitung der Pipeline wird gemessen, wie lange die App für die Verarbeitung von Eingabeereignissen benötigt hat. Dieser Messwert gibt an, wie lange die App mit der Ausführung von Code verbracht hat, der als Ergebnis von Eingabeereignis-Callbacks aufgerufen wurde.
Wenn dieses Segment groß ist
Hohe Werte in diesem Bereich sind in der Regel die Folge von zu viel oder zu komplexer Arbeit, die in den Event-Callbacks des Eingabehandlers ausgeführt wird. Da diese Callbacks immer im Hauptthread erfolgen, konzentrieren sich die Lösungen für dieses Problem darauf, die Arbeit direkt zu optimieren oder auf einen anderen Thread auszulagern.
Auch das Scrollen durch ein LazyColumn oder LazyRow kann in dieser Phase erfolgen. Sobald eine Berührung als Scrollen erkannt wird, werden die Touch-Ereignisse von der Lazy-Liste verarbeitet, um Elemente dynamisch zu erstellen und anzuordnen. Wenn Ihre App benutzerdefinierte Vorgänge als Reaktion auf Änderungen der Scrollposition ausführt, ist es wichtig, dass diese Vorgänge so schnell wie möglich ausgeführt werden, um Frame-Drops zu vermeiden. Profiling-Tools wie der CPU Profiler in Android Studio oder Perfetto können Ihnen helfen, das Problem genauer zu untersuchen. Weitere Informationen finden Sie unter System-Tracing.
Animationen
In der Animationsphase sehen Sie, wie lange es gedauert hat, alle Animationsstatus auszuwerten, die in diesem Frame ausgeführt wurden. Einige gängige Animations-APIs in Compose sind animate*AsState, Transition und Animatable.
Außerdem wird der Recomposer in dieser Phase ausgeführt, um Änderungen am Snapshot-Zustand zu verarbeiten und Kompositionen zu aktualisieren. Das bedeutet, dass der Recomposition-Overhead häufig direkt in der Animationsphase auftritt.
Für Jetpack Compose-UIs müssen Sie die Bibliothek Compose Runtime Tracing einbinden, um detaillierte Kompositions-Traces neben Systemereignissen zu sehen.
Wenn dieses Segment groß ist
Hohe Werte in diesem Bereich sind in der Regel das Ergebnis von Vorgängen, die aufgrund von Zustandsänderungen durch die Animation ausgeführt werden. Bei einer Fling-Animation, bei der in Ihrem LazyColumn oder LazyRow gescrollt wird, werden neue Listenelemente schnell zusammengestellt, gemessen und zugeordnet.
Messen
Damit Ihre Composables auf dem Bildschirm gerendert werden, führt Android drei Phasen für Layoutknoten in Ihrem UI-Baum aus.
Zuerst werden die Layoutknoten gemessen. Jede Composable-Funktion hat bestimmte Einschränkungen und Modifikatoren, die die Größenbeschränkungen des Objekts auf dem Bildschirm beschreiben. Einige Composables haben eine bestimmte, feste Größe, andere haben eine Größe, die sich an die Einschränkungen anpasst, die vom übergeordneten Layoutcontainer übergeben werden.
Zweitens platziert das System die Layoutknoten. Sobald Compose die Größen der untergeordneten Knoten während der Messphase berechnet hat, kann es mit der Platzierungsphase fortfahren, in der die Layoutknoten auf dem Bildschirm skaliert und positioniert werden.
Das System führt dieses Single-Pass-Layout immer aus, um die Effizienz zu steigern. Wenn ein zusammensetzbares Layout ungültig wird, misst Compose diesen bestimmten Knoten und gibt Layoutaktualisierungen nur an übergeordnete Hierarchien weiter, wenn das untergeordnete Element seine Größe oder Einschränkungen ändert.
Wenn dieses Segment groß ist
Ein großes Segment in diesem Bereich bedeutet, dass die App zu viel Zeit in der Layout-Phase verbringt, in der Layoutknoten positioniert und ihre Größe bestimmt wird. Dazu gehört auch das Ausführen von Mess- und Platzierungsmodifikatoren für Composables, was die Vorbereitung von Frames verzögern kann, wenn der Layoutbaum zu komplex ist. In diesen Fällen müssen Sie die Leistung Ihrer Compose-App messen und die Best Practices für die Leistung befolgen.
Verwenden Sie den CPU Profiler in Android Studio oder Perfetto, um Layoutdurchläufe zu untersuchen und Engpässe zu identifizieren. Weitere Informationen finden Sie unter System-Tracing.
Zeichnen
In der Zeichenphase werden Rendering-Vorgänge wie das Zeichnen eines Hintergrunds, einer Form oder eines Texts in eine Folge von nativen Zeichenbefehlen übersetzt. Das System erfasst diese Befehle in einer Anzeigeliste für die GPU-Ausführung.
In der Draw-Leiste wird die Zeit aufgezeichnet, die zum Erfassen der Befehle in der Anzeigeliste für alle Layoutknoten benötigt wird, die für diesen Frame auf dem Bildschirm aktualisiert werden mussten. Die gemessene Zeit gilt auch für jede benutzerdefinierte Zeichenlogik, die Sie in Draw-Modifikatoren oder einem Canvas-Composable haben.
Wenn dieses Segment groß ist
Vereinfacht ausgedrückt gibt dieser Messwert an, wie lange es gedauert hat, alle Zeichenbefehle für jeden ungültigen Layoutknoten auszuführen.
Diese Messung umfasst die Zeit, die für das Senden dieser Befehle an untergeordnete Knoten und Vektordrawables aufgewendet wird. Wenn Sie also einen solchen Anstieg sehen, kann das daran liegen, dass plötzlich viele Composables ungültig wurden. Durch die Ungültigmachung müssen Zeichenbefehle noch einmal ausgeführt und die Anzeigelisten von Layoutknoten neu generiert werden. Alternativ kann eine lange Zeit auch auf einige benutzerdefinierte Composables oder Canvas zurückzuführen sein, die eine extrem komplexe Logik in ihrer DrawScope-Implementierung haben.
Außerdem werden die internen Mess- und Layoutdurchläufe von Compose oft in der Draw-Phase der Plattform ausgeführt. Eine erhöhte Draw-Leiste kann also durch teure oder übermäßige interne Mess-/Layoutvorgänge und nicht nur durch Zeichenbefehle verursacht werden. Erstellen Sie im Zweifelsfall einen Perfetto-Trace, um zu sehen, ob der Overhead von Zeichenroutinen oder Compose-Mess- und Layout-Durchläufen stammt.
Hochladen
Der Messwert für den Upload gibt die Zeit an, die für die Übertragung von Bitmap-Objekten vom CPU-Arbeitsspeicher in den GPU-Arbeitsspeicher während des aktuellen Frames benötigt wird.
Da CPU und GPU unterschiedliche Prozessoren sind, haben sie unterschiedliche RAM-Bereiche für die Verarbeitung. Wenn Sie ein Bitmap unter Android zeichnen, überträgt das System das Bitmap in den GPU-Arbeitsspeicher, bevor die GPU es auf dem Bildschirm rendern kann. Die GPU speichert die Bitmap dann im Cache, sodass das System die Daten nicht noch einmal übertragen muss, es sei denn, die Textur wird aus dem GPU-Texturcache entfernt.
Hinweis:Auf Lollipop-Geräten ist diese Phase lila.
Wenn dieses Segment groß ist
Alle Ressourcen für einen Frame müssen sich im GPU-Arbeitsspeicher befinden, bevor sie zum Zeichnen eines Frames verwendet werden können. Ein hoher Wert für diesen Messwert kann entweder eine große Anzahl kleiner Ressourcenladevorgänge oder eine kleine Anzahl sehr großer Ressourcen bedeuten. Ein häufiger Fall ist, wenn in einer App ein einzelnes Bitmap angezeigt wird, das fast so groß wie der Bildschirm ist. Ein weiterer Fall ist, wenn in einer App eine große Anzahl von Thumbnails angezeigt wird.
Um diese Leiste zu verkleinern, können Sie beispielsweise folgende Techniken anwenden:
- Achten Sie darauf, dass die Bitmap-Auflösungen nicht viel größer sind als die Größe, in der sie angezeigt werden. Vermeiden Sie es beispielsweise, ein Bild mit 1.024 × 1.024 Pixeln als Bild mit 48 × 48 Pixeln darzustellen.
- Nutzen Sie moderne Bibliotheken wie Coil, um ein Bitmap vor der nächsten Synchronisierungsphase asynchron vorab hochzuladen.
Befehle erteilen
Das Segment „Befehle ausgeben“ stellt die Zeit dar, die zum Ausgeben aller Befehle benötigt wird, die zum Zeichnen von Displaylisten auf dem Bildschirm erforderlich sind.
Damit das System Anzeigelisten auf dem Bildschirm darstellen kann, sendet es die erforderlichen Befehle an die GPU. Normalerweise wird diese Aktion über die OpenGL ES API ausgeführt.
Dieser Vorgang dauert einige Zeit, da das System für jeden Befehl eine letzte Transformation und einen letzten Zuschnitt durchführt, bevor der Befehl an die GPU gesendet wird. Auf der GPU-Seite entsteht dann zusätzlicher Overhead, der die endgültigen Befehle berechnet. Diese Befehle umfassen endgültige Transformationen und zusätzliches Clipping.
Wenn dieses Segment groß ist
Die in dieser Phase verbrachte Zeit ist ein direktes Maß für die Komplexität und Anzahl der Anzeigelisten, die das System in einem bestimmten Frame rendert. Wenn beispielsweise viele Zeichenvorgänge ausgeführt werden, insbesondere wenn für jedes Zeichenprimitive geringe Kosten anfallen, kann sich diese Zeit verlängern. Beispiel:
for (i in 0 until 1000) { canvas.drawPoint() }
ist viel teurer als:
canvas.drawPoints(thousandPointArray)
Es gibt nicht immer eine 1:1-Korrelation zwischen dem Ausgeben von Befehlen und dem tatsächlichen Zeichnen von Anzeigelisten. Im Gegensatz zur Leiste für Problembefehle, in der die Zeit erfasst wird, die zum Senden von Zeichenbefehlen an die GPU benötigt wird, gibt der Messwert „Zeichnen“ die Zeit an, die zum Erfassen der ausgegebenen Befehle in der Anzeigeliste benötigt wurde.
Diese Abweichung ist darauf zurückzuführen, dass die Anzeigelisten nach Möglichkeit vom System im Cache gespeichert werden. Daher gibt es Situationen, in denen für einen Bildlauf, eine Transformation oder eine Animation eine neue Displayliste gesendet werden muss, ohne dass sie von Grund auf neu erstellt werden muss. Daher kann es sein, dass Sie einen hohen Balken für „Issue commands“ sehen, aber keinen hohen Balken für „Draw commands“.
Puffer tauschen
Sobald Android die Anzeigeliste an die GPU gesendet hat, gibt das System einen letzten Befehl aus, um dem Grafiktreiber mitzuteilen, dass der aktuelle Frame fertig ist. An diesem Punkt kann der Treiber das aktualisierte Bild endlich auf dem Bildschirm anzeigen.
Wenn dieses Segment groß ist
Es ist wichtig zu wissen, dass die GPU Aufgaben parallel zur CPU ausführt. Das Android-System sendet Zeichenbefehle an die GPU und fährt dann mit der nächsten Aufgabe fort. Die GPU liest diese Zeichenbefehle aus einer Warteschlange und verarbeitet sie.
Wenn die CPU Befehle schneller ausgibt, als die GPU sie verarbeitet, kann die Kommunikationswarteschlange zwischen den Prozessoren voll werden. In diesem Fall blockiert die CPU und wartet, bis in der Warteschlange Platz für den nächsten Befehl ist. Dieser Zustand der vollen Warteschlange tritt häufig während der Phase des Pufferwechsels auf, da zu diesem Zeitpunkt Befehle für ein ganzes Frame gesendet wurden.
Der Schlüssel zur Behebung dieses Problems besteht darin, die Komplexität der auf der GPU ausgeführten Arbeit zu reduzieren, ähnlich wie bei der Phase „Befehle ausgeben“.
Sonstiges
Zusätzlich zu der Zeit, die das Rendering-System für seine Arbeit benötigt, gibt es eine Reihe weiterer Aufgaben, die im Haupt-Thread ausgeführt werden und nichts mit dem Rendern zu tun haben. Die für diese Arbeit benötigte Zeit wird als „Sonstige Zeit“ erfasst. Die sonstige Zeit bezieht sich in der Regel auf Arbeit, die möglicherweise im UI-Thread zwischen zwei aufeinanderfolgenden Frames des Renderings stattfindet.
Wenn dieses Segment groß ist
Wenn dieser Wert hoch ist, hat Ihre App wahrscheinlich Callbacks, Intents oder andere Vorgänge, die in einem anderen Thread ausgeführt werden sollten. Tools wie der CPU Profiler in Android Studio oder Perfetto können Aufschluss über die Aufgaben geben, die im Hauptthread ausgeführt werden. Anhand dieser Informationen können Sie die Leistung verbessern. Weitere Informationen finden Sie unter System-Tracing.