Test de performance

Android 8.0 inclut des tests de performances de binder et hwbinder pour le débit et la latence. Bien qu'il existe de nombreux scénarios permettant de détecter des problèmes de performances perceptibles, leur exécution peut prendre du temps et les résultats ne sont souvent disponibles qu'après l'intégration du système. L'utilisation des tests de performances fournis facilite les tests pendant le développement, détecte plus tôt les problèmes graves et améliore l'expérience utilisateur.

Les tests de performances comprennent les quatre catégories suivantes :

  • débit du classeur (disponible dans system/libhwbinder/vts/performance/Benchmark_binder.cpp )
  • latence du classeur (disponible dans frameworks/native/libs/binder/tests/schd-dbg.cpp )
  • Débit hwbinder (disponible dans system/libhwbinder/vts/performance/Benchmark.cpp )
  • Latence hwbinder (disponible dans system/libhwbinder/vts/performance/Latency.cpp )

À propos du classeur et du hwbinder

Binder et hwbinder sont des infrastructures de communication inter-processus (IPC) Android qui partagent le même pilote Linux mais présentent les différences qualitatives suivantes :

Aspect classeur classeur
But Fournir un schéma IPC à usage général pour le cadre Communiquer avec le matériel
Propriété Optimisé pour l'utilisation du framework Android Faible latence minimale
Modifier la politique de planification pour le premier plan/arrière-plan Oui Non
Arguments passant Utilise la sérialisation prise en charge par l'objet Parcel Utilise des tampons de dispersion et évite la surcharge liée à la copie des données requises pour la sérialisation des colis
Héritage prioritaire Non Oui

Processus de liant et de hwbinder

Un visualiseur systrace affiche les transactions comme suit :

Figure 1. Visualisation Systrace des processus de liant.

Dans l'exemple ci-dessus :

  • Les quatre (4) processus schd-dbg sont des processus clients.
  • Les quatre (4) processus Binder sont des processus serveur (le nom commence par Binder et se termine par un numéro de séquence).
  • Un processus client est toujours associé à un processus serveur, dédié à son client.
  • Toutes les paires de processus client-serveur sont planifiées indépendamment par le noyau simultanément.

Dans le CPU 1, le noyau du système d'exploitation exécute le client pour émettre la requête. Il utilise ensuite le même processeur autant que possible pour réveiller un processus serveur, gérer la requête et revenir en arrière une fois la requête terminée.

Débit et latence

Dans une transaction parfaite, où les processus client et serveur commutent de manière transparente, les tests de débit et de latence ne produisent pas de messages sensiblement différents. Cependant, lorsque le noyau du système d'exploitation gère une demande d'interruption (IRQ) provenant du matériel, attend des verrous ou choisit simplement de ne pas gérer un message immédiatement, une bulle de latence peut se former.

Figure 2. Bulle de latence due aux différences de débit et de latence.

Le test de débit génère un grand nombre de transactions avec différentes tailles de charge utile, fournissant une bonne estimation du temps de transaction régulier (dans le meilleur des cas) et du débit maximum que le classeur peut atteindre.

En revanche, le test de latence n’effectue aucune action sur la charge utile afin de minimiser le temps de transaction normal. Nous pouvons utiliser le temps de transaction pour estimer la surcharge du classeur, établir des statistiques pour le pire des cas et calculer le ratio de transactions dont la latence respecte un délai spécifié.

Gérer les inversions de priorité

Une inversion de priorité se produit lorsqu'un thread de priorité plus élevée attend logiquement un thread de priorité inférieure. Les applications temps réel (RT) ont un problème d'inversion de priorité :

Figure 3. Inversion de priorité dans les applications temps réel.

Lorsque vous utilisez la planification Linux Completely Fair Scheduler (CFS), un thread a toujours une chance de s'exécuter même lorsque d'autres threads ont une priorité plus élevée. En conséquence, les applications avec planification CFS gèrent l'inversion de priorité comme un comportement attendu et non comme un problème. Dans les cas où le framework Android a besoin d'une planification RT pour garantir le privilège des threads haute priorité, l'inversion de priorité doit être résolue.

Exemple d'inversion de priorité lors d'une transaction de classeur (le thread RT est logiquement bloqué par d'autres threads CFS lors de l'attente du service d'un thread de classeur) :

Figure 4. Inversion de priorité, threads en temps réel bloqués.

Pour éviter les blocages, vous pouvez utiliser l'héritage de priorité pour escalader temporairement le thread Binder vers un thread RT lorsqu'il répond à une demande d'un client RT. Gardez à l’esprit que la planification RT dispose de ressources limitées et doit être utilisée avec prudence. Dans un système avec n processeurs, le nombre maximum de threads RT actuels est également n ; des threads RT supplémentaires pourraient devoir attendre (et donc manquer leurs délais) si tous les processeurs sont occupés par d'autres threads RT.

Pour résoudre toutes les inversions de priorité possibles, vous pouvez utiliser l'héritage de priorité pour binder et hwbinder. Cependant, comme le classeur est largement utilisé dans le système, l'activation de l'héritage prioritaire pour les transactions du classeur peut spammer le système avec plus de threads RT qu'il ne peut en traiter.

Exécuter des tests de débit

Le test de débit est exécuté sur le débit des transactions binder/hwbinder. Dans un système qui n’est pas surchargé, les bulles de latence sont rares et leur impact peut être éliminé à condition que le nombre d’itérations soit suffisamment élevé.

  • Le test de débit du classeur se trouve dans system/libhwbinder/vts/performance/Benchmark_binder.cpp .
  • Le test de débit hwbinder se trouve dans system/libhwbinder/vts/performance/Benchmark.cpp .

Résultats de test

Exemples de résultats de tests de débit pour des transactions utilisant différentes tailles de charge utile :

Benchmark                      Time          CPU           Iterations
---------------------------------------------------------------------
BM_sendVec_binderize/4         70302 ns      32820 ns      21054
BM_sendVec_binderize/8         69974 ns      32700 ns      21296
BM_sendVec_binderize/16        70079 ns      32750 ns      21365
BM_sendVec_binderize/32        69907 ns      32686 ns      21310
BM_sendVec_binderize/64        70338 ns      32810 ns      21398
BM_sendVec_binderize/128       70012 ns      32768 ns      21377
BM_sendVec_binderize/256       69836 ns      32740 ns      21329
BM_sendVec_binderize/512       69986 ns      32830 ns      21296
BM_sendVec_binderize/1024      69714 ns      32757 ns      21319
BM_sendVec_binderize/2k        75002 ns      34520 ns      20305
BM_sendVec_binderize/4k        81955 ns      39116 ns      17895
BM_sendVec_binderize/8k        95316 ns      45710 ns      15350
BM_sendVec_binderize/16k      112751 ns      54417 ns      12679
BM_sendVec_binderize/32k      146642 ns      71339 ns       9901
BM_sendVec_binderize/64k      214796 ns     104665 ns       6495
  • Le temps indique le délai aller-retour mesuré en temps réel.
  • CPU indique le temps accumulé pendant lequel les CPU sont planifiés pour le test.
  • Les itérations indiquent le nombre de fois où la fonction de test a été exécutée.

Par exemple, pour une charge utile de 8 octets :

BM_sendVec_binderize/8         69974 ns      32700 ns      21296

… le débit maximum que le liant peut atteindre est calculé comme suit :

Débit MAX avec charge utile de 8 octets = (8 * 21296)/69974 ~= 2,423 b/ns ~= 2,268 Gb/s

Options de test

Pour obtenir les résultats au format .json, exécutez le test avec l'argument --benchmark_format=json :

libhwbinder_benchmark --benchmark_format=json
{
  "context": {
    "date": "2017-05-17 08:32:47",
    "num_cpus": 4,
    "mhz_per_cpu": 19,
    "cpu_scaling_enabled": true,
    "library_build_type": "release"
  },
  "benchmarks": [
    {
      "name": "BM_sendVec_binderize/4",
      "iterations": 32342,
      "real_time": 47809,
      "cpu_time": 21906,
      "time_unit": "ns"
    },
   ….
}

Exécuter des tests de latence

Le test de latence mesure le temps nécessaire au client pour commencer à initialiser la transaction, passer au processus serveur pour le traitement et recevoir le résultat. Le test recherche également les mauvais comportements connus du planificateur qui peuvent avoir un impact négatif sur la latence des transactions, comme un planificateur qui ne prend pas en charge l'héritage de priorité ou qui ne respecte pas l'indicateur de synchronisation.

  • Le test de latence du classeur se trouve dans frameworks/native/libs/binder/tests/schd-dbg.cpp .
  • Le test de latence hwbinder se trouve dans system/libhwbinder/vts/performance/Latency.cpp .

Résultats de test

Les résultats (au format .json) affichent des statistiques sur la latence moyenne/meilleure/pire et le nombre de délais manqués.

Options de test

Les tests de latence prennent les options suivantes :

Commande Description
-i value Spécifiez le nombre d'itérations.
-pair value Spécifiez le nombre de paires de processus.
-deadline_us 2500 Précisez-nous le délai.
-v Obtenez une sortie détaillée (débogage).
-trace Arrêtez la trace en cas de dépassement d'une date limite.

Les sections suivantes détaillent chaque option, décrivent leur utilisation et fournissent des exemples de résultats.

Spécifier les itérations

Exemple avec un grand nombre d'itérations et une sortie détaillée désactivée :

libhwbinder_latency -i 5000 -pair 3
{
"cfg":{"pair":3,"iterations":5000,"deadline_us":2500},
"P0":{"SYNC":"GOOD","S":9352,"I":10000,"R":0.9352,
  "other_ms":{ "avg":0.2 , "wst":2.8 , "bst":0.053, "miss":2, "meetR":0.9996},
  "fifo_ms": { "avg":0.16, "wst":1.5 , "bst":0.067, "miss":0, "meetR":1}
},
"P1":{"SYNC":"GOOD","S":9334,"I":10000,"R":0.9334,
  "other_ms":{ "avg":0.19, "wst":2.9 , "bst":0.055, "miss":2, "meetR":0.9996},
  "fifo_ms": { "avg":0.16, "wst":3.1 , "bst":0.066, "miss":1, "meetR":0.9998}
},
"P2":{"SYNC":"GOOD","S":9369,"I":10000,"R":0.9369,
  "other_ms":{ "avg":0.19, "wst":4.8 , "bst":0.055, "miss":6, "meetR":0.9988},
  "fifo_ms": { "avg":0.15, "wst":1.8 , "bst":0.067, "miss":0, "meetR":1}
},
"inheritance": "PASS"
}

Ces résultats de tests montrent ce qui suit :

"pair":3
Crée une paire client et serveur.
"iterations": 5000
Comprend 5 000 itérations.
"deadline_us":2500
Le délai est de 2 500 us (2,5 ms) ; la plupart des transactions devraient atteindre cette valeur.
"I": 10000
Une seule itération de test comprend deux (2) transactions :
  • Une transaction par priorité normale ( CFS other )
  • Une transaction par priorité temps réel ( RT-fifo )
5 000 itérations équivalent à un total de 10 000 transactions.
"S": 9352
9 352 transactions sont synchronisées dans le même processeur.
"R": 0.9352
Indique le taux de synchronisation du client et du serveur dans le même processeur.
"other_ms":{ "avg":0.2 , "wst":2.8 , "bst":0.053, "miss":2, "meetR":0.9996}
La moyenne ( avg ), le pire ( wst ) et le meilleur ( bst ) pour toutes les transactions émises par un appelant prioritaire normal. Deux transactions miss la date limite, ce qui porte le ratio de rencontre ( meetR ) à 0,9996.
"fifo_ms": { "avg":0.16, "wst":1.5 , "bst":0.067, "miss":0, "meetR":1}
Similaire à other_ms , mais pour les transactions émises par le client avec la priorité rt_fifo . Il est probable (mais pas obligatoire) que le fifo_ms ait un meilleur résultat que other_ms , avec des valeurs avg et wst inférieures et un meetR plus élevé (la différence peut être encore plus significative avec une charge en arrière-plan).

Remarque : La charge en arrière-plan peut avoir un impact sur le résultat du débit et sur le tuple other_ms dans le test de latence. Seul le fifo_ms peut afficher des résultats similaires tant que le chargement en arrière-plan a une priorité inférieure à RT-fifo .

Spécifier les valeurs de paire

Chaque processus client est associé à un processus serveur dédié au client, et chaque paire peut être planifiée indépendamment sur n'importe quel processeur. Cependant, la migration du processeur ne devrait pas avoir lieu pendant une transaction tant que l'indicateur SYNC est honor .

Assurez-vous que le système n'est pas surchargé ! Même si une latence élevée est attendue dans un système surchargé, les résultats des tests pour un système surchargé ne fournissent pas d’informations utiles. Pour tester un système avec une pression plus élevée, utilisez -pair #cpu-1 (ou -pair #cpu avec prudence). Les tests utilisant -pair n avec n > #cpu surchargent le système et génèrent des informations inutiles.

Spécifier les valeurs de délai

Après des tests approfondis de scénarios utilisateur (exécution du test de latence sur un produit qualifié), nous avons déterminé que 2,5 ms était le délai à respecter. Pour les nouvelles candidatures ayant des exigences plus élevées (telles que 1 000 photos/seconde), cette valeur limite changera.

Spécifier une sortie détaillée

L’utilisation de l’option -v affiche une sortie détaillée. Exemple:

libhwbinder_latency -i 1 -v

-------------------------------------------------- service pid: 8674 tid: 8674 cpu: 1 SCHED_OTHER 0
-------------------------------------------------- main pid: 8673 tid: 8673 cpu: 1 -------------------------------------------------- client pid: 8677 tid: 8677 cpu: 0 SCHED_OTHER 0
-------------------------------------------------- fifo-caller pid: 8677 tid: 8678 cpu: 0 SCHED_FIFO 99 -------------------------------------------------- hwbinder pid: 8674 tid: 8676 cpu: 0 ??? 99
-------------------------------------------------- other-caller pid: 8677 tid: 8677 cpu: 0 SCHED_OTHER 0 -------------------------------------------------- hwbinder pid: 8674 tid: 8676 cpu: 0 SCHED_OTHER 0
  • Le thread de service est créé avec une priorité SCHED_OTHER et exécuté dans CPU:1 avec pid 8674 .
  • La première transaction est ensuite lancée par un fifo-caller . Pour traiter cette transaction, le hwbinder met à niveau la priorité du serveur ( pid: 8674 tid: 8676 ) à 99 et le marque également avec une classe de planification transitoire (imprimée sous la forme ??? ). Le planificateur place ensuite le processus serveur dans CPU:0 pour l'exécuter et le synchronise avec le même processeur que son client.
  • Le deuxième appelant de la transaction a une priorité SCHED_OTHER . Le serveur se rétrograde et dessert l'appelant avec la priorité SCHED_OTHER .

Utiliser la trace pour le débogage

Vous pouvez spécifier l'option -trace pour déboguer les problèmes de latence. Lorsqu'il est utilisé, le test de latence arrête l'enregistrement du tracelog au moment où une mauvaise latence est détectée. Exemple:

atrace --async_start -b 8000 -c sched idle workq binder_driver sync freq
libhwbinder_latency -deadline_us 50000 -trace -i 50000 -pair 3
deadline triggered: halt ∓ stop trace
log:/sys/kernel/debug/tracing/trace

Les composants suivants peuvent avoir un impact sur la latence :

  • Mode de construction Android . Le mode Eng est généralement plus lent que le mode userdebug.
  • Cadre . Comment le service framework utilise ioctl pour configurer le classeur ?
  • Pilote de classeur . Le pilote prend-il en charge le verrouillage à granularité fine ? Contient-il tous les correctifs d'amélioration des performances ?
  • Version noyau . Plus le noyau dispose de capacités en temps réel, meilleurs sont les résultats.
  • Configuration du noyau . La configuration du noyau contient-elle des configurations DEBUG telles que DEBUG_PREEMPT et DEBUG_SPIN_LOCK ?
  • Planificateur de noyau . Le noyau dispose-t-il d'un planificateur Energy-Aware (EAS) ou d'un planificateur Heterogeneous Multi-Processing (HMP) ? Les pilotes du noyau (pilote cpu-freq , pilote cpu-idle , cpu-hotplug , etc.) ont-ils un impact sur le planificateur ?