Pruebas de rendimiento

Android 8.0 incluye pruebas de rendimiento de Binder y Hwbinder para determinar el rendimiento y la latencia. Si bien existen muchos escenarios para detectar problemas de rendimiento perceptibles, ejecutar dichos escenarios puede llevar mucho tiempo y los resultados a menudo no están disponibles hasta que se integra el sistema. El uso de las pruebas de rendimiento proporcionadas facilita las pruebas durante el desarrollo, detecta problemas graves antes y mejora la experiencia del usuario.

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

  • 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 carpeta
Objetivo Proporcionar un esquema IPC de propósito general para el marco. Comunicarse con el hardware
Propiedad Optimizado para el uso del marco de Android Latencia baja y sobrecarga mínima
Cambiar la política de programación para primer plano/segundo plano No
Argumentos que pasan Utiliza la serialización soportada por el objeto Parcel. Utiliza buffers de dispersión y evita la sobrecarga de copiar los datos necesarios para la serialización de paquetes.
herencia prioritaria No

Procesos aglutinantes y hwbinder.

Un visualizador de systrace muestra las transacciones de la siguiente manera:

Figura 1. Visualización de Systrace de procesos aglutinantes.

En el ejemplo anterior:

  • Los cuatro (4) procesos schd-dbg son procesos de cliente.
  • Los cuatro (4) procesos de Binder 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 kernel al mismo tiempo.

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

Rendimiento frente a latencia

En una transacción perfecta, donde el proceso 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 maneja una solicitud de interrupción (IRQ) del hardware, espera bloqueos o simplemente elige no manejar un mensaje de inmediato, se puede formar una burbuja de latencia.

Figura 2. Burbuja de latencia debido a diferencias en rendimiento y 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 normal de transacción. Podemos utilizar el tiempo de transacción para estimar la sobrecarga de la carpeta, hacer estadísticas para el peor de los casos y calcular la proporción de transacciones cuya latencia cumple con un plazo específico.

Manejar inversiones de prioridad

Una inversión de prioridad ocurre cuando un hilo con mayor prioridad está lógicamente esperando a un hilo 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 en tiempo real.

Cuando se utiliza la programación Linux Completely Fair Scheduler (CFS), un subproceso siempre tiene la posibilidad de ejecutarse incluso cuando otros subprocesos tienen una mayor prioridad. 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 los que el marco de Android necesita programación RT para garantizar el privilegio de subprocesos de alta prioridad, se debe resolver la inversión de prioridad.

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

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

Para evitar bloqueos, puede utilizar la herencia de prioridad para escalar temporalmente el subproceso de Binder a un subproceso RT cuando atiende una solicitud de un cliente 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, incumplir sus plazos) si otros subprocesos RT toman todas las CPU.

Para resolver todas las posibles inversiones de prioridad, puede utilizar la herencia de prioridad tanto para binder como para hwbinder. Sin embargo, como el encuadernador se usa ampliamente en todo el sistema, habilitar la herencia de prioridad para las transacciones del encuadernador podría enviar spam al sistema con más subprocesos RT de los que puede atender.

Ejecutar 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 de la carpeta se encuentra en system/libhwbinder/vts/performance/Benchmark_binder.cpp .
  • La prueba de rendimiento de hwbinder se encuentra 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 del viaje de ida y vuelta medido en tiempo real.
  • CPU indica el tiempo acumulado en el que 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 encuadernador 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"
    },
   ….
}

Ejecutar 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 malos comportamientos conocidos del programador que puedan afectar negativamente la latencia de las transacciones, como un programador que no admite la herencia de prioridad o no 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 se encuentra en system/libhwbinder/vts/performance/Latency.cpp .

Resultados de la prueba

Los resultados (en .json) muestran estadísticas de latencia promedio/mejor/peor y la cantidad 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 resultados detallados (depuración).
-trace Detenga el seguimiento cuando se cumpla una fecha límite.

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

Especificar 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,5 ms); Se espera que la mayoría de las transacciones alcancen este valor.
"I": 10000
Una única 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 la fecha límite, lo que hace que el índice de cumplimiento ( 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 menor que RT-fifo .

Especificar 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 proporcionan información útil. Para probar un sistema con mayor presión, use -pair #cpu-1 (o -pair #cpu con precaución). Las pruebas usando -pair n con n > #cpu sobrecargan el sistema y generan información inútil.

Especificar valores de fecha límite

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

Especificar 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 .
  • Luego, una fifo-caller inicia la primera transacción . Para atender 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 coloca el proceso del servidor en CPU:0 para ejecutarlo y lo sincroniza con la misma CPU que su cliente.
  • El autor de la segunda transacción tiene una prioridad SCHED_OTHER . El servidor se degrada y atiende a la persona que llama con prioridad SCHED_OTHER .

Usar seguimiento para depurar

Puede especificar la opción -trace para depurar problemas de latencia. Cuando se utiliza, la prueba de latencia detiene la grabación del registro de seguimiento en el momento en que se detecta una latencia incorrecta. 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 de ingeniería suele ser más lento que el modo de depuración de usuario.
  • Estructura . ¿Cómo utiliza el servicio de marco ioctl para configurar la carpeta?
  • Conductor de carpeta . ¿El controlador admite el bloqueo detallado? ¿Contiene todos los parches de cambio de rendimiento?
  • Versión del núcleo . Mientras mejor capacidad en tiempo real tenga el 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 . ¿Tiene el kernel un programador Energy-Aware (EAS) o un programador de multiprocesamiento heterogéneo (HMP)? ¿Algún controlador del kernel (controlador cpu-freq , controlador cpu-idle , cpu-hotplug , etc.) afecta al programador?