Cómo probar el rendimiento de la IU

La prueba de rendimiento de la IU te garantiza que tu app no solo cumpla con los requisitos funcionales sino que la interacción del usuario con tu app sea fluida y funcione constantemente a 60 fotogramas por segundo (Why 60fps?) sin disminuir o retrasar fotogramas (lo que llamamos bloqueo). Este documento explica las herramientas disponibles para medir el rendimiento de la IU y establece un enfoque para integrar las medidas de rendimiento de la IU en tus prácticas de prueba.

Cómo medir el rendimiento de la IU

Para mejorar el rendimiento, primero necesitas poder medir el rendimiento de tu sistema y, luego, diagnosticar e identificar los problemas que puedan surgir debido a las varias secciones de tu canalización.

La herramienta dumpsys de Android se ejecuta en el dispositivo y vuelca información útil sobre el estado de los servicios del sistema. Al pasar el comando gxinfo a dumpsys, se obtiene una salida de logcat con información de rendimiento en relación con los fotogramas de animación que ocurren durante la fase de grabado.

    > adb shell dumpsys gfxinfo <PACKAGE_NAME>
    

Este comando puede crear múltiples variantes diferentes de datos del intervalo del fotograma.

Incorporación de Frame Stats

En Android 6.0 (API nivel 23), el comando emite un análisis adicional a logcat sobre los datos del fotograma. Estos datos se recopilan en toda la duración del proceso. Por ejemplo:

    Stats since: 752958278148ns
    Total frames rendered: 82189
    Janky frames: 35335 (42.99%)
    90th percentile: 34ms
    95th percentile: 42ms
    99th percentile: 69ms
    Number Missed Vsync: 4706
    Number High input latency: 142
    Number Slow UI thread: 17270
    Number Slow bitmap uploads: 1542
    Number Slow draw: 23342
    

Estas estadísticas de alto nivel representan, en un nivel avanzado, el rendimiento de representación de la app y su estabilidad en muchos fotogramas.

Información precisa del intervalo del fotograma

La versión de Android 6.0 ofrece un nuevo comando para gfxinfo, es framestats que brinda información extremadamente detallada sobre el intervalo del fotograma reciente, de manera que puedes localizar y depurar errores de manera más precisa.

    >adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats
    

Este comando emite información sobre el intervalo del fotograma, medida en nanosegundos, de los últimos 120 fotogramas que produjo la app. A continuación, se muestra un ejemplo sin formato de adb dumpsys gxinfo <PACKAGE_NAME> framestats:

    0,27965466202353,27965466202353,27965449758000,27965461202353,27965467153286,27965471442505,27965471925682,27965474025318,27965474588547,27965474860786,27965475078599,27965479796151,27965480589068,
    0,27965482993342,27965482993342,27965465835000,27965477993342,27965483807401,27965486875630,27965487288443,27965489520682,27965490184380,27965490568703,27965491408078,27965496119641,27965496619641,
    0,27965499784331,27965499784331,27965481404000,27965494784331,27965500785318,27965503736099,27965504201151,27965506776568,27965507298443,27965507515005,27965508405474,27965513495318,27965514061984,
    0,27965516575320,27965516575320,27965497155000,27965511575320,27965517697349,27965521276151,27965521734797,27965524350474,27965524884536,27965525160578,27965526020891,27965531371203,27965532114484,
    

Cada línea de esta salida representa un fotograma producido por la app. Cada línea tiene un número fijo de columnas que describen el tiempo transcurrido en cada etapa de la canalización de producción de fotogramas. En la siguiente sección, se describe este formato en detalle y se explica qué representa cada columna.

Formato de datos de framestats

Debido a que el bloque de datos se emite en formato CSV, es muy sencillo pegarlo en tu herramienta de hoja de cálculo preferida, o recopilar y redistribuir con un script. En la siguiente tabla, se explica el formato de las columnas de los datos de salida. Las marcas de tiempo están en nanosegundos.

  • FLAGS
    • El tiempo total del fotograma de las filas con "0" en la columna FLAGS se puede calcular restando la columna INTENDED_VSYNC a la columna FRAME_COMPLETED.
    • Si el resultado no es cero, la fila se debe ignorar, ya que se ha determinado que el fotograma contiene un valor atípico de rendimiento, donde se espera que el diseño y la imagen tomen más de 16 ms. Razones por las que esto puede suceder:
      • Se cambió el diseño de la ventana (ya sea el primer fotograma de la app o luego de una rotación).
      • También es posible que se haya omitido el fotograma, en cuyo caso, alguno de los valores tendrá marcas de tiempo no utilizables. Se puede omitir un fotograma si, por ejemplo, supera los 60 fotogramas por segundo o si no había nada desfasado en pantalla. Esto no necesariamente indica que la app tenga algún problema.
  • INTENDED_VSYNC
    • El punto de partida previsto del fotograma. Si este valor es diferente de VSYNC, el subproceso de la interfaz de usuario se encontraba ocupado, lo que evitó la respuesta a la señal vsync de manera oportuna.
  • VSYNC
    • El valor de tiempo que se utilizó en todas las escuchas vsync y las imágenes para el fotograma (devolución de llamada del fotograma Choreographer, animaciones, View.getDrawingTime(), etc.).
    • Para obtener más información sobre VSYNC y cómo influye en tu app, consulta el video Understanding VSYNC.
  • OLDEST_INPUT_EVENT
    • La marca de tiempo del evento de entrada más antiguo de la cola de entrada, o Long.MAX_VALUE en caso de que el fotograma no contenga ninguna entrada.
    • Este valor está diseñado principalmente para trabajar en la plataforma y tiene utilidad limitada para los desarrolladores de apps.
  • NEWEST_INPUT_EVENT
    • La marca de tiempo del evento de entrada más reciente de la cola de entrada, o 0 en caso de que el fotograma no contenga ninguna entrada.
    • Este valor está diseñado principalmente para trabajar en la plataforma y tiene utilidad limitada para los desarrolladores de apps.
    • Sin embargo, puede obtener una idea general sobre la cantidad de latencia que la app está añadiendo consultando (FRAME_COMPLETED - NEWEST_INPUT_EVENT).
  • HANDLE_INPUT_START
    • La marca de tiempo en que el evento de entrada se distribuye a la app.
    • Al observar el tiempo entre esto y ANIMATION_START, se puede medir cuánto tiempo dedicó la app a la administración de eventos de entrada.
    • Si este valor es alto (superior a 2 ms), esto significa que la app dedica tiempo poco común al proceso de los eventos de entrada, como View.onTouchEvent(), lo que indica que este proceso se debe optimizar o descargar a otro subproceso. Ten en cuenta que, en algunas ocasiones, como cuando al hacer clic en un evento que lanza nuevas actividades o algo parecido, se espera y es aceptable que este valor sea alto.
  • ANIMATION_START
    • La marca de tiempo en la que se ejecutaron las animaciones registradas con Choreographer.
    • Al observar el tiempo entre esto y PERFORM_TRANVERSALS_START, se puede determinar cuánto tiempo llevó evaluar todos los mecanismos de animación (los más comunes son ObjectAnimator, ViewPropertyAnimator y Transitions) que se estén ejecutando.
    • Si este valor es alto (superior a 2 ms), controla si tu app escribió alguna animación personalizada o qué campos está animando ObjectAnimators y asegúrate de que sean adecuados para una animación.
    • Para obtener más información sobre Choreographer, consulta el video For Butter or Worse.
  • PERFORM_TRAVERSALS_START
    • Si a este valor le restas DRAW_START, puedes saber cuánto tardaron en completarse las fases de medición y diseño. (Durante el desplazamiento o la animación, este número deberá ser cercano a cero).
    • Para obtener más información sobre las fases de medición y diseño de la canalización de representación, consulta el video Invalidations, Layouts and Performance.
  • DRAW_START
    • El momento en que comenzó la fase de dibujo de performTraversals. Este es el punto inicial de grabación de la listas de visualización de cualquier vista invalidada.
    • El tiempo entre esto y SYNC_START muestra cuánto se tardó en llamar a View.draw() en todas las vistas invalidadas en el árbol.
    • Para obtener más información sobre el modelo de dibujo, consulta los videos Hardware Acceleration o Invalidations, Layouts and Performance.
  • SYNC_QUEUED
    • El tiempo en el que se envió una solicitud de sincronización al RenderThread.
    • Esto marca el punto en el cual se envió un mensaje para iniciar la fase de sincronización al RenderThread. Si el tiempo entre este y SYNC_START es considerable (alrededor de >0,1 ms), significa que el RenderThread se encontraba ocupado trabajando en un fotograma diferente. Esto se utiliza internamente para diferenciar entre el fotograma que está haciendo demasiado trabajo y excede el límite de 16 ms, y el fotograma que se está deteniendo debido a que el fotograma anterior excede el límite de 16 ms.
  • SYNC_START
    • El momento en que comenzó la fase de sincronización del dibujo.
    • Si el tiempo entre esto e ISSUE_DRAW_COMMANDS_START es muy alto (superior a 0.4 ms o similar), generalmente esto significa que se dibujaron muchos mapas de bits que se deben subir a la GPU.
    • Para obtener más información sobre la fase de sincronización, consulta el video Profile GPU Rendering.
  • ISSUE_DRAW_COMMANDS_START
    • El momento en que el representador de hardware comenzó a enviar comandos de dibujo a la GPU.
    • El tiempo entre esto y FRAME_COMPLETED permite obtener una idea general sobre cuánto trabajo le genera la app a la GPU. Aquí aparecen los problemas como la superposición o los efectos de representación ineficientes.
  • SWAP_BUFFERS
    • El momento en que se llamó a eglSwapBuffers, generalmente de poca importancia fuera del trabajo en plataforma.
  • FRAME_COMPLETED
    • Todo listo. El tiempo total dedicado al trabajo en este fotograma se puede calcular haciendo FRAME_COMPLETED - INTENDED_VSYNC.

Puedes utilizar esta información de distintas maneras. Un método de visualización simple pero eficaz es el histograma que muestra la distribución de los tiempos del fotograma (FRAME_COMPLETED - INTENDED_VSYNC) en distintos bloques de latencia; ve la siguiente figura. Este gráfico indica brevemente que la mayoría de los fotogramas estuvieron muy bien, es decir, por debajo del límite de 16 ms (marcado en rojo). Sin embargo, algunos fotogramas estuvieron muy por encima del límite. En el histograma, podemos observar los cambios con el correr del tiempo para ver la creación de los cambios totales o los nuevos valores atípicos. También puedes graficar la latencia de entrada, el tiempo dedicado al diseño o cualquier otra medición interesante similar sobre las marcas de tiempo en los datos.

Volcado simple del intervalo del fotograma

Si, en las Opciones de Desarrollador, Profile GPU rendering se configura en In adb shell dumpsys gfxinfo, el comando adb shell dumpsys gfxinfo emite sobre el tiempo de los 120 fotogramas más recientes y los agrupa en algunas categorías diferentes con valores separados por tabulación. Esta información puede resultar útil para indicar qué partes de la canalización del dibujo podrían funcionar lento en un nivel alto.

Al igual que framestats, es muy sencillo pegar esta información en tu herramienta de hoja de cálculo preferida, o recolectar y redistribuir con un script. En el siguiente gráfico, se detalla dónde pasaron tiempo muchos de los fotogramas generados por la app.

El resultado de ejecutar gfxinfo, copiar la salida, pegar en una app de hoja de cálculo y graficar la información en forma de barras apiladas.

Cada barra vertical representa un fotograma de animación, su altura representa la cantidad de milisegundos que le llevó calcular ese fotograma de animación. Cada segmento de color de la barra representa una etapa diferente de la canalización de representación, de manera que puedas observar qué partes de tu app pueden estar creando un cuello de botella. Para obtener más información sobre la canalización de representación y cómo optimizarla, consulta el video Invalidations Layouts and Performance.

Control del período de recopilación de datos

Los intervalos de framestats y del fotograma simple recopilan datos durante un período muy breve: aproximadamente dos segundos de la representación. Para poder controlar este período con precisión, por ejemplo para limitar los datos a una animación en particular, puedes restablecer todos los contadores y agregar los datos recopilados.

    >adb shell dumpsys gfxinfo <PACKAGE_NAME> reset
    

Esto se puede usar junto con los comandos de volcado para recopilar y restablecer a una cadencia normal, a fin de capturar continuamente períodos de fotogramas de menos de dos segundos.

Diagnóstico de regresiones de rendimiento

La identificación de regresiones es un buen primer paso para localizar los problemas y mantener la app funcionando correctamente. Sin embargo, dumpsys solo identifica la existencia y la gravedad relativa de los problemas. Todavía debes diagnosticar la causa particular de los problemas de rendimiento y encontrar las soluciones adecuadas. Para esto, es sumamente recomendable que utilices la herramienta systrace.

Recursos adicionales

Para obtener más información sobre el funcionamiento de la canalización de representación de Android, los problemas comunes que puedes encontrar y cómo solucionarlos, es posible que algunos de los siguientes recursos te resulten útiles:

Automatización de las pruebas de rendimiento de la IU

Un enfoque para realizar la prueba de rendimiento de la IU es solicitar a un evaluador que realice una serie de operaciones de usuario en la app de destino para identificar visualmente bloqueos, o bien, pasar mucho tiempo utilizando un enfoque basado en alguna herramienta para encontrar bloqueos. Sin embargo, este enfoque manual tiene sus riesgos, ya que la habilidad humana para percibir cambios en los índices de los fotogramas varía de manera alarmante. Además, este proceso lleva mucho tiempo, es tedioso y propenso a errores.

Un método más eficiente es registrarse y analizar las mediciones de rendimiento clave a partir de pruebas automatizadas de IU. Android 6.0 incluye nuevas capacidades de registro que facilitan la determinación de la cantidad y gravedad de bloqueos en las animaciones de tu app, y pueden utilizarse para crear un proceso estricto a fin de determinar tu rendimiento actual y realizar un seguimiento de futuros objetivos de rendimiento.

Recursos adicionales

Para obtener más información sobre este tema, consulta los siguientes recursos.

Codelabs