Mit Profil-GPU-Rendering analysieren

Das Tool Profil-GPU-Rendering gibt die relative Zeit an, die jede Phase der Rendering-Pipeline zum Rendern des vorherigen Frames benötigt. Mit diesem Wissen können Sie Engpässe in der Pipeline erkennen und wissen, was optimiert werden muss, um die Rendering-Leistung Ihrer Anwendung zu verbessern.

Auf dieser Seite wird kurz erklärt, was in jeder Pipelinephase passiert. Außerdem werden Probleme erörtert, die dort Engpässe verursachen können. Bevor Sie diese Seite lesen, sollten Sie mit den Informationen unter Profil-GPU-Rendering vertraut sein. Außerdem kann es hilfreich sein, sich die Funktionsweise der Rendering-Pipeline anzusehen, um zu verstehen, wie die einzelnen Phasen zusammenpassen.

Visuelle Darstellung

Das Profil-GPU-Rendering-Tool zeigt Phasen und ihre relativen Zeiten in Form eines farbcodierten Histogramms an. Abbildung 1 zeigt ein Beispiel für ein solches Display.

Abbildung 1: GPU-Renderinggrafik für Profil

Jedes Segment jedes vertikalen Balkens im Profil-GPU-Rendering stellt eine Phase der Pipeline dar und wird im Balkendiagramm mit einer bestimmten Farbe hervorgehoben. Abbildung 2 enthält wichtige Informationen zur Bedeutung der einzelnen angezeigten Farben.

Abbildung 2: Legende der GPU-Renderinggrafik für Profil

Sobald Sie wissen, was die einzelnen Farben bedeuten, können Sie ein Targeting auf bestimmte Aspekte Ihrer App vornehmen, um die Rendering-Leistung zu optimieren.

Phasen und ihre Bedeutungen

In diesem Abschnitt wird erläutert, was in jeder Phase entsprechend einer Farbe in Abbildung 2 geschieht und worauf Engpässe achten müssen.

Eingabeverarbeitung

In der Phase der Eingabeverarbeitung wird gemessen, wie lange die Anwendung Eingabeereignisse verarbeitet hat. Dieser Messwert gibt an, wie lange die App damit verbracht hat, als Ergebnis von Eingabeereignis-Callbacks aufgerufenen Code auszuführen.

Wenn dieses Segment groß ist

Hohe Werte in diesem Bereich sind in der Regel das Ergebnis zu viel Arbeit oder zu komplexer Arbeit in den Ereignis-Callbacks des Eingabe-Handlers. Da diese Callbacks immer im Hauptthread auftreten, liegt der Schwerpunkt der Lösungen für dieses Problem auf der direkten Optimierung der Arbeit oder der Auslagerung der Arbeit an einen anderen Thread.

Erwähnenswert ist auch, dass in dieser Phase RecyclerView gescrollt werden kann. RecyclerView scrollt sofort, wenn das Touch-Ereignis verarbeitet wird. Dies kann dazu führen, dass die Anzahl der neuen Artikelansichten zu stark angekurbelt oder dargestellt wird. Aus diesem Grund ist es wichtig, diesen Vorgang so schnell wie möglich zu gestalten. Profilerstellungstools wie Traceview oder Systrace können Ihnen bei der weiteren Untersuchung helfen.

Animation

In der Animationsphase sehen Sie, wie lange es gedauert hat, alle Animatoren zu bewerten, die in diesem Frame ausgeführt wurden. Die häufigsten Animatoren sind ObjectAnimator, ViewPropertyAnimator und Transitions.

Wenn dieses Segment groß ist

Hohe Werte in diesem Bereich sind in der Regel das Ergebnis von Arbeiten, die aufgrund einer Änderung der Eigenschaften der Animation ausgeführt werden. So führt beispielsweise eine fließende Animation, bei der ListView oder RecyclerView durchgescrollt werden, zu einer starken Inflation und Bevölkerungszahl der Aufrufe.

Messung/Layout

Damit Android Ihre Ansichtselemente auf dem Bildschirm zeichnen kann, führt es zwei bestimmte Vorgänge über Layouts und Ansichten in Ihrer Ansichtshierarchie aus.

Zunächst misst das System die Ansichtselemente. Jede Ansicht und jedes Layout verfügt über spezifische Daten, die die Größe des Objekts auf dem Bildschirm beschreiben. Einige Ansichten können eine bestimmte Größe haben, andere haben eine Größe, die sich an die Größe des übergeordneten Layout-Containers anpasst.

Zweitens legt das System die Sichtelemente fest. Sobald das System die Größen der untergeordneten Ansichten berechnet hat, kann es mit dem Layout, der Größe und der Positionierung der Ansichten auf dem Bildschirm fortfahren.

Das System führt Messungen und Layout nicht nur für die zu zeichnenden Ansichten durch, sondern auch für die übergeordneten Hierarchien dieser Ansichten bis zur Stammansicht.

Wenn dieses Segment groß ist

Wenn Ihre App in diesem Bereich viel Zeit pro Frame verbringt, liegt das in der Regel an der enormen Menge an Aufrufen, die angelegt werden müssen, oder an Problemen wie doppelter Besteuerung an der falschen Stelle in Ihrer Hierarchie. In beiden Fällen müssen Sie zur Leistungsverbesserung die Leistung der Ansichtshierarchien verbessern.

Code, den Sie onLayout(boolean, int, int, int, int) oder onMeasure(int, int) hinzugefügt haben, kann ebenfalls Leistungsprobleme verursachen. Mit Traceview und Systrace können Sie Aufrufstacks untersuchen, um mögliche Probleme in Ihrem Code zu identifizieren.

Zeichnen

Im Zeichenbereich werden die Renderingvorgänge einer Ansicht, z. B. das Zeichnen eines Hintergrunds oder des Zeichnens, in eine Abfolge nativer Zeichenbefehle übersetzt. Das System erfasst diese Befehle in einer Anzeigeliste.

In der Leiste zum Zeichnen wird aufgezeichnet, wie lange es dauert, bis die Befehle für alle Ansichten, die auf dem Bildschirm in diesem Frame aktualisiert werden müssen, in die Anzeigeliste aufgenommen werden. Die gemessene Zeit gilt für jeden Code, den Sie den UI-Objekten in Ihrer App hinzugefügt haben. Beispiele für einen solchen Code sind onDraw(), dispatchDraw() und die verschiedenen draw ()methods, die zu den Unterklassen der Drawable-Klasse gehören.

Wenn dieses Segment groß ist

Vereinfacht ausgedrückt können Sie mit diesem Messwert nachvollziehen, wie lange es gedauert hat, alle Aufrufe von onDraw() für jede ungültige Ansicht auszuführen. Diese Messung umfasst die Zeit, die für das Senden von Zeichenbefehlen an untergeordnete Elemente und eventuell vorhandene Drawables benötigt wird. Wenn dieser Balken ansteigt, kann dies daran liegen, dass eine Reihe von Aufrufen plötzlich ungültig werden. Durch die Entwertung müssen die Anzeigelisten der Ansichten neu erstellt werden. Längere Zeit kann auch das Ergebnis einiger benutzerdefinierter Ansichten sein, die eine extrem komplexe Logik in ihren onDraw()-Methoden enthalten.

Synchronisieren/hochladen

Der Messwert für Synchronisierung und Upload gibt die Zeit an, die für die Übertragung von Bitmapobjekten vom CPU- in den GPU-Arbeitsspeicher während des aktuellen Frames benötigt wird.

Wie unterschiedliche Prozessoren haben CPU und GPU unterschiedliche RAM-Bereiche für die Verarbeitung. Wenn Sie eine Bitmap unter Android zeichnen, überträgt das System die Bitmap in den GPU-Arbeitsspeicher, bevor die GPU sie auf dem Bildschirm rendern kann. Dann speichert die GPU die Bitmap im Cache, sodass das System die Daten nicht noch einmal übertragen muss, es sei denn, die Textur wird aus dem GPU-Textur-Cache entfernt.

Hinweis:Auf Lollipop-Geräten ist dieser Bereich 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 also entweder eine große Anzahl kleiner Ressourcenlasten oder eine kleine Anzahl sehr großer Ressourcen bedeuten. Das ist häufig der Fall, wenn eine App eine einzelne Bitmap anzeigt, die nahe an der Größe des Bildschirms liegt. Ein anderer Fall ist, wenn eine App eine große Anzahl von Miniaturansichten anzeigt.

Zum Verkleinern dieses Balkens können Sie beispielsweise folgende Techniken verwenden:

  • Achten Sie darauf, dass die Bitmapauflösungen nicht viel größer als die Größe sind, in der sie angezeigt werden. Ihre Anwendung sollte z. B. kein Bild mit der Größe 1024 × 1024 als 48 × 48 Pixel anzeigen.
  • Sie nutzen prepareToDraw(), um eine Bitmap vor der nächsten Synchronisierungsphase asynchron vorab hochzuladen.

Befehle ausführen

Das Segment Ausgabebefehle gibt die Zeit an, die benötigt wird, um alle Befehle auszuführen, die zum Zeichnen von Anzeigelisten auf dem Bildschirm erforderlich sind.

Damit das System Anzeigelisten auf dem Bildschirm erstellen kann, sendet es die erforderlichen Befehle an die GPU. In der Regel wird diese Aktion über die OpenGL ES API ausgeführt.

Dieser Vorgang dauert einige Zeit, da das System für jeden Befehl die letzte Transformation und Begrenzung durchführt, bevor der Befehl an die GPU gesendet wird. Auf der GPU-Seite, die die endgültigen Befehle berechnet, entsteht dann zusätzlicher Aufwand. Diese Befehle umfassen endgültige Transformationen und zusätzliche Begrenzungen.

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 vorhanden sind, insbesondere wenn die inhärenten Kosten für jedes Zeichenprinzip gering sind, könnte dies dieses Mal zu einer Überschwemmung führen. Beispiele:

Kotlin

for (i in 0 until 1000) {
    canvas.drawPoint()
}

Java

for (int i = 0; i < 1000; i++) {
    canvas.drawPoint()
}

ist wesentlich teurer als:

Kotlin

canvas.drawPoints(thousandPointArray)

Java

canvas.drawPoints(thousandPointArray);

Es gibt nicht immer eine 1:1-Korrelation zwischen der Ausführung von Befehlen und dem Zeichnen von Anzeigelisten. Im Gegensatz zu Issue Commands, mit denen die Zeit zum Senden von Zeichenbefehlen an die GPU erfasst wird, gibt der Messwert Draw die Zeit an, die für die Erfassung der ausgegebenen Befehle in die Anzeigeliste benötigt wurde.

Das liegt daran, dass die Anzeigelisten nach Möglichkeit vom System im Cache gespeichert werden. Daher gibt es Situationen, in denen das System beim Scrollen, Transformieren oder Animationen eine Anzeigeliste noch einmal senden muss, diese aber nicht von Grund auf neu erstellen, also die Zeichenbefehle noch einmal erfassen muss. Dies führt dazu, dass die Leiste „Issues“ (Ausgabebefehle) über dem Balken angezeigt wird, aber nicht sehr für die Leiste Draw Commands (Drawbefehle).

Puffer verarbeiten/austauschen

Sobald Android die gesamte Anzeigeliste an die GPU gesendet hat, gibt das System einen letzten Befehl aus, um dem Grafiktreiber mitzuteilen, dass der aktuelle Frame verarbeitet wurde. Jetzt kann der Fahrer das aktualisierte Bild auf dem Bildschirm präsentieren.

Wenn dieses Segment groß ist

Es ist wichtig zu verstehen, dass die GPU-Ausführung parallel zur CPU ausgeführt wird. Das Android-System gibt Befehle an die GPU aus und fährt dann mit der nächsten Aufgabe fort. Die GPU liest diese Zeichenbefehle aus einer Warteschlange und verarbeitet sie.

In Situationen, in denen die CPU Befehle schneller ausgibt, als die GPU sie verbraucht, 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 vorhanden ist. Dieser Status der gesamten Warteschlange tritt häufig während der Phase Zwischenspeichern auf, da zu diesem Zeitpunkt Befehle für einen ganzen Frame gesendet wurden.

Der Schlüssel zur Minderung dieses Problems besteht darin, die Komplexität der Arbeit auf der GPU zu reduzieren, ähnlich wie bei der Phase „Ausgabebefehle“.

Sonstiges

Neben der Zeit, die das Renderingsystem für seine Arbeit benötigt, gibt es eine weitere Arbeitsgruppe, die im Hauptthread ausgeführt wird und nichts mit dem Rendering zu tun hat. Die für diese Arbeit benötigte Zeit wird als Sonstige Zeit angegeben. Die „Sonstige Zeit“ bezieht sich im Allgemeinen auf die Arbeit, die im UI-Thread zwischen zwei aufeinanderfolgenden Rendering-Frames ausgeführt werden kann.

Wenn dieses Segment groß ist

Wenn dieser Wert hoch ist, enthält Ihre Anwendung wahrscheinlich Callbacks, Intents oder andere Aufgaben, die in einem anderen Thread ausgeführt werden sollten. Tools wie Method Tracing oder Systrace können einen Einblick in die Aufgaben geben, die im Hauptthread ausgeführt werden. Anhand dieser Informationen können Sie Leistungsverbesserungen ausrichten.