Présentation de la mesure des performances des applications

Ce document vous aide à identifier et à résoudre les principaux problèmes de performances dans votre application.

Principaux problèmes de performances

De nombreux problèmes peuvent nuire aux performances d'une application, mais voici les plus courants :

Latence de démarrage

La latence de démarrage désigne le temps écoulé entre l'appui sur l'icône d'une application, une notification ou un autre point d'entrée, et l'affichage des données de l'utilisateur à l'écran.

Visez les objectifs de démarrage suivants dans vos applications :

  • Démarrage à froid en moins de 500 ms. Un démarrage à froid se produit lorsque l'application en cours de lancement n'est pas présente dans la mémoire du système. Cela se produit lorsqu'il s'agit du premier lancement de l'application depuis le redémarrage ou lorsque le processus de l'application a été arrêté par l'utilisateur ou le système. Un démarrage à froid nécessite le plus de travail du système, car il doit tout charger à partir du stockage et initialiser l'application. Essayez d'effectuer des démarrages à froid en 500 ms au maximum.
  • Démarrage tiède en moins de 200 ms et démarrage à chaud en moins de 150 ms. Un démarrage tiède se produit lorsque le processus de l'application est déjà en cours d'exécution en arrière-plan, mais que le système doit réinitialiser l'UI ou ramener l'activité au premier plan, par exemple lorsqu'un utilisateur quitte l'application et la rouvre peu de temps après. Un démarrage à chaud est encore plus rapide, car l'activité de l'application est déjà mise en cache en mémoire et n'a besoin que d'être mise au premier plan, sans qu'il soit nécessaire de recréer la hiérarchie des vues. Essayez de maintenir les démarrages à chaud en dessous de 200 ms et les démarrages à froid en dessous de 150 ms.
  • Des latences P95 et P99 très proches de la latence médiane. P95 et P99 représentent les 95e et 99e centiles des temps de démarrage, tandis que la médiane correspond au 50e centile. Lorsque l'application met beaucoup de temps à démarrer, l'expérience utilisateur est médiocre. Les communications inter-processus (IPC) et les E/S inutiles lors du processus critique de démarrage d'une application peuvent subir des conflits de verrouillage et introduire des incohérences.
Défilement par à-coups

Le terme à-coups décrit le problème visuel qui se produit lorsque le système n'est pas en mesure de construire et de fournir des frames à temps pour qu'ils soient affichés à l'écran à la cadence demandée de 60 Hz ou plus. Les à-coups sont particulièrement visibles en cas de défilement, alors que le flux d'animation doit être fluide. Des à-coups apparaissent lorsque le mouvement s'interrompt pendant la lecture d'un ou de plusieurs frames, car l'affichage du contenu de l'application prend plus de temps que la durée d'un frame sur le système.

Ciblez des fréquences d'actualisation de 90 Hz dans vos applications. Les fréquences de rendu traditionnelles sont de 60 Hz, mais de nombreux appareils plus récents fonctionnent en mode 90 Hz lors d'interactions utilisateur telles que le défilement. Certains appareils acceptent des fréquences encore plus élevées, jusqu'à 120 Hz.

Pour connaître la fréquence d'actualisation utilisée par un appareil à un moment donné, activez une superposition en sélectionnant Options pour les développeurs > Voir la fréquence d'actualisation dans la section Débogage.

Transitions manquant de fluidité

Ces problèmes surviennent lors des interactions, par exemple lorsque vous passez d'un onglet à l'autre ou chargez une nouvelle activité. Ces types de transitions doivent être des animations fluides et ne pas inclure de délai ni de scintillement visuel.

Inefficacité énergétique

Toute tâche consomme de la batterie et, à terme, réduit son autonomie.

Les allocations de mémoire, qui proviennent de la création d'objets dans le code, entraînent un travail dans le système. En effet, non seulement les allocations elles-mêmes nécessitent un effort de la part de l'environnement d'exécution Android Runtime (ART), mais la libération de ces objets ultérieurement (récupération de mémoire) nécessite également du temps et des efforts.

L'allocation de mémoire et la récupération de mémoire sont devenues beaucoup plus rapides et efficaces, en particulier pour les objets temporaires. Auparavant, il était recommandé d'éviter d'allouer des objets dans la mesure du possible. Toutefois, nous vous recommandons de faire ce qui convient le mieux à votre application et à votre architecture. Économiser sur les allocations au risque que le code devienne ingérable n'est pas le choix idéal, étant donné les capacités de l'ART.

Cependant, les allocations demandent un certain effort. Gardez donc en tête qu'elles peuvent entraîner des problèmes de performances si vous allouez de nombreux objets dans votre boucle interne.

Identifier les problèmes

Pour résoudre les problèmes de performances, identifiez et inspectez les critical user journeys suivants :

  • Les flux de démarrage courants, y compris depuis le lanceur d'applications et les notifications.
  • Les écrans sur lesquels l'utilisateur fait défiler les données.
  • Les transitions entre les écrans.
  • Les flux de longue durée, comme la navigation ou la lecture de musique.

Pour chacun de ces flux, inspectez ce qui se passe à l'aide des outils de débogage suivants :

  • Perfetto : permet de voir ce qui se passe sur l'ensemble de l'appareil, à l'aide de données temporelles précises.
  • Profileur de mémoire : permet de voir quelles allocations de mémoire se produisent sur le tas de mémoire.
  • Simpleperf : affiche un FlameGraph qui identifie les appels de fonction qui utilisent le plus le processeur au cours d'une période donnée. Si vous identifiez un élément qui prend beaucoup de temps dans Systrace, mais que vous ne savez pas pourquoi, Simpleperf peut fournir des informations supplémentaires.

Pour comprendre et déboguer ces problèmes de performances, il est essentiel de déboguer manuellement chaque exécution de test. Vous ne pouvez pas remplacer les étapes précédentes par une analyse de données agrégées. Toutefois, pour comprendre ce que les utilisateurs voient réellement et identifier quand des régressions peuvent se produire, il est important de configurer la collecte de métriques dans des tests automatisés et dans le champ :

  • Flux de démarrage
  • À-coups
    • Métriques de champ
      • Données essentielles liées aux frames de la Play Console : il n'est pas possible de limiter les métriques à un parcours utilisateur spécifique depuis la Play Console. Seuls les à-coups globaux dans l'application sont signalés.
      • Mesure personnalisée avec FrameMetricsAggregator : vous pouvez utiliser FrameMetricsAggregator pour enregistrer les métriques d'à-coups pendant un workflow particulier.
    • Tests labo
      • Défilement avec Macrobenchmark.
      • Macrobenchmark collecte le temps de rendu à l'aide de commandes dumpsys gfxinfo qui regroupent un seul parcours utilisateur. Il s'agit d'un moyen de comprendre les variations d'à-coups sur un parcours utilisateur spécifique. Les métriques RenderTime, qui mettent en évidence le temps nécessaire pour dessiner les frames, sont plus importantes que le nombre de frames saccadés pour identifier les régressions ou les améliorations.

Les liens d'application sont des liens profonds basés sur l'URL de votre site Web et dont l'appartenance à votre site Web a été validée. La validation des liens d'application peut échouer pour les raisons suivantes :

  • Portées de filtre d'intent incorrectes : n'ajoutez autoVerify aux filtres d'intent que pour les URL auxquelles votre application peut répondre.
  • Changements de protocole non validés : les redirections côté serveur et de sous-domaine non validées sont considérées comme des risques pour la sécurité et ne sont pas validées. Ils entraînent l'échec de tous les liens autoVerify. Par exemple, la redirection de liens HTTP vers HTTPS (par exemple, de example.com vers www.example.com) sans validation des liens HTTPS peut entraîner l'échec de la validation. Assurez-vous de valider les liens d'application en ajoutant des filtres d'intent.
  • Liens non vérifiables : l'ajout de liens non vérifiables à des fins de test peut empêcher le système de valider les liens vers votre application.
  • Serveurs peu fiables : assurez-vous que vos serveurs peuvent se connecter à vos applications clientes.

Configurer votre application pour l'analyse des performances

Une configuration appropriée est essentielle pour obtenir des analyses comparatives précises, reproductibles et exploitables à partir d'une application. Effectuez des tests sur un système le plus proche possible de la production, tout en supprimant les sources de bruit. Les sections suivantes présentent plusieurs étapes spécifiques à l'APK et au système que vous pouvez suivre pour préparer une configuration de test. Certaines d'entre elles sont spécifiques à un cas d'utilisation.

Points de trace

Les applications peuvent instrumenter leur code avec des événements de trace personnalisés.

Lors de la capture des traces, le traçage implique une légère surcharge d'environ 5 μs par section. Par conséquent, ne l'utilisez pas pour toutes les méthodes. Le traçage de segments de tâche plus larges (> 0,1 ms) peut fournir des informations importantes sur les goulots d'étranglement.

Remarques sur l'APK

Les variantes de débogage peuvent être utiles pour résoudre les problèmes et symboliser les échantillons de piles, mais elles ont un impact majeur sur les performances. Les appareils équipés d'Android 10 (niveau d'API 29) ou version ultérieure peuvent utiliser profileable android:shell="true" dans leur fichier manifeste pour activer le profilage dans les versions.

Utilisez votre configuration de minification de code de niveau production. Selon les ressources utilisées par votre application, cela peut avoir un impact important sur les performances. Certaines configurations ProGuard suppriment les points de trace. Par conséquent, pensez à supprimer ces règles pour la configuration sur laquelle vous exécutez des tests.

Compilation

Compilez votre application sur l'appareil dans un état connu, généralement speed pour plus de simplicité, ou speed-profile pour correspondre plus étroitement aux performances de production (bien que cela nécessite d'échauffer l'application et de vider les profils ou de compiler les profils de référence de l'application).

speed et speed-profile réduisent la quantité de code interprété à partir de dex, et par conséquent la quantité de compilation juste-à-temps (JIT) en arrière-plan, qui peut entraîner des interférences importantes. Seul speed-profile réduit l'impact du chargement de classe d'exécution à partir de dex.

La commande suivante compile l'application à l'aide du mode speed :

adb shell cmd package compile -m speed -f com.example.packagename

Le mode de compilation speed compile complètement les méthodes de l'application. Le mode speed-profile compile les méthodes et les classes de l'application en fonction d'un profil des chemins de code utilisés qui sont collectés lors de l'utilisation de l'application. Il peut être difficile de collecter des profils de manière cohérente et correcte. Par conséquent, si vous décidez de les utiliser, vérifiez qu'ils collectent les éléments attendus. Les profils se trouvent à l'emplacement suivant :

/data/misc/profiles/ref/[package-name]/primary.prof

Remarques concernant le système

Pour les mesures de bas niveau et de haute fidélité, calibrez vos appareils. Effectuez des comparaisons A/B sur le même appareil et la même version d'OS. Il peut exister des variations importantes des performances, même sur le même type d'appareil.

Sur les appareils en mode root, envisagez d'utiliser un script lockClocks pour les microbenchmarks. Ces scripts effectuent les opérations suivantes :

  • Réglage des processeurs à une fréquence fixe
  • Désactivation des petits cœurs et configuration du GPU
  • Désactivation de la limitation thermique

Nous vous déconseillons d'utiliser un script lockClocks pour les tests axés sur l'expérience utilisateur, tels que le lancement d'une application, les tests DoU et les tests d'à-coups. Toutefois, cela peut s'avérer essentiel pour réduire le bruit dans les tests Microbenchmark.

Dans la mesure du possible, envisagez d'utiliser un framework de test tel que Macrobenchmark, qui permet de réduire le bruit et d'éviter les erreurs de mesure.

Démarrage lent de l'application : activité de trampoline inutile

Une activité de trampoline peut prolonger inutilement le démarrage d'une application. Il est important de savoir si votre application est concernée. Comme le montre l'exemple de trace suivant, un événement activityStart est immédiatement suivi d'un autre événement activityStart sans qu'aucun frame ne soit dessiné par la première activité.

alt_text Figure 1 : Trace montrant une activité de trampoline

Cela peut se produire à la fois dans un point d'entrée de notification et dans un point d'entrée de démarrage d'application standard, et le problème peut souvent être résolu par la refactorisation. Par exemple, si vous utilisez cette activité pour effectuer la configuration avant l'exécution d'une autre activité, intégrez ce code dans un composant ou une bibliothèque réutilisables.

Les allocations inutiles déclenchent des récupérations de mémoire fréquentes

Vous remarquerez peut-être que les récupérations de mémoire se produisent plus fréquemment que prévu dans une trace Systrace.

Dans l'exemple suivant, le garbage collection toutes les 10 secondes au cours d'une opération de longue durée indique que l'application alloue inutilement et de manière cohérente au fil du temps :

alt_text Figure 2 : Trace montrant l'espace entre les événements de récupération de mémoire

Vous remarquerez peut-être également dans le Profileur de mémoire qu'une pile d'appel spécifique effectue la grande majorité des allocations. Vous n'avez pas besoin d'éliminer toutes les allocations de manière agressive, car cela peut rendre le code plus difficile à gérer. Commencez plutôt par travailler sur les hotspots des allocations.

Frames saccadés

Le pipeline graphique est relativement complexe, et il peut être difficile de déterminer si un utilisateur a finalement subi une perte de fréquence de frames. Dans certains cas, la plate-forme peut "sauver" un frame grâce à la mise en mémoire tampon. Cependant, vous pouvez ignorer la plupart de ces nuances pour identifier les frames problématiques du point de vue de votre application.

Lorsque les frames sont dessinés avec un effort minimal de l'application, les points de trace Choreographer.doFrame() se produisent à une cadence de 16,7 ms sur un appareil de 60 FPS :

alt_text Figure 3 : Trace affichant des frames rapides fréquents

Si vous faites un zoom arrière et que vous parcourez la trace, vous verrez parfois que les frames prennent un peu plus de temps, mais pas plus de 16,7 ms. Ces frames sont acceptables :

alt_text Figure 4. Trace affichant des frames rapides fréquents associés à des pics de travail périodiques

Lorsque vous constatez une interruption de cette cadence régulière, il s'agit d'un frame saccadé, comme illustré dans les figures 5 et 6 :

alt_text Figure 5. Trace montrant un frame saccadé

alt_text Figure 6 : Trace affichant plus de frames saccadés

Dans certains cas, vous devrez effectuer un zoom avant sur un point de trace pour en savoir plus sur les composants d'UI mis à jour par Compose ou, comme dans la figure 6, sur ce que fait un LazyColumn. Lors du diagnostic de ces goulots d'étranglement de l'UI, le traçage système standard peut ne pas indiquer quels composables sont à l'origine du problème. Dans ce cas, utilisez le traçage de la composition Jetpack Compose, qui affiche les fonctions composables exactes directement dans la trace, ce qui permet d'identifier plus facilement les recompositions inattendues. Les figures 5 et 6 montrent les résultats du traçage de la composition.

Pour en savoir plus sur l'optimisation des performances de Compose, consultez Performances de Jetpack Compose. Pour en savoir plus sur l'identification des frames saccadés et le débogage de leurs causes, consultez Affichage lent.

Erreurs courantes liées à la mise en page différée

L'invalidation inutile de l'intégralité de l'état de sauvegarde d'une mise en page différée peut entraîner des recompositions excessives, de longs délais d'affichage des frames et des à-coups. Pour minimiser le nombre d'éléments de liste à mettre à jour, utilisez des clés d'élément pour vos éléments et ne modifiez que les éléments d'état spécifiques qui changent.

Consultez Utiliser des clés de mise en page différée pour éviter les réallocations de liste complète coûteuses, qui entraînent la mise à jour du contenu au lieu de le remplacer complètement.

Une implémentation incorrecte des listes à défilement imbriquées peut entraîner une baisse des performances. Évitez d'imbriquer une mise en page Lazy scrolling dans un autre conteneur de défilement sans contraintes explicites. Pour en savoir plus, consultez Éviter l'imbrication de composants à faire défiler dans la même direction.

Si la récupération préalable de données est insuffisante ou non opportune, il peut être pénible d'atteindre le bas d'une liste déroulante lorsqu'un utilisateur doit attendre plus de données du serveur. Bien que cela ne soit pas techniquement un "à-coup", car aucune date limite d'affichage n'est ratée, vous pouvez améliorer considérablement l'expérience utilisateur pour modifier le délai et la quantité de préchargement afin que l'utilisateur ne doive pas attendre les données.

Déboguer votre application

Voici quelques méthodes pour déboguer les performances de votre application.

Déboguer le démarrage de l'application avec Systrace

Consultez Temps de démarrage de l'application pour obtenir une présentation du processus de démarrage de l'application. Regardez également la vidéo suivante pour obtenir une présentation du traçage système et de l'utilisation du profileur Android Studio.

Vous pouvez lever l'ambiguïté des types de démarrage aux étapes suivantes :

  • Démarrage à froid : commence par créer un nouveau processus sans état enregistré.
  • Démarrage tiède : recrée l'activité en réutilisant le processus ou recrée le processus avec l'état enregistré.
  • Démarrage à chaud : redémarre l'activité et commence à l'inflation.

Nous vous recommandons de capturer les systraces avec l'application de traçage système sur l'appareil. Pour Android 10 ou version ultérieure, utilisez Perfetto. Pour Android 9 et versions antérieures, utilisez Systrace. Nous vous recommandons également d'afficher les fichiers de trace avec le lecteur de traces Perfetto basé sur le Web. Pour en savoir plus, consultez Présentation du traçage système.

Voici quelques éléments à rechercher :

  • Contention du contrôleur : la concurrence pour les ressources protégées par un contrôleur peut entraîner un décalage important au démarrage de l'application.
  • Transactions de liaisons synchrones : recherchez les transactions inutiles dans le chemin critique de votre application. Si une transaction nécessaire est coûteuse, envisagez de collaborer avec l'équipe de la plate-forme associée pour l'améliorer.

  • Récupération de mémoire simultanée : ce processus est courant et a un impact relativement faible. Toutefois, si vous le sollicitez trop souvent, nous vous recommandons d'étudier la question à l'aide du Profileur de mémoire Android Studio.

  • E/S : recherchez les E/S effectuées pendant le démarrage et recherchez les blocages.

  • Activité significative sur d'autres threads : cela peut interférer avec le thread UI. Par conséquent, faites attention au travail en arrière-plan au démarrage.

Nous vous recommandons d'appeler reportFullyDrawn lorsque le démarrage est terminé du point de vue de l'application pour améliorer le reporting des métriques de démarrage de l'application. Pour en savoir plus sur l'utilisation de reportFullyDrawn, consultez la section Temps avant affichage complet. Vous pouvez extraire les heures de début définies par RFD à l'aide du processeur de trace Perfetto. Un événement de trace visible par l'utilisateur est émis.

Utiliser le traçage système sur l'appareil

Vous pouvez utiliser l'application système appelée "Traçage système" pour capturer une trace système sur un appareil. Cette application vous permet d'enregistrer des traces à partir de l'appareil sans avoir à le brancher ni à le connecter à adb.

Utiliser le Profileur de mémoire Android Studio

Vous pouvez utiliser le Profileur de mémoire Android Studio pour inspecter la pression sur la mémoire qui peut être due à des fuites de mémoire ou à des modèles d'utilisation incorrects. Il offre une vue en direct des allocations d'objets.

Vous pouvez résoudre les problèmes de mémoire de votre application en utilisant le Profileur de mémoire pour savoir pourquoi et à quelle fréquence les récupérations de mémoire se produisent.

Pour profiler la mémoire de l'application, procédez comme suit :

  1. Détecter les problèmes de mémoire.

    Enregistrez une session de profilage de la mémoire du parcours utilisateur sur lequel vous souhaitez vous concentrer. Recherchez un nombre d'objets croissant, comme illustré dans la figure 7, qui finit par entraîner des GC, comme illustré dans la figure 8.

    alt_text Figure 7. Augmentation du nombre d'objets.

    alt_text Figure 8. Récupérations de mémoire.

    Une fois que vous avez identifié le parcours utilisateur qui augmente la pression sur la mémoire, analysez les causes de cette pression.

  2. Diagnostiquez les zones sensibles de pression sur la mémoire.

    Sélectionnez une plage dans la chronologie pour visualiser les allocations et la taille superficielle, comme illustré dans la figure 9.

    alt_text Figure 9. Valeurs pour Allocations et Taille superficielle.

    Il existe plusieurs façons de trier ces données. Voici quelques exemples de la manière dont chaque vue peut vous aider à analyser les problèmes.

    • Organiser par classe : utile lorsque vous souhaitez rechercher des classes qui génèrent des objets qui, autrement, sont mis en cache ou réutilisés à partir d'un pool de mémoire.

      Par exemple, si une application crée 2 000 objets d'une classe particulière par seconde, le nombre d'allocations augmente de 2 000 par seconde, ce qui est visible lors du tri par classe. Si vous souhaitez réutiliser ces objets pour éviter de générer des déchets, implémentez un pool de mémoire.

    • Organiser par pile d'appels : utile lorsque vous souhaitez trouver un chemin réactif dans lequel de la mémoire est attribuée, par exemple dans une boucle ou dans une fonction spécifique effectuant de nombreuses opérations d'allocation.

    • Taille superficielle : ne suit que la mémoire de l'objet lui-même. Elle est utile pour suivre des classes simples composées principalement de valeurs primitives.

    • Taille conservée : indique la mémoire totale due à l'objet lui-même, ainsi que toutes les références qui ne sont référencées que par l'objet. Elle est utile pour suivre la pression sur la mémoire en raison d'objets complexes. Pour obtenir cette valeur, effectuez un vidage complet de la mémoire, comme illustré dans la figure 10. La colonne Taille conservée est ajoutée, comme illustré à la figure 11.

      alt_text Figure 10 : Vidage complet de la mémoire.

      colonne "Taille conservée".
      Figure 11. Colonne "Taille conservée".
  3. Mesurez l'impact d'une optimisation.

    Les GC sont plus évidents et il est plus facile de mesurer l'impact des optimisations de la mémoire. Lorsqu'une optimisation réduit la pression sur la mémoire, vous constatez une diminution du nombre de récupérations de mémoire.

    Pour mesurer l'impact de l'optimisation, mesurez le délai entre les récupérations de mémoire dans la chronologie du profileur. Un impact positif se traduit par un temps plus long entre les récupérations de mémoire.

    L'impact final des améliorations de la mémoire est le suivant :

    • Les arrêts pour mémoire insuffisante sont probablement réduits si l'application n'est pas constamment sollicitée.
    • Avoir moins de récupérations de mémoire améliore les métriques liées aux à-coups, en particulier au niveau du P99. En effet, les récupérations de mémoire provoquent des conflits au niveau du processeur, ce qui peut différer les tâches de rendu.