É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 l'utilisation du processeur et l'activité des threads de l'application en temps réel.

Deux indicateurs de performances sont visibles par les utilisateurs :

  • Des performances prévisibles et perceptibles : L'interface utilisateur (UI) perd-elle des images ou s'affiche-t-elle de manière constante à 60 FPS ? Le contenu audio est-il lu sans artefacts ni grésillements ? Quel est le délai entre le moment où l'utilisateur touche l'écran et le moment où l'effet s'affiche ?
  • Durée requise pour les opérations plus longues (comme l'ouverture d'applications).

La première est plus visible que la seconde. Les utilisateurs remarquent généralement les saccades, mais ils ne peuvent pas faire la différence entre un temps de démarrage de l'application de 500 ms et de 600 ms, 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 maintenir le pipeline d'UI fonctionnel. Cela signifie que le pipeline d'UI doit préempter tout autre travail qui n'est pas nécessaire pour une UI fluide. Pour maintenir une UI fluide, la synchronisation en arrière-plan, la distribution des notifications et les tâches similaires doivent toutes être retardées si le travail de l'UI peut être exécuté. Il est acceptable de sacrifier les performances des opérations plus longues (durée d'exécution HDR+, démarrage de l'application, etc.) pour maintenir une interface utilisateur fluide.

Capacité et gigue

Lorsque vous examinez les performances des appareils, la capacité et le jitter sont deux métriques importantes.

Capacité

La capacité correspond à la quantité totale d'une ressource dont dispose l'appareil pendant une certaine période. Il peut s'agir de ressources de processeur, de GPU, d'E/S, de réseau, de bande passante mémoire ou de toute autre métrique similaire. Lorsque vous examinez les performances de l'ensemble du système, il peut être utile d'abstraire les composants individuels et de supposer qu'une seule métrique détermine les performances (en particulier lors de l'optimisation 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 processeur/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 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. Les moyens d'y parvenir se limitent aux améliorations du temps d'exécution (framework Android, ART, Bionic, compilateur/pilotes GPU, noyau).

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 le document intitulé The Case of the Missing Supercomputer Performance: Achieving Optimal Performance on the 8,192 processors of ASCI Q. (Il s'agit d'une étude sur les raisons pour lesquelles le superordinateur ASCI Q n'a pas atteint les performances attendues. C'est une excellente introduction à l'optimisation des grands systèmes.)

Cette page utilise le terme "gigue" pour décrire ce que le document ASCI Q appelle bruit. Le jitter est un comportement aléatoire du système qui empêche l'exécution de tâches perceptibles. Il s'agit souvent d'un travail qui doit être exécuté, mais qui peut ne pas avoir d'exigences de timing strictes qui l'obligent à s'exécuter à un moment précis. Comme il est aléatoire, il est extrêmement difficile de prouver l'inexistence de gigue pour une charge de travail donnée. Il est également extrêmement difficile de prouver qu'une source connue de gigue était à l'origine d'un problème de performances particulier. Les outils les plus couramment utilisés pour diagnostiquer les causes du 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 :

  • Retard du programmeur
  • Gestionnaires d'interruptions
  • Code du pilote s'exécutant trop longtemps avec la préemption ou les interruptions désactivées
  • Softirqs de longue durée
  • Conflit de verrouillage (application, framework, pilote du noyau, verrouillage Binder, verrouillage mmap)
  • Contention de descripteur de fichier où un thread de faible priorité détient le verrou sur un fichier, empêchant l'exécution d'un thread de haute priorité
  • Exécution de code essentiel à l'UI dans des files d'attente de tâches où il pourrait être retardé
  • Transitions d'inactivité du processeur
  • Journalisation
  • Retards d'E/S
  • Création de processus inutiles (par exemple, diffusions CONNECTIVITY_CHANGE)
  • Thrashing du cache de page causé par une mémoire libre insuffisante

La durée requise pour une période de gigue 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 sur 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 gigue. Par conséquent, des processeurs plus rapides n'améliorent généralement pas les performances dans les situations où la gigue est limitée.

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

Consommation de mémoire

La consommation de mémoire est traditionnellement considérée comme responsable des mauvaises performances. Bien que la consommation en elle-même ne soit pas un problème de performances, elle peut entraîner des saccades en raison de la surcharge de lowmemorykiller, des redémarrages de service et du thrashing du cache de page. La réduction de la consommation de mémoire peut éviter les causes directes de mauvaises performances, mais il peut exister d'autres améliorations ciblées qui évitent également ces causes (par exemple, l'épinglage du framework pour l'empêcher d'être paginé lorsqu'il le sera peu de temps après).

Analyser les performances initiales de l'appareil

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

Utilisez 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 prend en charge le point de trace sched_blocked_reason, ainsi que d'autres points de trace dans le pipeline d'affichage qui indiquent quand le frame est transmis à l'écran.
  3. Effectuez de longues traces de l'ensemble du pipeline d'UI (de la réception de l'entrée via une IRQ à la sortie de balayage finale) tout en exécutant une charge de travail légère et cohérente (par exemple, UiBench ou le test de la balle dans TouchLatency).
  4. Corrigez les pertes d'images 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 courir sans perte d'images pendant au moins 20 secondes.
  6. Passez à d'autres sources de saccades visibles par l'utilisateur.

Voici d'autres actions simples que vous pouvez effectuer au début de la mise en service de l'appareil :

  • Assurez-vous que votre noyau dispose du patch tracepoint sched_blocked_reason. Ce point de trace est activé avec la catégorie de trace sched dans systrace et fournit la fonction responsable de la mise en veille lorsque ce thread entre en veille non interruptible. Elle est essentielle pour l'analyse des performances, car le sommeil ininterrompu est un indicateur très courant de gigue.
  • Assurez-vous de disposer d'un traçage suffisant pour les pipelines GPU et d'affichage. Sur les SoC Qualcomm récents, les tracepoints sont activés à l'aide de :
  • 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. Vous pouvez ainsi afficher des informations supplémentaires dans la trace concernant 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 Comprendre systrace).

    Ce que vous attendez de ce type de traçage d'affichage, c'est un événement unique qui indique directement qu'un frame a été envoyé à l'écran. Vous pouvez alors déterminer si vous avez atteint votre temps de frame avec succès.Si l'événement Xn se produit moins de 16, 7 ms après l'événement Xn-1 (en supposant un écran de 60 Hz), vous savez que vous n'avez pas saccadé. Si votre SOC ne fournit pas de tels signaux, contactez votre fournisseur pour les obtenir. Il est extrêmement difficile de déboguer le jitter 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 traiter les benchmarks comme un proxy pour les performances perçues de l'appareil.

D'après les expériences avec les SoC, les différences de performances des benchmarks synthétiques entre les SoC ne sont pas corrélées à une différence similaire dans les performances perceptibles de l'UI (nombre d'images perdues, temps d'image au 99e centile, etc.). Les benchmarks synthétiques sont des benchmarks de capacité uniquement. La gigue n'a d'impact sur les performances mesurées de ces benchmarks qu'en volant du temps à l'opération groupée du benchmark. Par conséquent, les scores de benchmark synthétiques sont pour la plupart sans intérêt en tant que métrique des performances perçues par les utilisateurs.

Prenons l'exemple de deux SoC exécutant Benchmark X, qui affiche 1 000 frames d'UI et indique le temps de rendu total (un score plus faible est préférable).

  • SOC 1 affiche chaque frame de Benchmark X en 10 ms et obtient un score de 10 000.
  • SOC 2 affiche 99 % des frames en 1 ms,mais 1 % des frames en 100 ms et obtient un score de 19 900, ce qui est nettement mieux.

Si le benchmark est représentatif des performances réelles de l'UI, SOC 2 serait inutilisable. En supposant une fréquence d'actualisation de 60 Hz, SOC 2 aurait un frame saccadé toutes les 1,5 s de fonctionnement. Pendant ce temps, le SOC 1 (le SOC le plus lent selon Benchmark X) serait parfaitement fluide.

Utiliser les rapports de bug

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

Utiliser TouchLatency

Plusieurs exemples de mauvais comportements proviennent de TouchLatency, qui est la charge de travail périodique préférée utilisée pour les Pixel et 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 aussi simple qu'il y paraît : une balle rebondit sur l'écran indéfiniment, quelle que soit l'action de l'utilisateur. C'est généralement aussi le test le plus difficile à exécuter parfaitement, mais plus il s'approche d'une exécution sans perte d'images, plus votre appareil sera performant. 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 régulateur de fréquence ; si l'appareil fonctionne plutôt avec des fréquences fixes, réduisez la fréquence du processeur/GPU à une valeur proche du minimum lorsque vous exécutez le test de la balle rebondissante pour la première fois). À mesure que le système se calme et que les horloges se rapprochent de l'état inactif, le temps de processeur/GPU requis par frame augmente. Vous pouvez observer la balle et voir les saccades, 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. Pour ce faire, suivez exactement ce qui s'exécute sur le système pendant chaque frame manquant au lieu du pipeline d'UI. Les fréquences d'horloge plus basses amplifient les effets du jitter, car il est plus probable que tout jitter entraîne une perte d'image. Par conséquent, plus TouchLatency est proche de 60 FPS, moins vous risquez de rencontrer des comportements système indésirables qui provoquent des saccades sporadiques et difficiles à reproduire dans les applications plus volumineuses.

Comme le jitter est souvent (mais pas toujours) invariant à 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 :

  • Tous les tremblements ne sont pas invariants à la fréquence d'horloge. De nombreuses sources consomment simplement du temps de processeur.
  • Le gouverneur devrait faire en sorte que le temps de frame moyen soit proche du délai en réduisant la fréquence d'horloge. Le temps passé à exécuter des tâches non liées à l'UI peut donc le faire dépasser la limite et entraîner la perte d'un frame.