Évaluer les performances

Utilisez Simpleperf pour évaluer les performances d'un appareil. Simpleperf est un outil de profilage natif pour les applications et les processus natifs sur Android. Utilisez le Profileur de processeur pour inspecter en temps réel l'utilisation du processeur et l'activité des threads de votre application.

Il existe deux indicateurs de performances visibles par l'utilisateur:

  • Performances prévisibles et perceptibles L'interface utilisateur (UI) perd-elle des images ou effectue-t-elle un rendu constant à 60 FPS ? Le contenu audio est-il lu sans artefacts ni clignotement ? Quel est le délai entre le moment où l'utilisateur touche l'écran et le moment où l'effet s'affiche à l'écran ?
  • Durée requise pour les opérations plus longues (telles que l'ouverture d'applications).

La première est plus visible que la seconde. Les utilisateurs remarquent généralement les à-coups, mais ils ne peuvent pas faire la différence entre 500 ms et 600 ms de temps de démarrage de l'application, sauf s'ils regardent deux appareils côte à côte. La latence tactile est immédiatement perceptible et contribue de manière significative à la perception d'un appareil.

Par conséquent, sur un appareil rapide, le pipeline d'UI est l'élément le plus important du système, à l'exception de ce qui est nécessaire pour le maintenir opérationnel. Cela signifie que le pipeline d'interface utilisateur doit préempter toute autre tâche qui n'est pas nécessaire pour une interface utilisateur fluide. Pour maintenir une interface utilisateur fluide, la synchronisation en arrière-plan, la diffusion de notifications et les tâches similaires doivent toutes être retardées si la tâche de l'UI peut être exécutée. Il est acceptable de sacrifier les performances des opérations plus longues (temps d'exécution HDR+, démarrage de l'application, etc.) pour maintenir une interface utilisateur fluide.

Capacité par rapport au jitter

En ce qui concerne les performances de l'appareil, la capacité et le jitter sont deux métriques importantes.

Capacité

La capacité correspond à la quantité totale d'une ressource dont dispose l'appareil sur une période donnée. Il peut s'agir de ressources de processeur, de GPU, d'E/S, de réseau, de bande passante de mémoire ou de toute métrique similaire. Lorsque vous examinez les performances de l'ensemble du système, il peut être utile d'extraire les composants individuels et de supposer qu'une seule métrique détermine les performances (en particulier lors du réglage d'un nouvel appareil, car les charges de travail exécutées sur cet appareil sont probablement fixes).

La capacité d'un système varie en fonction des ressources de calcul en ligne. La modification de la fréquence du CPU/GPU est le principal moyen de modifier la capacité, mais il en existe d'autres, comme la modification du nombre de cœurs de processeur en ligne. Par conséquent, la capacité d'un système correspond à la consommation d'énergie. Toute modification de la capacité entraîne toujours une modification similaire de la consommation d'énergie.

La capacité requise à un moment donné est principalement déterminée par l'application en cours d'exécution. Par conséquent, la plate-forme ne peut pas faire grand-chose pour ajuster la capacité requise pour une charge de travail donnée, et les moyens de le faire sont limités aux améliorations d'exécution (framework Android, ART, Bionic, compilateur/pilotes GPU, kernel).

Gigue

Bien que la capacité requise pour une charge de travail soit facile à identifier, le jitter est un concept plus nébuleux. Pour une bonne introduction au jitter en tant qu'obstacle aux systèmes rapides, nous vous recommandons de lire l'article intitulé The Case of the Missing Supercomputer Performance: Achieving Optimal Performance on the 8,192 processors of ASCI Q. (Il s'agit d'une analyse des raisons pour lesquelles le supercalculateur ASCI Q n'a pas atteint les performances attendues. Il s'agit d'une excellente introduction à l'optimisation de grands systèmes.)

Cette page utilise le terme "jitter" pour décrire ce que l'article ASCI Q appelle le bruit. Le jitter est le comportement aléatoire du système qui empêche l'exécution d'un travail perceptible. Il s'agit souvent d'une tâche qui doit être exécutée, mais qui n'a pas nécessairement de contraintes temporelles strictes qui l'obligent à s'exécuter à un moment donné. Étant donné qu'il est aléatoire, il est extrêmement difficile de réfuter l'existence de jitter pour une charge de travail donnée. Il est également extrêmement difficile de prouver qu'une source connue de jitter était à l'origine d'un problème de performances particulier. Les outils les plus couramment utilisés pour diagnostiquer les causes de jitter (tels que le traçage ou la journalisation) peuvent introduire leur propre jitter.

Voici quelques sources de jitter rencontrées dans les implémentations réelles d'Android:

  • Délai du planificateur
  • Gestionnaires d'interruptions
  • Code du pilote exécuté trop longtemps avec la préemption ou les interruptions désactivées
  • Softirqs de longue durée
  • Conflits de verrouillage (application, framework, pilote de kernel, verrouillage de liaison, verrouillage mmap)
  • Conflit de descripteur de fichier où un thread de faible priorité détient le verrouillage d'un fichier, empêchant l'exécution d'un thread de priorité élevée
  • Exécuter du code critique pour l'interface utilisateur dans des files d'attente où il pourrait être retardé
  • Transitions d'inactivité du processeur
  • Journalisation
  • Retards d'E/S
  • Création de processus inutile (par exemple, diffusions CONNECTIVITY_CHANGE)
  • Épuisement du cache de page en raison d'une mémoire libre insuffisante

La durée requise pour une période de jitter donnée peut ou non diminuer à mesure que la capacité augmente. Par exemple, si un pilote laisse les interruptions désactivées en attendant une lecture à partir d'un bus I2C, cela prendra un temps fixe, que le processeur soit à 384 MHz ou à 2 GHz. Augmenter la capacité n'est pas une solution viable pour améliorer les performances en cas de jitter. Par conséquent, les processeurs plus rapides n'améliorent généralement pas les performances dans des situations où le jitter est limité.

Enfin, contrairement à la capacité, le jitter relève presque entièrement du domaine du fournisseur du système.

Consommation de mémoire

La consommation de mémoire est généralement mise en cause pour les performances médiocres. Bien que la consommation elle-même ne soit pas un problème de performances, elle peut entraîner des à-coups via les frais généraux de lowmemorykiller, les redémarrages de service et le balayage du cache de page. Réduire la consommation de mémoire peut éviter les causes directes des mauvaises performances, mais d'autres améliorations ciblées peuvent également éviter ces causes (par exemple, épingler le framework pour l'empêcher d'être mis en page lorsqu'il sera mis en page peu de temps après).

Analyser les performances initiales de l'appareil

Partir d'un système fonctionnel, mais aux performances médiocres, et tenter de corriger son comportement en examinant des cas individuels de mauvaises performances visibles par l'utilisateur n'est pas une stratégie judicieuse. Étant donné que les mauvaises performances ne sont généralement pas faciles à reproduire (c'est-à-dire le jitter) ou qu'il s'agit d'un problème d'application, trop de variables dans l'ensemble du système empêchent cette stratégie d'être efficace. Par conséquent, il est très facile de mal identifier les causes et d'apporter des améliorations mineures, tout en manquant d'opportunités systémiques pour corriger les performances dans l'ensemble du système.

Suivez plutôt l'approche générale suivante lorsque vous configurez un nouvel appareil:

  1. Démarrez le système sur l'UI avec tous les pilotes en cours d'exécution et certains paramètres de base du gouverneur de fréquence (si vous modifiez les paramètres du gouverneur de fréquence, répétez toutes les étapes ci-dessous).
  2. Assurez-vous que le noyau est compatible avec le point de trace sched_blocked_reason, ainsi que les autres points de trace du pipeline d'affichage qui indiquent quand le frame est envoyé à l'écran.
  3. Effectuez des traces longues de l'ensemble du pipeline d'interface utilisateur (de la réception des entrées via un IRQ à la sortie finale) lorsque vous exécutez une charge de travail légère et cohérente (par exemple, UiBench ou le test de la balle dans TouchLatency).
  4. Correction des baisses de frame détectées dans la charge de travail légère et cohérente.
  5. Répétez les étapes 3 et 4 jusqu'à ce que vous puissiez exécuter le rendu sans perte de frame pendant plus de 20 secondes à la fois.
  6. Passez aux autres sources de saccades visibles par l'utilisateur.

Vous pouvez également effectuer d'autres actions simples au début de la mise en service de l'appareil:

  • Assurez-vous que votre kernel dispose du correctif de tracepoint sched_blocked_reason. Ce point de trace est activé avec la catégorie de trace sched dans systrace et fournit la fonction chargée de la mise en veille lorsque ce thread passe en veille ininterruptible. Il est essentiel pour l'analyse des performances, car le sommeil ininterrompu est un indicateur très courant de jitter.
  • Assurez-vous de disposer d'un traçage suffisant pour les pipelines GPU et d'affichage. Sur les SOC Qualcomm récents, les points de trace sont activés à l'aide des éléments suivants:
  • adb shell "echo 1 > /d/tracing/events/kgsl/enable"
    adb shell "echo 1 > /d/tracing/events/mdss/enable"
    

    Ces événements restent activés lorsque vous exécutez systrace afin que vous puissiez consulter des informations supplémentaires dans la trace sur le pipeline d'affichage (MDSS) dans la section mdss_fb0. Sur les SOC Qualcomm, vous ne verrez aucune information supplémentaire sur le GPU dans la vue systrace standard, mais les résultats sont présents dans la trace elle-même (pour en savoir plus, consultez la section Comprendre systrace).

    Ce que vous voulez de ce type de traçage d'affichage est un seul événement qui indique directement qu'un frame a été envoyé à l'écran. À partir de là, vous pouvez déterminer si vous avez atteint votre temps de frame.Si l'événement Xn se produit moins de 16,7 ms après l'événement Xn-1 (en supposant un écran 60 Hz), vous savez que vous n'avez pas subi de à-coups. Si votre SOC ne fournit pas de tels signaux, collaborez avec votre fournisseur pour les obtenir. Le débogage du jitter est extrêmement difficile sans signal définitif de fin de frame.

Utiliser des benchmarks synthétiques

Les benchmarks synthétiques sont utiles pour s'assurer que les fonctionnalités de base d'un appareil sont présentes. Toutefois, il n'est pas utile de considérer les benchmarks comme un proxy des performances perçues de l'appareil.

D'après les expériences avec les SOC, les différences de performances de référence synthétiques entre les SOC ne sont pas corrélées à une différence similaire des performances de l'UI perceptibles (nombre de frames abandonnés, temps de frame au 99e percentile, etc.). Les benchmarks synthétiques ne sont que des benchmarks de capacité. Le jitter n'a d'impact sur les performances mesurées de ces benchmarks que s'il vole du temps à l'opération groupée du benchmark. Par conséquent, les scores de référence synthétiques sont généralement sans pertinence en tant que métrique des performances perçues par l'utilisateur.

Prenons l'exemple de deux SOC exécutant le benchmark X qui génère 1 000 frames d'interface utilisateur et indique le temps de rendu total (un score plus faible est préférable).

  • Le SOC 1 affiche chaque frame du benchmark X en 10 ms et obtient un score de 10 000.
  • SOC 2 affiche 99% des images en 1 ms, mais 1% des images en 100 ms, et obtient un score de 19 900, ce qui est nettement meilleur.

Si le benchmark est représentatif des performances réelles de l'interface utilisateur, SOC 2 ne sera pas utilisable. En supposant une fréquence d'actualisation de 60 Hz, SOC 2 afficherait un frame saccadé toutes les 1,5 secondes d'opération. En revanche, le SOC 1 (le SOC le plus lent selon le benchmark X) serait parfaitement fluide.

Utiliser les rapports de bug

Les rapports de bugs sont parfois utiles pour l'analyse des performances, mais comme ils sont très lourds, ils sont rarement utiles pour déboguer des problèmes de saccades occasionnels. Ils peuvent fournir des indices sur ce que le système faisait à un moment donné, en particulier si le à-coup était lié à une transition d'application (qui est enregistrée dans un rapport de bug). Les rapports de bugs peuvent également indiquer qu'un problème plus général affecte le système et pourrait réduire sa capacité effective (par exemple, un étranglement thermique ou une fragmentation de la mémoire).

Utiliser TouchLatency

Plusieurs exemples de mauvais comportement proviennent de TouchLatency, qui est la charge de travail périodique privilégiée pour le Pixel et le Pixel XL. Il est disponible sur frameworks/base/tests/TouchLatency et comporte deux modes: latence tactile et balle rebondissante (pour changer de mode, cliquez sur le bouton en haut à droite).

Le test de la balle rebondissante est exactement aussi simple qu'il en a l'air: une balle rebondit sur l'écran indéfiniment, quelle que soit l'entrée de l'utilisateur. Il s'agit généralement aussi du test le plus difficile à exécuter parfaitement, mais plus il s'approche de l'exécution sans perte de frames, meilleur est votre appareil. Le test de la balle rebondissante est difficile, car il s'agit d'une charge de travail triviale, mais parfaitement cohérente, qui s'exécute à une fréquence très basse (cela suppose que l'appareil dispose d'un gouverneur de fréquence. Si l'appareil s'exécute avec des horloges fixes, réduisez la fréquence du processeur/GPU à un niveau proche du minimum lorsque vous exécutez le test de la balle rebondissante pour la première fois). À mesure que le système passe en mode veille et que les horloges se rapprochent de l'inactivité, le temps requis par CPU/GPU par frame augmente. Vous pouvez regarder la balle et voir des à-coups, et vous pourrez également voir les frames manqués dans systrace.

Étant donné que la charge de travail est très cohérente, vous pouvez identifier la plupart des sources de jitter beaucoup plus facilement que dans la plupart des charges de travail visibles par l'utilisateur en suivant exactement ce qui s'exécute sur le système à chaque frame manqué au lieu du pipeline d'interface utilisateur. Les horloges plus basses amplifient les effets du jitter, car il est plus probable qu'un jitter provoque une perte de frame. Par conséquent, plus TouchLatency est proche de 60 FPS, moins vous êtes susceptible de rencontrer de mauvais comportements système qui provoquent des à-coups sporadiques et difficiles à reproduire dans les applications plus volumineuses.

Comme le jitter est souvent (mais pas toujours) indépendant de la fréquence d'horloge, utilisez un test qui s'exécute à des fréquences d'horloge très basses pour diagnostiquer le jitter pour les raisons suivantes:

  • Tout jitter n'est pas invariant par rapport à la fréquence d'horloge. De nombreuses sources ne consomment que du temps de processeur.
  • Le gouverneur doit rapprocher le temps de frame moyen de la limite de temps en réduisant la fréquence d'horloge. Le temps passé à exécuter des tâches autres que l'UI peut donc le pousser à abandonner un frame.