Étude de cas: comment l'équipe Gmail Wear OS a amélioré le démarrage de son application de 50%

Le démarrage de l'application représente la première impression de votre application auprès des utilisateurs. De plus, les utilisateurs n'aiment pas attendre. Vous devez donc vous assurer que votre application démarre rapidement. Pour vous montrer comment une équipe de développement d'applications réelle a identifié et diagnostiqué des problèmes au démarrage de son application, voici ce qu'a fait l'équipe Gmail Wear OS.

L'équipe Gmail Wear OS a entrepris des efforts d'optimisation, en se concentrant particulièrement sur les performances de démarrage et d'affichage de l'environnement d'exécution, afin de répondre aux critères de performances de son équipe. Toutefois, même si vous n'avez pas de seuils spécifiques à cibler, il est presque toujours possible d'améliorer le démarrage de l'application si vous prenez le temps de l'examiner.

Capturer une trace et examiner le démarrage de l'application

Pour commencer l'analyse, capturez une trace qui inclut le démarrage de l'application afin de l'examiner de plus près dans Perfetto ou Android Studio. Cette étude de cas utilise Perfetto, car elle vous montre ce qui se passe sur le système de l'appareil, au-delà de votre application. Lorsque vous importez la trace dans Perfetto, elle se présente comme suit:

Figure 1. Vue initiale de la trace dans Perfetto

Étant donné que l'objectif est d'améliorer le démarrage de l'application, localisez la ligne contenant la métrique personnalisée Démarrage des applications Android. Il est utile de l'épingler en haut de votre vue en cliquant sur l'icône en forme d'épingle qui s'affiche lorsque vous pointez sur la ligne. La barre, ou tranche, que vous voyez sur la ligne Démarrages des applications Android indique la période couverte par le démarrage de l'application jusqu'à ce que le premier frame de l'application soit dessiné à l'écran. Vous devez donc rechercher les problèmes ou les goulots d'étranglement.

Ligne "Android App Startups" avec l'option d'épinglage mise en évidence.
Figure 2 : Épinglez la métrique personnalisée "Démarrage de l'application Android" en haut de votre tableau de bord pour faciliter l'analyse.

Notez que la métrique Démarrages des applications Android représente le délai d'affichage initial, même si vous utilisez reportFullyDrawn(). Pour identifier le délai d'affichage total, recherchez reportFullyDrawn() dans le champ de recherche de Perfetto.

Vérifier le thread principal

Tout d'abord, examinez ce qui se passe sur le thread principal. Le thread principal est très important, car c'est généralement là que se produit tout le rendu de l'interface utilisateur. Lorsqu'il est bloqué, aucun dessin ne peut se produire et votre application semble bloquée. Vous devez donc vous assurer qu'aucune opération de longue durée n'est exécutée sur le thread principal.

Pour trouver le thread principal, recherchez la ligne contenant le nom du package de votre application et développez-la. Les deux lignes portant le même nom que le package (généralement les deux premières lignes de la section) représentent le thread principal. Parmi les deux lignes principales des threads, la première représente l'état du processeur et la seconde les points de trace. Épinglez les deux lignes du thread principal sous la métrique Démarrages de l'application Android.

Démarrages de l'application Android et lignes de threads principales épinglées.
Figure 3. Épinglez les lignes du thread principal sous la métrique personnalisée "Démarrage de l'application Android" pour faciliter l'analyse.

Temps passé à l'état exécutable et en conflit avec le processeur

Pour obtenir une vue globale de l'activité du processeur au démarrage de l'application, faites glisser votre curseur sur le thread principal pour capturer la période de démarrage de l'application. Le panneau Thread States (État des threads) qui s'affiche indique le temps total passé dans chaque état du processeur au cours de la période sélectionnée.

Examinez le temps passé à l'état Runnable. Lorsqu'un thread est à l'état Runnable, il peut effectuer des tâches, mais aucune tâche n'est planifiée. Cela peut indiquer que l'appareil est soumis à une charge importante et qu'il est incapable de planifier des tâches à priorité élevée. L'application visible par l'utilisateur a la priorité la plus élevée en termes de planification. Par conséquent, un thread principal inactif indique souvent que les processus intensifs de votre application, tels que le rendu de l'animation, sont en concurrence avec le thread principal pour le temps CPU.

Thread principal mis en surbrillance avec la durée totale dans différents états dans le panneau "États des threads".
Figure 4. Évaluez le temps relatif entre les états Runnable et Running pour avoir une première idée de l'ampleur des conflits entre les processeurs.

Plus le ratio entre le temps dans l'état Runnable et le temps dans l'état Running est élevé, plus il est probable qu'il y ait des conflits au niveau du processeur. Lorsque vous inspectez les problèmes de performances de cette manière, concentrez-vous d'abord sur l'image la plus longue et concentrez-vous sur les images les plus petites.

Lorsque vous analysez le temps passé à l'état Runnable, tenez compte du matériel de l'appareil. Étant donné que l'application décrite s'exécute sur un accessoire connecté doté de deux processeurs, on s'attend à ce que le temps passé à l'état Runnable soit plus élevé et qu'il y ait davantage de conflits de CPU avec d'autres processus que si nous avions examiné un appareil avec plus de processeurs. Même si l'état Runnable passe plus de temps que prévu pour une application pour téléphone classique, cela peut être compréhensible dans le contexte des accessoires connectés.

Temps passé dans OpenDexFilesFromOat*

Vérifiez maintenant le temps passé dans OpenDexFilesFromOat*. Dans la trace, cela se produit en même temps que la tranche bindApplication. Cette tranche représente le temps nécessaire pour lire les fichiers DEX de l'application.

Transactions de liaison bloquées

Vérifiez ensuite les transactions de liaison. Les transactions de liaison représentent des appels entre le client et le serveur: dans ce cas, l'application (client) appelle le système Android (serveur) avec un binder transaction, et le serveur répond avec un binder reply. Assurez-vous que l'application n'effectue pas de transactions de liaison inutiles au démarrage, car elles augmentent le risque de conflit avec le processeur. Si vous le pouvez, différez les tâches qui impliquent des appels de liaison après le temps de démarrage de l'application. Si vous devez effectuer des transactions de liaison, assurez-vous qu'elles ne prennent pas plus de temps que la fréquence d'actualisation Vsync de votre appareil.

La première transaction de liaison, qui se produit généralement en même temps que la tranche ActivityThreadMain, semble être assez longue dans ce cas. Pour en savoir plus sur ce qui peut se produire, procédez comme suit:

  1. Pour afficher la réponse de liaison associée et en savoir plus sur la priorité de la transaction de liaison, cliquez sur la portion de la transaction de liaison qui vous intéresse.
  2. Pour voir la réponse de liaison, accédez au panneau Current Selection (Sélection actuelle), puis cliquez sur binder response (Réponse de liaison) dans la section Following threads (Suivis des fils de discussion). Le champ Thread vous indique également le thread sur lequel la réponse de liaison se produit si vous souhaitez y accéder manuellement. Il s'agira d'un processus différent. Une ligne qui s'affiche connecte la transaction de liaison et la réponse.

    Une ligne relie l'appel de liaison et sa réponse.
    Figure 5. Identifiez les transactions de liaison qui se produisent au démarrage de l'application et déterminez si vous pouvez les reporter.
  3. Pour voir comment le serveur système gère cette transaction de liaison, épinglez les threads Cpu 0 et Cpu 1 en haut de votre écran.

  4. Recherchez les processus système qui gèrent la réponse de liaison en recherchant les tranches contenant le nom du thread de la réponse de liaison, dans ce cas "Binder:687_11 [2542]". Cliquez sur les processus système concernés pour obtenir plus d'informations sur la transaction de liaison.

Examinez ce processus système associé à la transaction de liaison d'intérêt qui se produit sur le processeur 0:

Processus système avec l'état final "Runnable (préempté)".
Figure 6 : Le processus système est à l'état Runnable (Preempted), ce qui indique qu'il est retardé.

L'état de fin indique Runnable (Preempted), ce qui signifie que le processus est retardé parce que le processeur effectue autre chose. Pour savoir par quoi il est préempté, développez les lignes Ftrace Events (Événements Ftrace). Dans l'onglet Ftrace Events (Événements Ftrace) qui s'affiche, faites défiler les événements et recherchez les événements liés au thread de liaison qui vous intéresse "Binder:687_11 [2542]". Au moment où le processus système est préempté, deux événements de serveur système incluant l'argument "decon" se sont produits, ce qui signifie qu'ils sont liés au contrôleur d'affichage. Cela semble raisonnable, car le contrôleur d'affichage place les cadres à l'écran, ce qui est une tâche importante. Laissez les événements tels quels.

Événements FTrace associés à la transaction de liaison d'intérêt mis en évidence.
Figure 7 : Les événements FTrace indiquent que la transaction de liaison est retardée par les événements du contrôleur d'affichage.

Activité JIT

Pour examiner l'activité de compilation juste à temps (JIT), développez les processus appartenant à votre application, recherchez les deux lignes "Pool de threads Jit" et épinglez-les en haut de votre vue. Comme cette application bénéficie des profils de référence au démarrage de l'application, très peu d'activités JIT se produisent jusqu'à ce que le premier frame soit dessiné, c'est-à-dire à la fin du premier appel Choreographer.doFrame. Toutefois, notez le motif de démarrage lent JIT compiling void, qui suggère que l'activité du système se produisant pendant le point de trace étiqueté Application creation est à l'origine de beaucoup d'activités JIT en arrière-plan. Pour résoudre ce problème, ajoutez les événements qui se produisent peu de temps après le dessin du premier frame dans le profil de référence en développant la collection de profils jusqu'à ce que l'application soit prête à être utilisée. Dans de nombreux cas, vous pouvez le faire en ajoutant une ligne à la fin du test Macrobenchmark de votre collection de profils de référence, qui attend qu'un widget d'interface utilisateur particulier apparaisse à l'écran, ce qui indique que l'écran est entièrement rempli.

Pools de threads Jit avec la tranche "Jit compilation vide" encadrée.
Figure 8 : Si vous constatez de nombreuses activités JIT, développez votre profil de référence jusqu'à ce que l'application soit prête à être utilisée.

Résultats

À la suite de cette analyse, l'équipe Gmail Wear OS a apporté les améliorations suivantes:

  • Étant donné qu'elle a remarqué des conflits au démarrage de l'application en observant l'activité du processeur, elle a remplacé l'animation de l'icône de chargement utilisée pour indiquer que l'application se charge avec une seule image statique. Ils ont également prolongé l'écran de démarrage pour différer l'état de scintillement, le deuxième état d'écran utilisé pour indiquer que l'application est en cours de chargement, afin de libérer des ressources de processeur. Cela a permis d'améliorer la latence de démarrage de l'application de 50%.
  • En examinant le temps passé dans OpenDexFilesFromOat* et l'activité JIT, ils ont activé la réécriture R8 des profils de référence. Cela a permis d'améliorer la latence de démarrage des applications de 20%.

Voici quelques conseils de l'équipe pour analyser efficacement les performances des applications:

  • Configurez un processus continu capable de collecter automatiquement les traces et les résultats. Envisagez de configurer le traçage automatisé pour votre application à l'aide de l'analyse comparative.
  • Utilisez les tests A/B pour les modifications qui, selon vous, amélioreront les choses, et refusez-les si ce n'est pas le cas. Vous pouvez mesurer les performances dans différents scénarios à l'aide de la bibliothèque Macrobenchmark.

Pour en savoir plus, consultez les ressources suivantes :