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 est arrêté par l'utilisateur ou le système.

    En revanche, un démarrage tiède se produit lorsque l'application s'exécute déjà en arrière-plan. 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.

  • Des latences P95 et P99 très proches de la latence médiane. 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

À-coups est le terme qui 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.

Les applications doivent cibler des fréquences d'actualisation de 90 Hz. 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 retards ni de scintillements visuels.

Inefficacité énergétique

Toute tâche consomme de la batterie et les tâches inutiles réduisent l'autonomie de la batterie.

Les allocations de mémoire, qui proviennent de la création d'objets dans le code, peuvent entraîner un travail important 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 et la collecte sont bien 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, cela demande un certain effort. Gardez donc en tête que l'allocation de nombreux objets dans votre boucle interne pourrait entraîner des problèmes de performances.

Identifier les problèmes

Nous vous recommandons de suivre le workflow ci-dessous pour identifier et résoudre les problèmes de performances :

  1. 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.
  2. Inspectez ce qui se passe au cours des flux précédents à 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 vers une 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. Voici les raisons pouvant entraîner l'échec des validations d'App Link.

  • Champ d'application des filtres d'intent: n'ajoutez autoVerify qu'aux filtres d'intent pour les URL auxquelles votre application peut répondre.
  • Commutateurs de protocole non validés: les redirections côté serveur et de sous-domaine non validées sont considérées comme des risques de 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, comme example.com vers www.example.com, sans validation des liens HTTPS peut entraîner un échec de la validation. Veillez à valider les liens vers une 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 une application pour 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 builds.

Utilisez votre configuration de minification du 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 une meilleure adéquation aux performances de production (bien que cela nécessite de pré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 exécuté interprété à partir de dex, et par conséquent la quantité de compilation JIT (juste-à-temps) en arrière-plan, ce 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, toutes les 10 secondes au cours d'une opération de longue durée, cela indique que votre 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 qu'une pile d'appel spécifique effectue la grande majorité des allocations lorsque vous utilisez le Profileur de mémoire. 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 cela ne pose pas de problème, car ils ne prennent pas plus de 16,7 ms.

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 la figure 5 :

alt_text Figure 5. Trace montrant un frame saccadé

Vous pouvez vous entraîner à les identifier.

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 vues gonflées ou sur ce que fait RecyclerView. Dans d'autres cas, vous devrez peut-être procéder à une inspection plus approfondie.

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

Erreurs RecyclerView courantes

L'invalidation inutile de l'intégralité des données de sauvegarde de RecyclerView peut entraîner de longs délais d'affichage des frames et des à-coups. Pour réduire le nombre de vues à mettre à jour, n'invalidez que les données modifiées.

Consultez Présenter des données dynamiques pour éviter les appels notifyDatasetChanged() coûteux qui entraînent la mise à jour du contenu au lieu de le remplacer complètement.

Si vous ne prenez pas correctement en charge chaque RecyclerView imbriqué, le RecyclerView interne peut être entièrement recréé à chaque fois. Chaque RecyclerView imbriqué interne doit disposer d'un RecycledViewPool défini pour garantir que les vues peuvent être recyclées entre chaque RecyclerView interne.

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

Vous trouverez ci-dessous différentes méthodes de débogage des performances de votre application. Regardez la vidéo ci-dessous pour obtenir un aperçu du traçage système et de l'utilisation du profileur Android Studio.

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

Pour en savoir plus sur le processus de démarrage de l'application, consultez la section Temps de démarrage de l'application, et regardez la vidéo suivante pour en savoir plus sur le traçage système.

Vous pouvez clarifier les types de démarrage aux étapes suivantes:

  • Démarrage à froid: commencez par créer un processus sans état enregistré.
  • Démarrage à chaud: 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 des systraces avec l'application de traçage système sur l'appareil. Pour Android 10 ou version ultérieure, utilisez Perfetto. Pour Android 9 ou version antérieure, utilisez Systrace. Nous vous recommandons également d'afficher les fichiers de trace avec le lecteur de traces Perfetto Web. Pour en savoir plus, consultez la présentation du traçage système.

Voici quelques éléments à vérifier:

  • Conflit de moniteur: la concurrence pour les ressources protégées via des moniteurs 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.

  • GC simultané: il s'agit d'un problème courant qui a un impact relativement faible. Toutefois, si vous le rencontrez 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 au démarrage et recherchez les blocages.

  • Activité significative sur d'autres threads: ceux-ci peuvent 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 les rapports sur les métriques de démarrage de l'application. Pour en savoir plus sur l'utilisation de reportFullyDrawn, consultez la section Temps d'affichage complet. Vous pouvez extraire les heures de début définies par le RFD via le processeur de trace Perfetto, et 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 de niveau 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 causée par 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 suivant les informations fournies par le Profileur de mémoire pour savoir pourquoi et à quelle fréquence les GC se produisent.

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

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

    Enregistrez une session de profilage de la mémoire du parcours utilisateur sur lequel vous souhaitez vous concentrer. Recherchez une augmentation du nombre d'objets, comme illustré dans la figure 7, ce qui conduit finalement à 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.

    Après avoir 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 à la fois les allocations et la taille superficielle, comme illustré dans la figure 9.

    alt_text Figure 9 Valeurs pour Allocations et Taille peu profonde.

    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 vous voyez qu'une application crée 2 000 objets d'une classe appelée "Vertex" 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 uniquement.

    • Taille conservée: affiche la mémoire totale due à l'objet et aux 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 de mémoire complet, comme illustré dans la figure 10, et la colonne Taille conservée est ajoutée, comme illustré dans 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 permettent de mesurer plus facilement l'impact des optimisations de la mémoire. Lorsqu'une optimisation réduit la pression sur la mémoire, vous constatez une réduction du nombre de récupérations de mémoire.

    Pour mesurer l'impact de l'optimisation, dans la chronologie du profileur, mesurez le délai entre les récupérations de mémoire. Vous pouvez alors constater que les récupérations de mémoire prennent plus de temps.

    Les conséquences finales des améliorations de la mémoire sont les suivantes:

    • Les arrêts dus à une 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 dans le 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.