Présentation de la mesure des performances des applications

Cet article 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 situations les plus courantes :

  • Défilement par à-coups
    • "À-coup" est le terme utilisé pour décrire 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 (60 Hz ou plus). Les à-coups sont particulièrement visibles en cas de défilement, alors que le flux d'animation doit être fluide et que le mouvement s'interrompt pendant la lecture d'un ou 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.
  • 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.
    • Vous devez viser les deux objectifs de démarrage suivants dans vos applications :

      • Démarrage à froid inférieur à 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é supprimé par l'utilisateur ou le système.

        En revanche, un "démarrage à chaud" 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 de commencer à froid en 500 ms au maximum.

      • Des latences P95/P99 très proches de la latence médiane. Lorsque le démarrage de l'application est parfois très long, la confiance des utilisateurs s'érode. Les IPC et les E/S inutiles lors du processus critique de démarrage d'une application peuvent subir des conflits de verrouillage et introduire ces incohérences.

  • 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 comporter des animations fluides et ne pas inclure de délai ni de scintillement visuel.
  • Inefficacité énergétique

    • Les tâches inutiles consomment et 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, 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 qu'auparavant, en particulier pour les objets temporaires. Ainsi, là où il était recommandé d'éviter d'allouer des objets dans la mesure du possible, il est maintenant recommandé de faire ce qui a le plus de sens pour 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. Il est donc important de savoir si vous allouez de nombreux objets dans votre boucle interne, ce qui pourrait entraîner des problèmes de performances.

Identifier les problèmes

Voici le workflow recommandé pour identifier et résoudre les problèmes de performances :

  • Identifier les critical user journeys (CUJ) à inspecter. Ces derniers peuvent inclure :
    • 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.
  • Inspecter ce qui se passe au cours de ces flux à l'aide des outils de débogage :
    • Systrace ou Perfetto : permet de voir exactement ce qui se passe sur l'ensemble de l'appareil, à l'aide de données temporelles précises.
    • Profileur de mémoire : vous permet de voir quelles allocations de mémoire se produisent sur le segment 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.

Le débogage manuel des exécutions de test individuelles est essentiel à la compréhension et au débogage de ces problèmes de performances. Les étapes ci-dessus ne peuvent pas être remplacées par l'analyse de données agrégées. Toutefois, il est également important de configurer la collecte de métriques dans les tests automatisés et sur le terrain afin de comprendre ce que les utilisateurs voient réellement et d'identifier à quel moment les régressions peuvent se produire :

  • Flux de démarrage
  • À-coups
    • Métriques de champ
      • Données essentielles liées aux frames de la Play Console : notez qu'il n'est pas possible de limiter les métriques à un parcours utilisateur spécifique depuis la Play Console, car 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
      • Jetpack Macrobenchmark : défilement
      • Macrobenchmark collecte le temps de rendu à l'aide de commandes dumpsys gfxinfo qui regroupent un seul parcours utilisateur. Il s'agit d'un moyen raisonnable 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.

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 (environ 5 μs) par section. Par conséquent, ne l'utilisez pas pour toutes les méthodes. Le simple fait de tracer des segments de tâche plus larges (> 0,1 ms) peut fournir des informations importantes sur les goulots d'étranglement.

Remarques sur l'APK

Attention : Ne mesurez pas les performances sur une version de débogage.

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 non linéaire 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 du code de niveau production. Selon les ressources utilisées par votre application, cela peut avoir un impact important sur les performances. Notez que 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 (en général, vitesse ou profil de vitesse). L'activité JIT en arrière-plan peut avoir un impact important sur les performances, surtout lorsque vous réinstallez l'APK entre deux exécutions de test. Pour ce faire, procédez comme suit :

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

Le mode de compilation "vitesse" permet de compiler entièrement l'application. Le mode "profil de vitesse" compile 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 ici :

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

Notez que Macrobenchmark vous permet de spécifier directement un mode de compilation.

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 pour configurer le GPU
  • Désactivation de la limitation thermique

Cette option n'est pas recommandée pour les tests portant sur l'expérience utilisateur (tels que le lancement d'une application, les tests DoU et les tests d'à-coups), mais elle peut s'avérer essentielle pour réduire le bruit dans les tests de microbenchmarks.

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 vous pouvez le voir dans 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

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 ce cas, 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

Vous pouvez également remarquer qu'une pile d'appels spécifique représente la majeure partie des allocations lorsque vous utilisez 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 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 facilement les images problématiques du point de vue de votre application.

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

alt_text

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

Si cette cadence régulière présente des perturbations, cela correspond à des frames saccadés :

alt_text

Avec un peu d'entraînement, vous pourrez les voir facilement.

alt_text

Dans certains cas, vous devez effectuer un zoom avant sur ce 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

  • Invalidation inutile de l'ensemble des données de sauvegarde de RecyclerView. Cela peut entraîner de longs délais d'affichage et donc des à-coups. En revanche, invalidez uniquement les données qui ont été modifiées afin de réduire le nombre de vues à mettre à jour.
    • 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.
  • Échec de la prise en charge des RecyclerView imbriqués, entraînant la recréation complète de la propriété RecyclerView interne à chaque fois.
    • Chaque RecyclerView imbriqué interne doit disposer d'un RecycledViewPool défini pour garantir le recyclage des vues entre les RecyclerView internes.
  • La récupération préalable de données insuffisante ou non opportune Il peut être pénible d'atteindre rapidement le bas d'une liste déroulante et d'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, il peut être utile d'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.