Mit Profil-GPU-Rendering analysieren

Das Tool GPU-Rendering für Profil gibt die relative Zeit an, die jede Phase der Rendering-Pipeline für das Rendern des vorherigen Frames benötigt. Dieses Wissen kann Ihnen helfen, Engpässe in der Pipeline zu identifizieren, damit Sie wissen, was optimiert werden muss, um die Rendering-Leistung Ihrer App zu verbessern.

Auf dieser Seite wird kurz erklärt, was in den einzelnen Pipeline-Phasen passiert, und es werden Probleme beschrieben, die dort zu Engpässen führen können. Bevor Sie diese Seite lesen, sollten Sie sich mit den Informationen unter Profil-GPU-Rendering vertraut machen. Wenn Sie verstehen möchten, wie alle Phasen zusammenpassen, kann es auch hilfreich sein, sich anzusehen, wie die Rendering-Pipeline funktioniert.

Visuelle Darstellung

Das Tool „GPU-Rendering für Profil“ zeigt die einzelnen Phasen und ihre relativen Zeiten in Form eines Diagramms an: eines farbcodierten Histogramms. Abbildung 1 zeigt ein Beispiel für eine solche Anzeige.

Abbildung 1: Grafik zum GPU-Rendering für Profil

Jedes Segment jedes vertikalen Balkens, der im Diagramm für das GPU-Rendering im Profil angezeigt wird, stellt eine Phase der Pipeline dar und wird im Balkendiagramm mit einer bestimmten Farbe hervorgehoben. Abbildung 2 zeigt einen Schlüssel zur Bedeutung der einzelnen angezeigten Farben.

Abbildung 2. Legende des GPU-Renderingdiagramms für Profil

Sobald Sie wissen, wofür die einzelnen Farben stehen, können Sie ein Targeting auf bestimmte Aspekte Ihrer Anwendung vornehmen, um die Rendering-Leistung zu optimieren.

Phasen und ihre Bedeutung

In diesem Abschnitt wird erläutert, was in den einzelnen Phasen passiert, die den Farben in Abbildung 2 entsprechen, sowie mögliche Ursachen für Engpässe.

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 Code ausgeführt hat, der als Ergebnis von Eingabeereignis-Callbacks aufgerufen wurde.

Wenn dieses Segment groß ist

Hohe Werte in diesem Bereich sind in der Regel auf zu viel Arbeit oder zu komplexe Arbeiten zurückzuführen, die innerhalb der Ereignis-Callbacks des Eingabe-Handlers auftreten. Da diese Callbacks immer im Hauptthread auftreten, konzentrieren sich die Lösungen für dieses Problem auf die direkte Optimierung der Arbeit oder die Auslagerung der Arbeit an einen anderen Thread.

Erwähnenswert ist auch, dass in dieser Phase RecyclerView das Scrollen möglich ist. RecyclerView wird sofort gescrollt, wenn das Touch-Ereignis verarbeitet wird. Dadurch kann die Anzahl der neuen Artikelaufrufe erhöht oder ergänzt werden. Aus diesem Grund ist es wichtig, diesen Vorgang so schnell wie möglich zu gestalten. Mit Profiling-Tools wie Traceview oder Systrace können Sie weitere Untersuchungen durchführen.

Animation

In der Animationsphase sehen Sie, wie lange es gedauert hat, alle Animatoren zu bewerten, die in diesem Frame ausgeführt wurden. Die gängigsten Animatoren sind ObjectAnimator, ViewPropertyAnimator und Übergänge.

Wenn dieses Segment groß ist

Hohe Werte in diesem Bereich sind in der Regel auf die Arbeit zurückzuführen, die aufgrund einer Eigenschaftsänderung der Animation ausgeführt wird. Eine Wischanimation, mit der die ListView oder RecyclerView gescrollt wird, führt beispielsweise zu einer starken Steigerung der Aufrufe und der Anzahl der Nutzer.

Maße/Layout

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

Zuerst werden die Elemente der Ansicht gemessen. Jede Ansicht und jedes Layout hat bestimmte Daten, die die Größe des Objekts auf dem Bildschirm beschreiben. Einige Ansichten können eine bestimmte Größe haben, andere passen sich der Größe des übergeordneten Layoutcontainers an.

Zweitens legt das System die Ansichtselemente fest. Sobald das System die Größe 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 das 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 dies normalerweise entweder an der überwältigenden Menge an Ansichten, die angepasst werden müssen, oder an Problemen wie Doppelbesteuerung an der falschen Stelle in Ihrer Hierarchie. In beiden Fällen muss die Leistungssteigerung 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 die Aufrufstacks untersuchen, um Probleme mit Ihrem Code zu identifizieren.

Zeichnen

In der Zeichenphase werden die Renderingvorgänge einer Ansicht, z. B. das Zeichnen eines Hintergrunds oder von Text, in eine Sequenz nativer Zeichenbefehle übersetzt. Das System erfasst diese Befehle in einer Anzeigeliste.

Die Draw-Leiste gibt an, wie lange es dauert, die Erfassung der Befehle in der Anzeigeliste für alle Ansichten abzuschließen, die in diesem Frame auf dem Bildschirm aktualisiert werden mussten. 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 abgeleiteten Klassen der Drawable-Klasse gehören.

Wenn dieses Segment groß ist

Vereinfacht ausgedrückt gibt dieser Messwert an, wie lange es gedauert hat, alle Aufrufe an 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 gegebenenfalls vorhandene Drawables aufgewendet wurde. Wenn du also diesen Balken siehst, könnte das daran liegen, dass eine Reihe von Aufrufen plötzlich ungültig wurde. In diesem Fall müssen die Displaylisten der Aufrufe neu generiert werden. Alternativ kann eine längere Zeit das Ergebnis einiger benutzerdefinierter Ansichten sein, deren onDraw()-Methoden eine extrem komplexe Logik enthalten.

Synchronisieren/Hochladen

Der Messwert „Synchronisieren und hochladen“ gibt an, wie lange es dauert, Bitmap-Objekte während des aktuellen Frames aus dem CPU-Arbeitsspeicher in den GPU-Arbeitsspeicher zu übertragen.

Da es sich um unterschiedliche Prozessoren handelt, haben die CPU und die GPU unterschiedliche RAM-Bereiche für die Verarbeitung. Wenn Sie unter Android eine Bitmap zeichnen, überträgt das System die Bitmap an den GPU-Arbeitsspeicher, bevor die GPU sie auf dem Bildschirm rendern kann. Anschließend 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 diese Phase lila.

Wenn dieses Segment groß ist

Alle Ressourcen für einen Frame müssen sich im GPU-Speicher 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. Ein häufiger Fall ist, wenn in einer App eine einzelne Bitmap angezeigt wird, die der Größe des Bildschirms nahekommt. Ein anderer Fall ist, wenn in einer App eine große Anzahl von Miniaturansichten angezeigt wird.

Um diese Leiste zu verkleinern, können Sie z. B. folgende Techniken anwenden:

  • Die Bitmapauflösungen dürfen nicht viel größer als die Größe sein, in der sie angezeigt werden. In Ihrer App sollte beispielsweise kein Bild mit 1.024 × 1.024 Pixeln als Bild mit 48 × 48 Pixeln angezeigt werden.
  • Mithilfe von prepareToDraw() wird eine Bitmap vor der nächsten Synchronisierungsphase asynchron vorab hochgeladen.

Befehle geben

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

Damit das System die Displaylisten auf dem Bildschirm anzeigt, 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 die letzte Transformation und Begrenzung für jeden Befehl durchführt, bevor der Befehl an die GPU gesendet wird. Auf der GPU-Seite, die die endgültigen Befehle berechnet, entsteht zusätzlicher Aufwand. Diese Befehle umfassen abschließende Transformationen und zusätzliches Zuschneiden.

Wenn dieses Segment groß ist

Die in dieser Phase verbrachte Zeit ist ein direktes Maß für die Komplexität und Menge der Anzeigelisten, die das System in einem bestimmten Frame rendert. Beispielsweise kann diese Zeit durch viele Zeichenvorgänge verlängert werden, insbesondere wenn mit jedem Zeichenprimitiv kleine Kosten verbunden sind. Beispiel:

Kotlin

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

Java

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

ist viel teurer als:

Kotlin

canvas.drawPoints(thousandPointArray)

Java

canvas.drawPoints(thousandPointArray);

Es gibt nicht immer eine 1:1-Korrelation zwischen der Ausgabe von Befehlen und dem tatsächlichen Erstellen von Anzeigelisten. Im Gegensatz zu Issue Commands, die die Zeit erfasst, die zum Senden von Zeichenbefehlen an die GPU benötigt wird, stellt der Messwert Draw die Zeit dar, die benötigt wurde, um die ausgegebenen Befehle in der Anzeigeliste zu erfassen.

Dieser Unterschied entsteht, weil die Displaylisten nach Möglichkeit vom System im Cache gespeichert werden. Daher gibt es Situationen, in denen das System eine Anzeigeliste für Scrollen, Transformieren oder Animationen noch einmal senden muss, sie aber nicht von Grund auf neu erstellen muss, also die Zeichenbefehle noch einmal erfassen. Daher kann es sein, dass der Balken „Befehle geben“ hoch ist, der Balken Zeichnen-Befehle aber nicht.

Prozess-/Wechselzwischenspeicher

Sobald Android die gesamte Anzeigeliste an die GPU gesendet hat, gibt das System einen letzten Befehl aus, um dem Grafiktreiber mitzuteilen, dass der Vorgang mit dem aktuellen Frame ausgeführt wird. Jetzt kann der Fahrer das aktualisierte Bild auf dem Bildschirm anzeigen.

Wenn dieses Segment groß ist

Die GPU führt die Arbeit parallel zur CPU aus. Das Android-System gibt Befehle an die GPU aus und geht 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 verarbeiten kann, kann die Kommunikationswarteschlange zwischen den Prozessoren voll werden. In diesem Fall wird die CPU blockiert und wartet, bis in der Warteschlange Platz für den nächsten Befehl ist. Dieser Status der vollständigen Warteschlange tritt häufig während der Austauschzwischenspeicher-Phase auf, da zu diesem Zeitpunkt die Befehle eines ganzen Frames gesendet wurden.

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

Sonstiges

Zusätzlich zu der Zeit, die das Renderingsystem zum Ausführen seiner Arbeit benötigt, wird im Hauptthread eine weitere Arbeit ausgeführt, die nichts mit dem Rendering zu tun hat. Die dafür aufgewendete Zeit wird als Sonstige Zeit erfasst. Die Zeit für Sonstiges entspricht in der Regel der Arbeit, die zwischen zwei aufeinanderfolgenden Rendering-Frames im UI-Thread ausgeführt wird.

Wenn dieses Segment groß ist

Wenn dieser Wert hoch ist, enthält Ihre App wahrscheinlich Rückrufe, 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 bieten, die im Hauptthread ausgeführt werden. Anhand dieser Informationen können Sie Leistungsverbesserungen gezielter einsetzen.