Analyser l'affichage avec le rendu GPU du profil

L'outil de Rendu GPU du profil indique la durée nécessaire à chaque étape du pipeline de rendu pour afficher l'image précédente. Ces connaissances peuvent vous aider à identifier les goulots d'étranglement dans le pipeline, et donc les éléments à optimiser pour améliorer les performances d'affichage de votre application.

Cette page décrit brièvement ce qui se passe à chaque étape du pipeline et évoque les problèmes qui peuvent causer des goulots d'étranglement. Avant de lire cette page, vous devez avoir pris connaissance des informations présentées dans la section Profiler la vitesse de rendu GPU. En outre, pour comprendre l'interaction entre toutes les étapes, il peut être utile d'examiner le fonctionnement du pipeline de rendu.

Représentation visuelle

L'outil de rendu GPU du profil affiche les étapes et les durées associées sous la forme d'un graphique : un histogramme doté d'un code couleur. La figure 1 est un exemple de ce type d'affichage.

Graphique du rendu GPU du profil
Figure 1 : Graphique du rendu GPU du profil

Les différents segments des barres verticales affichées dans le graphique de rendu GPU du profil représentent une étape du pipeline et sont mis en évidence à l'aide d'une couleur spécifique dans le graphique à barres. La figure 2 illustre la signification de chaque couleur affichée.

Légende du graphique de rendu GPU du profil
Figure 2 : Légende du graphique de rendu GPU du profil

Une fois que vous avez compris la signification de chaque couleur, vous pouvez cibler des aspects spécifiques de votre application pour essayer d'optimiser ses performances d'affichage.

Les étapes et leur signification

Cette section explique ce qui se passe à chaque étape, ainsi que les causes des goulots d'étranglement.

Traitement des entrées

L'étape de traitement des entrées du pipeline mesure la durée pendant laquelle l'application a traité les événements d'entrée. Cette métrique indique la durée pendant laquelle l'application a exécuté du code appelé à la suite de rappels d'événements d'entrée.

Lorsque ce segment est volumineux

Les valeurs élevées dans cette zone sont généralement le résultat d'un travail trop important ou trop complexe dans les rappels d'événement du gestionnaire d'entrées. Étant donné que ces rappels se produisent toujours sur le thread principal, les solutions à ce problème se concentrent sur l'optimisation directe du travail ou sur le déchargement du travail dans un autre thread.

Le défilement d'un LazyColumn ou d'un LazyRow peut également apparaître à cette étape. Une fois qu'un contact utilisateur est considéré comme un défilement, la liste paresseuse consomme les événements tactiles pour composer et organiser les éléments de manière dynamique. Si votre application effectue un travail personnalisé en réponse aux changements de position de défilement, il est important de rendre cette opération aussi rapide que possible pour éviter les pertes d'images. Des outils de profilage comme le Profileur de processeur dans Android Studio ou Perfetto peuvent vous aider à approfondir vos recherches. Pour en savoir plus, consultez la page Présentation du traçage système.

Animations

La phase d'animations vous indique la durée nécessaire pour évaluer tous les états d'animation exécutés dans ce cadre. Voici quelques API d'animation courantes dans Compose : animate*AsState, Transition et Animatable. De plus, le Recomposer s'exécute pendant cette phase pour traiter les modifications de l'état de l'instantané et mettre à jour les compositions. Cela signifie que la surcharge de recomposition apparaît souvent directement au cours de l'étape d'animation.

Pour les UI Jetpack Compose, incluez la bibliothèque Compose Runtime Tracing pour afficher des traces de composition détaillées à côté des événements système.

Lorsque ce segment est volumineux

Les valeurs élevées dans cette zone sont généralement le résultat d'un travail d'exécution en raison de changements d'état générés par l'animation. Par exemple, une animation de déplacement qui fait défiler votre LazyColumn ou votre LazyRow entraîne une composition, une mesure et une allocation rapides de nouveaux éléments de liste.

Mesurer

Pour dessiner vos composables à l'écran, Android exécute trois phases sur les nœuds de mise en page de votre arborescence d'UI.

Tout d'abord, le système mesure les nœuds de mise en page. Chaque composable comporte des contraintes et des modificateurs spécifiques qui décrivent les limites de taille de l'objet à l'écran. Certains composables peuvent avoir une taille spécifique et fixe. D'autres ont une taille qui s'adapte aux contraintes transmises par le conteneur de mise en page parent.

Ensuite, le système place les nœuds de mise en page. Une fois que Compose a calculé la taille des nœuds enfants pendant la phase de mesure, il peut passer à la phase de placement, au cours de laquelle il dimensionne et positionne les nœuds de mise en page à l'écran.

Le système effectue toujours cette mise en page en une seule passe pour plus d'efficacité. Lorsqu'une mise en page composable est invalidée, Compose mesure ce nœud spécifique et ne propage les mises à jour de mise en page aux hiérarchies parentes que si l'enfant modifie sa taille ou ses contraintes.

Lorsque ce segment est volumineux

Un segment volumineux dans cette zone signifie que l'application passe trop de temps dans la phase de mise en page, qui consiste à positionner et à déterminer la taille des nœuds de mise en page. Ces opérations incluent l'exécution de modificateurs de mesure et de placement pour les composables, ce qui peut retarder la préparation des frames si l'arborescence de mise en page est trop complexe. Dans ces cas, l'optimisation des performances implique de comparer votre application Compose à un benchmark et de suivre les bonnes pratiques en matière de performances.

Utilisez le Profileur de processeur dans Android Studio ou Perfetto pour inspecter les passes de mise en page et identifier les goulots d'étranglement. Pour en savoir plus, consultez la page Présentation du traçage système.

Dessiner

L'étape du dessin traduit les opérations de rendu, comme le dessin d'un arrière-plan, d'une forme ou d'un texte, en une séquence de commandes de dessin natives. Le système enregistre ces commandes dans une liste d'affichage pour l'exécution du GPU.

La barre de dessin enregistre la durée nécessaire pour capturer les commandes dans la liste d'affichage, pour tous les nœuds de mise en page à mettre à jour dans le cadre à l'écran. Le temps mesuré s'applique également à toute logique de dessin personnalisée que vous pouvez avoir dans les modificateurs de dessin ou dans un composable Canvas.

Lorsque ce segment est volumineux

Pour faire simple, cette métrique indique la durée nécessaire pour exécuter toutes les commandes de dessin pour chaque nœud de mise en page invalidé. Cette mesure inclut le temps passé à envoyer ces commandes aux nœuds enfants et aux drawables vectoriels. Par conséquent, lorsque vous voyez un pic de barre, il est possible qu'un grand nombre de composables ait soudainement été invalidé. L'invalidation permet de réexécuter les commandes de dessin et de générer à nouveau les listes d'affichage des nœuds de mise en page. Une durée longue peut également être le résultat de quelques composables ou canevas personnalisés dont l'implémentation DrawScope comporte une logique extrêmement complexe.

De plus, Compose gère souvent ses passes de mesure et de mise en page internes dans ce que la plate-forme considère comme la phase de dessin. Par conséquent, une barre "Dessin" élevée peut être due à des opérations de mise en page/de mesure internes coûteuses ou excessives plutôt qu'aux seules commandes de dessin. En cas de doute, capturez une trace Perfetto pour voir si la surcharge provient des routines de dessin ou des passes de mesure et de mise en page de Compose.

Importer

La métrique d'importation représente le temps nécessaire pour transférer des objets bitmap de la mémoire du processeur vers la mémoire du GPU concernant le cadre actuel.

En tant que processeurs distincts, le processeur et le GPU disposent de différentes zones RAM dédiées au traitement. Lorsque vous dessinez un bitmap sur Android, le système le transfère dans la mémoire du GPU avant que celui-ci ne s'affiche à l'écran. Ensuite, le GPU met en cache le bitmap pour éviter au système de transférer à nouveau les données, sauf si la texture est exclue du cache de texture du GPU.

Remarque : Sur les appareils Lollipop, cette étape est en violet.

Lorsque ce segment est volumineux

Toutes les ressources d'un cadre doivent se trouver dans la mémoire GPU avant de pouvoir être utilisées pour dessiner une cadre. Cela signifie qu'une valeur élevée pour cette métrique peut indiquer un grand nombre de charges de petite taille ou un petit nombre de très grandes ressources. C'est le cas lorsqu'une application affiche un seul bitmap dont la taille avoisine celle de l'écran. C'est aussi le cas lorsqu'une application affiche un grand nombre de vignettes.

Pour réduire cette barre, vous pouvez utiliser les techniques suivantes :

  • Assurez-vous que vos résolutions bitmap ne sont pas plus grandes que leur taille d'affichage. Par exemple, évitez d'afficher une image de 1 024 x 1 024 au format 48 x 48.
  • Utilisez des bibliothèques modernes comme Coil pour préimporter un bitmap de manière asynchrone avant la phase de synchronisation suivante.

Émission de commandes

Le segment "Émission de commandes" représente la durée nécessaire pour exécuter toutes les commandes nécessaires à l'affichage des listes d'affichage.

Pour que le système trace des listes à l'écran, il envoie les commandes nécessaires au GPU. En règle générale, il effectue cette action via l'API OpenGL ES.

Ce processus prend un certain temps, car le système effectue la transformation finale et le bornement de chaque commande avant de l'envoyer au GPU. Des frais généraux supplémentaires sont ensuite produits du côté du GPU, qui calcule les commandes finales. Ces commandes incluent des transformations finales et des bornements supplémentaires.

Lorsque ce segment est volumineux

Le temps passé pour cette étape est une mesure directe de la complexité et de la quantité des listes d'affichage que le système affiche dans un cadre donné. Cette durée peut être allongée en présence de nombreuses opérations de tracé, en particulier dans les cas où chaque primitive de dessin a un faible coût, par exemple. Exemple :

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

est bien plus coûteux que :

canvas.drawPoints(thousandPointArray)

Il n'y a pas toujours de corrélation directe entre l'émission de commandes et l'affichage de listes d'affichage. Contrairement à la barre de commandes d'émission, qui capture la durée nécessaire pour envoyer des commandes de dessin au GPU, la métrique de dessin représente la durée nécessaire pour capturer les commandes émises dans la liste d'affichage.

Cette différence est due au fait que les listes d'affichage sont mises en cache par le système dans la mesure du possible. Par conséquent, il peut arriver que le défilement, la transformation ou l'animation nécessitent que le système renvoie une liste d'affichage, mais qu'ils n'aient pas à la recréer complètement (capturer à nouveau les commandes de dessin). Par conséquent, il est possible que vous voyiez une barre "Commandes d'émission" élevée, mais qu'aucune barre "Commandes de dessin" ne s'affiche.

Intervertir les tampons

Une fois qu'Android a terminé d'envoyer sa liste d'affichage au GPU, le système envoie une dernière commande pour indiquer au pilote graphique que le cadre actuel est traité. À ce stade, le pilote peut enfin présenter l'image mise à jour à l'écran.

Lorsque ce segment est volumineux

Il est important de comprendre que le GPU exécute le travail en parallèle avec le processeur. Le système Android émet des commandes de dessin sur le GPU, puis passe à la tâche suivante. Le GPU lit ces commandes de dessin à partir d'une file d'attente et les traite.

Dans les cas où le processeur émet des commandes plus rapidement que le GPU, la file d'attente des communications entre les processeurs peut être saturée. Dans ce cas, le processeur se bloque et attend qu'il y ait suffisamment d'espace dans la file d'attente pour placer la commande suivante. Cet état de mise en file d'attente apparaît souvent pendant l'étape d'échange de tampons, car à ce moment-là, tout un frame de commandes a été envoyé.

La solution à ce problème consiste à réduire la complexité du travail qui se produit sur le GPU, de la même manière que pour la phase d'émission des commandes.

Autres

Outre le temps nécessaire pour que le système de rendu effectue son travail, un autre ensemble de travaux se produit sur le thread principal, qui n'a rien à voir avec l'affichage. La durée nécessaire pour exécuter ce travail est signalée par la légende "Temps divers". Le temps divers représente généralement le travail qui peut se produire sur le thread UI entre deux cadres de rendu consécutifs.

Lorsque ce segment est volumineux

Si cette valeur est élevée, il est probable que votre application présente des rappels, des intents ou d'autres tâches qui devraient se produire sur un autre thread. Des outils tels que le Profileur de processeur dans Android Studio ou Perfetto peuvent offrir une visibilité sur les tâches en cours d'exécution sur le thread principal. Ces informations peuvent vous aider à améliorer vos performances. Pour en savoir plus, consultez la page Présentation du traçage système.