Leistungstest

Android 8.0 enthält Binder- und Hwbinder-Leistungstests für Durchsatz und Latenz. Während es viele Szenarien gibt, um wahrnehmbare Leistungsprobleme zu erkennen, kann die Ausführung solcher Szenarien zeitaufwändig sein und Ergebnisse sind oft erst verfügbar, nachdem ein System integriert wurde. Die Verwendung der bereitgestellten Leistungstests erleichtert das Testen während der Entwicklung, das frühere Erkennen schwerwiegender Probleme und das Verbessern der Benutzererfahrung.

Leistungstests umfassen die folgenden vier Kategorien:

  • Binderdurchsatz (verfügbar in system/libhwbinder/vts/performance/Benchmark_binder.cpp )
  • Bindelatenz (verfügbar in frameworks/native/libs/binder/tests/schd-dbg.cpp )
  • hwbinder-Durchsatz (verfügbar in system/libhwbinder/vts/performance/Benchmark.cpp )
  • hwbinder-Latenz (verfügbar in system/libhwbinder/vts/performance/Latency.cpp )

Über Binder und Hwbinder

Binder und hwbinder sind Android Inter-Process Communication (IPC)-Infrastrukturen, die denselben Linux-Treiber verwenden, aber die folgenden qualitativen Unterschiede aufweisen:

Aspekt Bindemittel hwbinder
Zweck Stellen Sie ein allgemeines IPC-Schema für das Framework bereit Kommunizieren Sie mit der Hardware
Eigentum Optimiert für die Nutzung des Android-Frameworks Minimaler Overhead, niedrige Latenz
Planungsrichtlinie für Vordergrund/Hintergrund ändern Ja Nein
Argumente vorbei Verwendet die vom Parcel-Objekt unterstützte Serialisierung Verwendet Streupuffer und vermeidet den Overhead zum Kopieren von Daten, die für die Parcel-Serialisierung erforderlich sind
Prioritätsvererbung Nein Ja

Binder- und hwbinder-Prozesse

Ein Systrace-Visualizer zeigt Transaktionen wie folgt an:

Abbildung 1. Systrace-Visualisierung von Binderprozessen.

Im obigen Beispiel:

  • Die vier (4) schd-dbg-Prozesse sind Client-Prozesse.
  • Die vier (4) Binderprozesse sind Serverprozesse (der Name beginnt mit Binder und endet mit einer Sequenznummer).
  • Ein Client-Prozess ist immer mit einem Server-Prozess gepaart, der seinem Client zugeordnet ist.
  • Alle Client-Server-Prozesspaare werden gleichzeitig unabhängig vom Kernel geplant.

In der CPU 1 führt der Betriebssystemkern den Client aus, um die Anforderung auszugeben. Es verwendet dann wann immer möglich dieselbe CPU, um einen Serverprozess aufzuwecken, die Anfrage zu verarbeiten und den Kontext zurückzuschalten, nachdem die Anfrage abgeschlossen ist.

Durchsatz vs. Latenz

Bei einer perfekten Transaktion, bei der Client- und Serverprozess nahtlos wechseln, erzeugen Durchsatz- und Latenztests keine wesentlich unterschiedlichen Nachrichten. Wenn der Betriebssystemkern jedoch eine Interrupt-Anforderung (IRQ) von der Hardware verarbeitet, auf Sperren wartet oder sich einfach dafür entscheidet, eine Nachricht nicht sofort zu verarbeiten, kann sich eine Latenzblase bilden.

Abbildung 2. Latenzblase aufgrund von Unterschieden in Durchsatz und Latenz.

Der Durchsatztest generiert eine große Anzahl von Transaktionen mit unterschiedlichen Nutzlastgrößen, was eine gute Schätzung für die reguläre Transaktionszeit (im besten Fall) und den maximalen Durchsatz liefert, den der Binder erreichen kann.

Im Gegensatz dazu führt der Latenztest keine Aktionen an der Nutzlast aus, um die reguläre Transaktionszeit zu minimieren. Wir können die Transaktionszeit verwenden, um den Binder-Overhead zu schätzen, Statistiken für den schlimmsten Fall erstellen und das Verhältnis von Transaktionen berechnen, deren Latenz eine bestimmte Frist einhält.

Umgang mit Prioritätsumkehrungen

Eine Prioritätsumkehr tritt auf, wenn ein Thread mit höherer Priorität logisch auf einen Thread mit niedrigerer Priorität wartet. Echtzeit (RT)-Anwendungen haben ein Prioritätsinversionsproblem:

Abbildung 3. Prioritätsumkehr in Echtzeitanwendungen.

Bei Verwendung des Linux Completely Fair Scheduler (CFS)-Scheduling hat ein Thread immer die Möglichkeit, ausgeführt zu werden, selbst wenn andere Threads eine höhere Priorität haben. Infolgedessen behandeln Anwendungen mit CFS-Scheduling die Prioritätsumkehr als erwartetes Verhalten und nicht als Problem. In Fällen, in denen das Android-Framework RT-Scheduling benötigt, um das Privileg von Threads mit hoher Priorität zu gewährleisten, muss jedoch die Prioritätsumkehr behoben werden.

Beispiel einer Prioritätsumkehr während einer Binder-Transaktion (RT-Thread wird logischerweise von anderen CFS-Threads blockiert, wenn er darauf wartet, dass ein Binder-Thread bedient wird):

Abbildung 4. Prioritätsumkehrung, blockierte Echtzeit-Threads.

Um Blockaden zu vermeiden, können Sie die Prioritätsvererbung verwenden, um den Binder-Thread vorübergehend zu einem RT-Thread zu eskalieren, wenn er eine Anforderung von einem RT-Client bedient. Beachten Sie, dass die RT-Planung begrenzte Ressourcen hat und mit Bedacht verwendet werden sollte. In einem System mit n CPUs ist die maximale Anzahl aktueller RT-Threads ebenfalls n ; Zusätzliche RT-Threads müssen möglicherweise warten (und verpassen somit ihre Fristen), wenn alle CPUs von anderen RT-Threads belegt sind.

Um alle möglichen Prioritätsumkehrungen aufzulösen, könnten Sie die Prioritätsvererbung sowohl für binder als auch für hwbinder verwenden. Da Binder jedoch im gesamten System weit verbreitet ist, kann das Aktivieren der Prioritätsvererbung für Bindertransaktionen das System mit mehr RT-Threads spammen, als es bedienen kann.

Durchsatztests ausführen

Der Durchsatztest wird gegen den Transaktionsdurchsatz von binder/hwbinder ausgeführt. In einem nicht überlasteten System sind Latenzblasen selten und ihre Auswirkungen können eliminiert werden, solange die Anzahl der Iterationen hoch genug ist.

  • Der Binder -Durchsatztest befindet sich in system/libhwbinder/vts/performance/Benchmark_binder.cpp .
  • Der hwbinder -Durchsatztest befindet sich in system/libhwbinder/vts/performance/Benchmark.cpp .

Testergebnisse

Beispielergebnisse für Durchsatztests für Transaktionen mit unterschiedlichen Nutzlastgrößen:

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
  • Die Zeit gibt die in Echtzeit gemessene Umlaufverzögerung an.
  • CPU gibt die kumulierte Zeit an, in der CPUs für den Test geplant sind.
  • Iterationen gibt an, wie oft die Testfunktion ausgeführt wurde.

Zum Beispiel für eine 8-Byte-Nutzlast:

BM_sendVec_binderize/8         69974 ns      32700 ns      21296

… der maximale Durchsatz, den der Binder erreichen kann, errechnet sich aus:

MAX. Durchsatz mit 8-Byte-Nutzlast = (8 * 21296)/69974 ~= 2,423 b/ns ~= 2,268 Gb/s

Testmöglichkeiten

Um Ergebnisse in .json zu erhalten, führen Sie den Test mit dem 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"
    },
   ….
}

Latenztests ausführen

Der Latenztest misst die Zeit, die der Client benötigt, um mit der Initialisierung der Transaktion zu beginnen, zur Verarbeitung auf den Serverprozess umzuschalten und das Ergebnis zu erhalten. Der Test sucht auch nach bekanntem schlechtem Scheduler-Verhalten, das sich negativ auf die Transaktionslatenz auswirken kann, z. B. ein Scheduler, der die Prioritätsvererbung nicht unterstützt oder das Sync-Flag nicht beachtet.

  • Der Binder-Latenztest befindet sich in frameworks/native/libs/binder/tests/schd-dbg.cpp .
  • Der Hwbinder-Latenztest befindet sich in system/libhwbinder/vts/performance/Latency.cpp .

Testergebnisse

Ergebnisse (in .json) zeigen Statistiken für die durchschnittliche/beste/schlechteste Latenz und die Anzahl der verpassten Fristen.

Testmöglichkeiten

Latenztests verwenden die folgenden Optionen:

Befehl Beschreibung
-i value Geben Sie die Anzahl der Iterationen an.
-pair value Geben Sie die Anzahl der Prozesspaare an.
-deadline_us 2500 Geben Sie die Frist in uns an.
-v Erhalten Sie eine ausführliche (Debugging-)Ausgabe.
-trace Halten Sie die Ablaufverfolgung bei einem Deadline-Treffer an.

In den folgenden Abschnitten werden die einzelnen Optionen detailliert beschrieben, die Verwendung beschrieben und Beispielergebnisse bereitgestellt.

Iterationen angeben

Beispiel mit einer großen Anzahl von Iterationen und deaktivierter ausführlicher Ausgabe:

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"
}

Diese Testergebnisse zeigen Folgendes:

"pair":3
Erstellt ein Client- und Serverpaar.
"iterations": 5000
Enthält 5000 Iterationen.
"deadline_us":2500
Die Frist beträgt 2500 us (2,5 ms); Die meisten Transaktionen werden voraussichtlich diesen Wert erreichen.
"I": 10000
Eine einzelne Testiteration umfasst zwei (2) Transaktionen:
  • Eine Transaktion mit normaler Priorität ( CFS other )
  • Eine Transaktion nach Echtzeitpriorität ( RT-fifo )
5000 Iterationen entsprechen insgesamt 10000 Transaktionen.
"S": 9352
9352 der Transaktionen werden in derselben CPU synchronisiert.
"R": 0.9352
Gibt das Verhältnis an, in dem Client und Server in derselben CPU synchronisiert werden.
"other_ms":{ "avg":0.2 , "wst":2.8 , "bst":0.053, "miss":2, "meetR":0.9996}
Der durchschnittliche ( avg ), der schlechteste ( wst ) und der beste ( bst ) Fall für alle Transaktionen, die von einem Aufrufer mit normaler Priorität ausgegeben werden. Zwei Transaktionen miss die Frist, sodass das Meet-Verhältnis ( meetR ) 0,9996 beträgt.
"fifo_ms": { "avg":0.16, "wst":1.5 , "bst":0.067, "miss":0, "meetR":1}
Ähnlich wie other_ms , aber für Transaktionen, die vom Client mit rt_fifo Priorität ausgegeben wurden. Es ist wahrscheinlich (aber nicht erforderlich), dass fifo_ms ein besseres Ergebnis liefert als other_ms , mit niedrigeren avg - und wst -Werten und einem höheren meetR (der Unterschied kann bei Belastung im Hintergrund noch signifikanter sein).

Hinweis: Die Hintergrundlast kann sich auf das Durchsatzergebnis und das Tupel other_ms im Latenztest auswirken. Nur die fifo_ms können ähnliche Ergebnisse zeigen, solange die Hintergrundlast eine niedrigere Priorität als RT-fifo .

Paarwerte angeben

Jeder Client-Prozess ist mit einem für den Client dedizierten Server-Prozess gepaart, und jedes Paar kann unabhängig für jede CPU geplant werden. Die CPU-Migration sollte jedoch nicht während einer Transaktion stattfinden, solange das SYNC-Flag auf honor gesetzt ist.

Stellen Sie sicher, dass das System nicht überlastet wird! Während eine hohe Latenz in einem überlasteten System zu erwarten ist, liefern Testergebnisse für ein überlastetes System keine nützlichen Informationen. Um ein System mit höherem Druck zu testen, verwenden -pair #cpu-1 (oder -pair #cpu mit Vorsicht). Das Testen mit -pair n with n > #cpu überlastet das System und generiert nutzlose Informationen.

Terminwerte angeben

Nach umfangreichen Tests mit Benutzerszenarien (Durchführung des Latenztests auf einem qualifizierten Produkt) haben wir festgestellt, dass 2,5 ms die einzuhaltende Frist ist. Für neue Anwendungen mit höheren Anforderungen (z. B. 1000 Fotos/Sekunde) ändert sich dieser Fristwert.

Angabe der ausführlichen Ausgabe

Bei Verwendung der Option -v wird eine ausführliche Ausgabe angezeigt. Beispiel:

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
  • Der Service-Thread wird mit einer SCHED_OTHER Priorität erstellt und in CPU:1 mit pid 8674 ausgeführt.
  • Die erste Transaktion wird dann von einem fifo-caller gestartet. Um diese Transaktion zu bedienen, erhöht der hwbinder die Priorität des Servers ( pid: 8674 tid: 8676 ) auf 99 und markiert sie auch mit einer vorübergehenden Scheduling-Klasse (gedruckt als ??? ). Der Scheduler versetzt dann den Serverprozess in CPU:0 zum Ausführen und synchronisiert ihn mit derselben CPU mit seinem Client.
  • Der zweite Transaktionsaufrufer hat eine SCHED_OTHER Priorität. Der Server stuft sich selbst herunter und bedient den Anrufer mit SCHED_OTHER Priorität.

Trace zum Debuggen verwenden

Sie können die Option -trace angeben, um Latenzprobleme zu debuggen. Bei Verwendung stoppt der Latenztest die Tracelog-Aufzeichnung in dem Moment, in dem eine schlechte Latenz erkannt wird. Beispiel:

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

Die folgenden Komponenten können sich auf die Latenz auswirken:

  • Android-Build-Modus . Der Eng-Modus ist normalerweise langsamer als der Userdebug-Modus.
  • Rahmen . Wie verwendet der Framework-Dienst ioctl , um den Binder zu konfigurieren?
  • Binder-Treiber . Unterstützt der Treiber feinkörniges Sperren? Enthält es alle Performance Turning Patches?
  • Kernel-Version . Je besser die Echtzeitfähigkeit des Kernels ist, desto besser sind die Ergebnisse.
  • Kernel-Konfig . Enthält die Kernel-Konfiguration DEBUG -Konfigurationen wie DEBUG_PREEMPT und DEBUG_SPIN_LOCK ?
  • Kernel-Scheduler . Verfügt der Kernel über einen Energy-Aware-Scheduler (EAS) oder einen Heterogeneous Multi-Processing (HMP)-Scheduler? Beeinflussen irgendwelche Kernel-Treiber ( cpu-freq driver, cpu-idle driver, cpu-hotplug , etc.) den Scheduler?