Tests de performances

Android 8.0 inclut des tests de performances de liaison et de hwbinder pour le débit et la latence. Bien que de nombreux scénarios existent pour détecter les 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 d'un système. L'utilisation des tests de performances fournis facilite les tests pendant le développement, permet de détecter plus tôt les problèmes graves et d'améliorer l'expérience utilisateur.

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

  • Débit du liaisonneur (disponible dans system/libhwbinder/vts/performance/Benchmark_binder.cpp)
  • Latence de liaison (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 de binder et de 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 hwbinder
Objectif Fournir un schéma d'IPC à usage général pour le framework Communiquer avec le matériel
Propriété Optimisé pour l'utilisation du framework Android Faible latence avec un coût minimal
Modifier la stratégie de planification pour le premier plan/l'arrière-plan Oui Non
Transmission d'arguments Utilise la sérialisation compatible avec l'objet Parcel Utilise des tampons de dispersion et évite les frais généraux liés à la copie des données requises pour la sérialisation des parcelles.
Héritage de priorité Non Oui

Processus de liaison et hwbinder

Un visualiseur systrace affiche les transactions comme suit:

Figure 1 Visualisation Systrace des processus de liaison.

Dans l'exemple ci-dessus:

  • Les quatre (4) processus schd-dbg sont des processus client.
  • Les quatre (4) processus de liaison sont des processus de 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, qui lui est dédié.
  • Toutes les paires de processus client-serveur sont planifiées indépendamment par le noyau de manière simultanée.

Dans le processeur 1, le noyau de l'OS exécute le client pour envoyer la requête. Il utilise ensuite le même processeur dans la mesure du possible pour réveiller un processus de serveur, traiter la requête et rétablir le contexte une fois la requête terminée.

Débit par rapport à la latence

Dans une transaction parfaite, où le processus client et serveur bascule de manière transparente, les tests de débit et de latence ne produisent pas de messages sensiblement différents. Toutefois, lorsque le noyau de l'OS 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, ce qui fournit une bonne estimation du temps de transaction régulier (dans les scénarios les plus favorables) et du débit maximal que le liaisonneur peut atteindre.

En revanche, le test de latence n'effectue aucune action sur la charge utile pour minimiser la durée de la transaction régulière. Nous pouvons utiliser le temps de transaction pour estimer le coût du liant, créer des statistiques pour le pire des cas et calculer le ratio des 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é supérieure attend logiquement un thread de priorité inférieure. Les applications en temps réel (RT) présentent un problème d'inversion de priorité:

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

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

Exemple d'inversion de priorité lors d'une transaction de liaison (le thread RT est bloqué de manière logique par d'autres threads CFS lorsqu'il attend qu'un thread de liaison soit traité):

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 de liaison vers un thread RT lorsqu'il traite une requête d'un client RT. N'oubliez pas que la planification RT dispose de ressources limitées et doit être utilisée avec précaution. Dans un système avec n CPU, le nombre maximal de threads RT actuels est également de n. Des threads RT supplémentaires peuvent devoir attendre (et manquer ainsi leurs échéances) si tous les CPU 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 le liaison et le hwbinder. Toutefois, comme le liaisonneur est largement utilisé dans le système, l'activation de l'héritage de priorité pour les transactions de liaisonneur peut inonder le système de 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é tant que le nombre d'itérations est suffisamment élevé.

  • Le test de débit du liaison 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 des tests

Exemples de résultats de test de débit pour les 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
  • Temps indique le délai aller-retour mesuré en temps réel.
  • CPU indique le temps cumulé lorsque les processeurs sont planifiés pour le test.
  • Itérations : indique 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 maximal que le liaisonneur peut atteindre est calculé comme suit:

Débit maximal avec une charge utile de 8 octets = (8 * 21 296)/69 974 ~= 2,423 b/ns ~= 2,268 Go/s

Options de test

Pour obtenir des 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 de serveur pour la gestion et recevoir le résultat. Le test recherche également des comportements de planificateur connus qui peuvent avoir un impact négatif sur la latence des transactions, comme un planificateur qui n'est pas compatible avec l'héritage de priorité ou qui n'honore pas l'indicateur de synchronisation.

  • Le test de latence du liaisonneur 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 des tests

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

Options de test

Les tests de latence acceptent 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 Spécifiez l'échéance en heures.
-v Obtenez une sortie détaillée (débogage).
-trace Arrêtez la trace lorsqu'une échéance est atteinte.

Les sections suivantes décrivent chaque option, l'utilisation et des exemples de résultats.

Spécifier les itérations

Exemple avec un grand nombre d'itérations et la 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"
}

Les résultats de ces tests indiquent ce qui suit:

"pair":3
Crée une paire client-serveur.
"iterations": 5000
Inclut 5 000 itérations.
"deadline_us":2500
L'échéance est de 2 500 µs (2,5 ms). La plupart des transactions devraient respecter 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é en temps réel (RT-fifo)
5 000 itérations correspondent à un total de 10 000 transactions.
"S": 9352
9 352 transactions sont synchronisées sur le même processeur.
"R": 0.9352
Indique le ratio auquel le client et le serveur sont synchronisés dans le même processeur.
"other_ms":{ "avg":0.2 , "wst":2.8 , "bst":0.053, "miss":2, "meetR":0.9996}
Cas moyen (avg), pire (wst) et meilleur (bst) pour toutes les transactions émises par un appelant de priorité normale. Deux transactions miss la date limite, ce qui fait que le ratio de respect (meetR) est de 0,9996.
"fifo_ms": { "avg":0.16, "wst":1.5 , "bst":0.067, "miss":0, "meetR":1}
Semblable à other_ms, mais pour les transactions émises par le client avec une priorité rt_fifo. Il est probable (mais pas obligatoire) que fifo_ms obtienne un meilleur résultat que other_ms, avec des valeurs avg et wst plus faibles et une meetR plus élevée (la différence peut être encore plus importante avec la charge en arrière-plan).

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

Spécifier les valeurs des paires

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. Toutefois, la migration du processeur ne doit pas se produire pendant une transaction tant que l'indicateur SYNC est honor.

Assurez-vous que le système n'est pas surchargé. Bien que la latence élevée dans un système surchargé soit attendue, les résultats des tests pour un système surchargé ne fournissent pas d'informations utiles. Pour tester un système à pression plus élevée, utilisez -pair #cpu-1 (ou -pair #cpu avec précaution). Les tests à l'aide de -pair n avec n > #cpu surchargent le système et génèrent des informations inutiles.

Spécifier des valeurs de délai

Après des tests approfondis sur les scénarios utilisateur (exécution du test de latence sur un produit qualifié), nous avons déterminé que 2,5 ms était la limite à respecter. Pour les nouvelles applications présentant des exigences plus élevées (par exemple, 1 000 photos/seconde), cette valeur d'échéance 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 d'une classe de planification temporaire (affichée sous la forme ???). Le planificateur met ensuite le processus serveur en CPU:0 pour l'exécuter et le synchronise avec le même processeur avec son client.
  • L'appelant de la deuxième transaction a une priorité SCHED_OTHER. Le serveur se rétrograde et traite l'appelant avec une 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 journal de trace 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 une incidence sur la latence:

  • Mode de compilation Android Le mode Eng est généralement plus lent que le mode userdebug.
  • Framework Comment le service de framework utilise-t-il ioctl pour configurer le liaisonneur ?
  • Pilote de liaison Le pilote est-il compatible avec le verrouillage précis ? Contient-il tous les correctifs de performances ?
  • Version du noyau Plus le noyau est performant en temps réel, meilleurs sont les résultats.
  • Configuration du kernel La configuration du kernel contient-elle des configurations DEBUG telles que DEBUG_PREEMPT et DEBUG_SPIN_LOCK ?
  • Planificateur de noyau. Le noyau dispose-t-il d'un planificateur EAS (Energy-Aware Scheduler) ou HMP (Heterogeneous Multi-Processing) ? Des pilotes de kernel (pilote cpu-freq, pilote cpu-idle, cpu-hotplug, etc.) ont-ils un impact sur le planificateur ?