Pruebas de rendimiento

Android 8.0 incluye pruebas de rendimiento de binder y hwbinder para el rendimiento y la latencia. Si bien existen muchos escenarios para detectar problemas de rendimiento perceptibles, la ejecución de dichos escenarios puede llevar mucho tiempo y los resultados a menudo no están disponibles hasta que se integra un sistema. El uso de las pruebas de rendimiento proporcionadas facilita la realización de pruebas durante el desarrollo, la detección temprana de problemas graves y la mejora de la experiencia del usuario.

Las pruebas de rendimiento incluyen las siguientes cuatro categorías:

  • Rendimiento del enlazador (disponible en system/libhwbinder/vts/performance/Benchmark_binder.cpp )
  • latencia del enlazador (disponible en frameworks/native/libs/binder/tests/schd-dbg.cpp )
  • rendimiento de hwbinder (disponible en system/libhwbinder/vts/performance/Benchmark.cpp )
  • latencia de hwbinder (disponible en system/libhwbinder/vts/performance/Latency.cpp )

Acerca de binder y hwbinder

Binder y hwbinder son infraestructuras de comunicación entre procesos (IPC) de Android que comparten el mismo controlador de Linux pero tienen las siguientes diferencias cualitativas:

Aspecto aglutinante encuadernador
Objetivo Proporcionar un esquema IPC de propósito general para el marco Comunicarse con hardware
Propiedad Optimizado para el uso del marco de trabajo de Android Baja latencia de sobrecarga mínima
Cambiar la política de programación para primer plano/segundo plano No
Argumentos pasando Utiliza la serialización compatible con el objeto Parcel. Utiliza búferes de dispersión y evita la sobrecarga para copiar los datos necesarios para la serialización de paquetes
herencia de prioridad No

Procesos Binder y Hwbinder

Un visualizador systrace muestra las transacciones de la siguiente manera:

Figura 1. Visualización Systrace de los procesos de unión.

En el ejemplo anterior:

  • Los cuatro (4) procesos schd-dbg son procesos de cliente.
  • Los cuatro (4) procesos de enlace son procesos de servidor (el nombre comienza con Binder y termina con un número de secuencia).
  • Un proceso de cliente siempre está emparejado con un proceso de servidor, que está dedicado a su cliente.
  • Todos los pares de procesos cliente-servidor son programados de forma independiente por el núcleo al mismo tiempo.

En la CPU 1, el kernel del sistema operativo ejecuta el cliente para emitir la solicitud. Luego, usa la misma CPU siempre que sea posible para activar un proceso de servidor, manejar la solicitud y cambiar de contexto una vez que se completa la solicitud.

Rendimiento frente a latencia

En una transacción perfecta, en la que los procesos del cliente y del servidor cambian sin problemas, las pruebas de rendimiento y latencia no producen mensajes sustancialmente diferentes. Sin embargo, cuando el kernel del sistema operativo está manejando una solicitud de interrupción (IRQ) del hardware, esperando bloqueos o simplemente eligiendo no manejar un mensaje de inmediato, se puede formar una burbuja de latencia.

Figura 2. Burbuja de latencia debido a diferencias en el rendimiento y la latencia.

La prueba de rendimiento genera una gran cantidad de transacciones con diferentes tamaños de carga útil, lo que proporciona una buena estimación del tiempo de transacción normal (en el mejor de los casos) y el rendimiento máximo que puede lograr el enlazador.

Por el contrario, la prueba de latencia no realiza ninguna acción en la carga útil para minimizar el tiempo de transacción normal. Podemos usar el tiempo de transacción para estimar la sobrecarga del enlazador, hacer estadísticas para el peor de los casos y calcular la proporción de transacciones cuya latencia cumple con un plazo específico.

Manejo de inversiones prioritarias

Una inversión de prioridad ocurre cuando un subproceso con mayor prioridad espera lógicamente a un subproceso con menor prioridad. Las aplicaciones en tiempo real (RT) tienen un problema de inversión de prioridad:

Figura 3. Inversión de prioridad en aplicaciones de tiempo real.

Cuando se utiliza la programación del programador completamente justo (CFS) de Linux, un subproceso siempre tiene la oportunidad de ejecutarse incluso cuando otros subprocesos tienen una prioridad más alta. Como resultado, las aplicaciones con programación CFS manejan la inversión de prioridad como un comportamiento esperado y no como un problema. Sin embargo, en los casos en que el marco de trabajo de Android necesite la programación de RT para garantizar el privilegio de los subprocesos de alta prioridad, se debe resolver la inversión de prioridad.

Ejemplo de inversión de prioridad durante una transacción de enlace (el subproceso RT está lógicamente bloqueado por otros subprocesos de CFS cuando se espera que un subproceso de enlace entre en servicio):

Figura 4. Inversión de prioridad, hilos en tiempo real bloqueados.

Para evitar bloqueos, puede usar la herencia de prioridad para escalar temporalmente el subproceso de Binder a un subproceso de RT cuando atiende una solicitud de un cliente de RT. Tenga en cuenta que la programación de RT tiene recursos limitados y debe usarse con cuidado. En un sistema con n CPU, el número máximo de subprocesos RT actuales también es n ; Es posible que los subprocesos RT adicionales deban esperar (y, por lo tanto, no cumplan con sus plazos) si otros subprocesos RT toman todas las CPU.

Para resolver todas las posibles inversiones de prioridad, puede usar la herencia de prioridad tanto para binder como para hwbinder. Sin embargo, dado que el enlazador se usa ampliamente en todo el sistema, habilitar la herencia de prioridad para las transacciones del enlazador puede generar spam en el sistema con más subprocesos RT de los que puede atender.

Ejecución de pruebas de rendimiento

La prueba de rendimiento se ejecuta contra el rendimiento de transacciones de binder/hwbinder. En un sistema que no está sobrecargado, las burbujas de latencia son raras y su impacto puede eliminarse siempre que el número de iteraciones sea lo suficientemente alto.

  • La prueba de rendimiento del enlazador se encuentra en system/ libhwbinder system/libhwbinder/vts/performance/Benchmark_binder.cpp .
  • La prueba de rendimiento de hwbinder está en system/libhwbinder/vts/performance/Benchmark.cpp .

Resultados de la prueba

Ejemplos de resultados de pruebas de rendimiento para transacciones que utilizan diferentes tamaños de carga útil:

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
  • El tiempo indica el retraso de ida y vuelta medido en tiempo real.
  • CPU indica el tiempo acumulado cuando las CPU están programadas para la prueba.
  • Iteraciones indica el número de veces que se ejecutó la función de prueba.

Por ejemplo, para una carga útil de 8 bytes:

BM_sendVec_binderize/8         69974 ns      32700 ns      21296

… el rendimiento máximo que puede alcanzar el aglutinante se calcula como:

Rendimiento máximo con carga útil de 8 bytes = (8 * 21296)/69974 ~= 2,423 b/ns ~= 2,268 Gb/s

Opciones de prueba

Para obtener resultados en .json, ejecute la prueba con el argumento --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"
    },
   ….
}

Ejecución de pruebas de latencia

La prueba de latencia mide el tiempo que le toma al cliente comenzar a inicializar la transacción, cambiar al proceso del servidor para su manejo y recibir el resultado. La prueba también busca comportamientos incorrectos conocidos del programador que pueden afectar negativamente la latencia de la transacción, como un programador que no admite la herencia de prioridad o respeta el indicador de sincronización.

  • La prueba de latencia del enlazador se encuentra en frameworks/native/libs/binder/tests/schd-dbg.cpp .
  • La prueba de latencia de hwbinder está en system/libhwbinder/vts/performance/Latency.cpp .

Resultados de la prueba

Los resultados (en .json) muestran estadísticas de la latencia media/mejor/peor y el número de plazos incumplidos.

Opciones de prueba

Las pruebas de latencia toman las siguientes opciones:

Dominio Descripción
-i value Especifique el número de iteraciones.
-pair value Especifique el número de pares de procesos.
-deadline_us 2500 Especifique el plazo en nosotros.
-v Obtenga una salida detallada (depuración).
-trace Detener el seguimiento en un hit de fecha límite.

Las siguientes secciones detallan cada opción, describen el uso y proporcionan resultados de ejemplo.

Especificación de iteraciones

Ejemplo con una gran cantidad de iteraciones y salida detallada deshabilitada:

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

Los resultados de estas pruebas muestran lo siguiente:

"pair":3
Crea un par de cliente y servidor.
"iterations": 5000
Incluye 5000 iteraciones.
"deadline_us":2500
La fecha límite es 2500us (2.5ms); se espera que la mayoría de las transacciones alcancen este valor.
"I": 10000
Una sola iteración de prueba incluye dos (2) transacciones:
  • Una transacción por prioridad normal ( CFS other )
  • Una transacción por prioridad en tiempo real ( RT-fifo )
5000 iteraciones equivalen a un total de 10000 transacciones.
"S": 9352
9352 de las transacciones se sincronizan en la misma CPU.
"R": 0.9352
Indica la proporción en la que el cliente y el servidor se sincronizan juntos en la misma CPU.
"other_ms":{ "avg":0.2 , "wst":2.8 , "bst":0.053, "miss":2, "meetR":0.9996}
El caso promedio ( avg ), peor ( wst ) y mejor ( bst ) para todas las transacciones emitidas por una persona que llama con prioridad normal. Dos transacciones miss vencen en la fecha límite, lo que hace que el índice de encuentro ( meetR ) sea 0,9996.
"fifo_ms": { "avg":0.16, "wst":1.5 , "bst":0.067, "miss":0, "meetR":1}
Similar a other_ms , pero para transacciones emitidas por el cliente con prioridad rt_fifo . Es probable (pero no obligatorio) que fifo_ms tenga un mejor resultado que other_ms , con valores avg y wst más bajos y un meetR más alto (la diferencia puede ser aún más significativa con la carga en segundo plano).

Nota: la carga en segundo plano puede afectar el resultado del rendimiento y la tupla other_ms en la prueba de latencia. Solo fifo_ms puede mostrar resultados similares siempre que la carga en segundo plano tenga una prioridad más baja que RT-fifo .

Especificación de valores de par

Cada proceso de cliente está emparejado con un proceso de servidor dedicado para el cliente, y cada par puede programarse de forma independiente para cualquier CPU. Sin embargo, la migración de la CPU no debería ocurrir durante una transacción siempre que el indicador SYNC esté en honor .

¡Asegúrese de que el sistema no esté sobrecargado! Si bien se espera una latencia alta en un sistema sobrecargado, los resultados de las pruebas para un sistema sobrecargado no brindan información útil. Para probar un sistema con mayor presión, use -pair #cpu-1 (o -pair #cpu con precaución). Probar usando -pair n con n > #cpu sobrecarga el sistema y genera información inútil.

Especificación de valores de fecha límite

Después de una extensa prueba de escenario de usuario (ejecutando la prueba de latencia en un producto calificado), determinamos que 2,5 ms es el plazo a cumplir. Para nuevas aplicaciones con requisitos más altos (como 1000 fotos/segundo), este valor de fecha límite cambiará.

Especificación de salida detallada

El uso de la opción -v muestra una salida detallada. Ejemplo:

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
  • El subproceso de servicio se crea con una prioridad SCHED_OTHER y se ejecuta en CPU:1 con pid 8674 .
  • A continuación, la primera transacción la inicia un fifo-caller . Para dar servicio a esta transacción, hwbinder actualiza la prioridad del servidor ( pid: 8674 tid: 8676 ) a 99 y también lo marca con una clase de programación transitoria (impresa como ??? ). Luego, el programador pone el proceso del servidor en CPU:0 para ejecutarlo y lo sincroniza con la misma CPU con su cliente.
  • El llamador de la segunda transacción tiene una prioridad SCHED_OTHER . El servidor se degrada a sí mismo y atiende a la persona que llama con SCHED_OTHER prioridad.

Uso de seguimiento para la depuración

Puede especificar la opción -trace para depurar problemas de latencia. Cuando se usa, la prueba de latencia detiene la grabación del registro de seguimiento en el momento en que se detecta una mala latencia. Ejemplo:

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

Los siguientes componentes pueden afectar la latencia:

  • Modo de compilación de Android . El modo Eng suele ser más lento que el modo de depuración de usuario.
  • marco ¿Cómo utiliza el servicio de marco ioctl para configurar el archivador?
  • Conductor de carpeta . ¿El controlador es compatible con el bloqueo de grano fino? ¿Contiene todos los parches de cambio de rendimiento?
  • Versión del núcleo . Cuanto mejor sea la capacidad en tiempo real del kernel, mejores serán los resultados.
  • Configuración del núcleo . ¿La configuración del kernel contiene configuraciones DEBUG como DEBUG_PREEMPT y DEBUG_SPIN_LOCK ?
  • Programador del núcleo . ¿El núcleo tiene un planificador consciente de la energía (EAS) o un planificador de procesamiento múltiple heterogéneo (HMP)? ¿Algún controlador del kernel ( cpu-freq driver, cpu-idle driver, cpu-hotplug , etc.) afecta al programador?