Perfiles de rendimiento

Lo que aprenderás:

  • El objetivo de Flutter es proporcionar un rendimiento de 60 frames por segundo (fps), o un rendimiento de 120 fps en dispositivos capaces de realizar actualizaciones de 120 Hz.
  • Para 60fps, los frames se renderizan aproximadamente cada 16ms.
  • El Jank se produce cuando la UI no se renderiza fluidamente. Por ejemplo, de vez en cuando, un frame tarda 10 veces más en renderizarse, por lo que se descarta, y la animación se sacude visiblemente.

Se dice que “una aplicación rápida es genial, pero una fluida es aún mejor”. Si tu aplicación no se está renderizando correctamente, ¿cómo la arreglas? ¿Por dónde empiezas? Esta guía te muestra por dónde empezar, los pasos a seguir y las herramientas que pueden ayudarte.

Diagnóstico de problemas de rendimiento

Para diagnosticar una aplicación con problemas de rendimiento, habilitarás la capa sobrepuesta de rendimiento para ver los subprocesos de la UI y la GPU. Antes de empezar, deberás asegurarte de que estás ejecutando en modo profile y de que no estás usando un emulador. Para obtener mejores resultados, puedes elegir el dispositivo más lento que tus usuarios puedan utilizar.

Conéctate a un dispositivo físico

Casi toda la depuración del rendimiento de las aplicaciones Flutter debe realizarse en un dispositivo físico Android o iOS, con su aplicación Flutter ejecutándose en modo profile. Usar el modo debug, o ejecutar aplicaciones en simuladores o emuladores, generalmente no es indicativo del comportamiento final de las compilaciones del modo release. Deberías considerar comprobar el rendimiento en el dispositivo más lento que tus usuarios puedan usar razonablemente.

Ejecutar en modo profile

El modo profile de Flutter compila e inicia tu aplicación de forma casi idéntica al modo release, pero con la funcionalidad adicional suficiente para permitir la depuración de problemas de rendimiento. Por ejemplo, el modo de perfil proporciona información de trazabilidad a Observatory y otras herramientas.

Lanza la aplicación en modo profile de la siguiente manera:

  • En Android Studio e IntelliJ, usa el elemento del menú Run > Flutter Run main.dart in Profile Mode.

  • En VS Code, abre tu archivo launch.json, y asigna la propiedad flutterMode a profile (cuando termines el profile, cámbialo de nuevo hacia release o debug):

    "configurations": [
    	{
    		"name": "Flutter",
    		"request": "launch",
    		"type": "dart",
    		"flutterMode": "profile"
    	}
    ]
    
  • Desde la línea de comando, usa el parámetro --profile:

    $ flutter run --profile

Para obtener más información sobre cómo funcionan los diferentes modos, consulta Modos en Flutter.

Comenzarás activando la capa sobrepuesta de rendimiento, como se explica en la siguiente sección.

La capa sobrepuesta de rendimiento

La capa sobrepuesta de rendimiento muestra las estadísticas en dos gráficos que muestran dónde se está gastando el tiempo tu aplicación. Si la UI está en jank (saltando frames), estos gráficos te ayudan a averiguar por qué. Los gráficos se muestran encima de tu aplicación en ejecución, pero no se dibujan como un widget normal; el propio motor Flutter pinta la capa sobrepuesta de rendimiento y sólo tiene un impacto mínimo en el rendimiento. Cada gráfico representa los últimos 300 frames para ese hilo.

En esta sección se describe cómo habilitar la función PerformanceOverlay, y usarla para diagnosticar la causa del jank en tu aplicación. La siguiente captura de pantalla muestra la capa sobrepuesta de rendimiento que se está ejecutando en el ejemplo de Flutter Gallery:

screenshot of performance overlay showing zero jank
La capa sobrepuesta de rendimiento muestra el hilo de la UI (arriba) y el hilo de la GPU (abajo). Las barras verdes verticales representan el frame actual.


Flutter utiliza varios hilos para hacer su trabajo. Todo tu código de Dart se ejecuta en el hilo de la UI. Aunque no tienes acceso directo a ningún otro hilo, tus acciones en el hilo de la UI tienen consecuencias de rendimiento en otros hilos.

  1. Platform thread
    El hilo principal de la plataforma. El código del Plugin se ejecuta aquí. Para más información, consulta la documentación para iOS UIKit , o la documentación para Android MainThread. Este hilo no se muestra en la capa sobrepuesta de rendimiento.

  2. UI thread
    El hilo UI ejecuta el código Dart en la VM de Dart. Este hilo incluye el código que escribiste, y el código ejecutado por el framework de Flutter en beneficio de tu aplicación. Cuando la aplicación crea y muestra una escena, el subproceso de la interfaz de usuario crea un árbol de capas, un objeto ligero que contiene comandos de trazado agnósticos del dispositivo, y envía el árbol de capas al hilo de la GPU para que se renderice en el dispositivo. No bloquees este hilo! Se muestra en la fila inferior de la capa sobrepuesta de rendimiento.

  3. GPU thread
    El hilo de la GPU toma el árbol de capas y lo muestra hablando a la GPU (unidad de procesamiento gráfico). No puedes acceder directamente al hilo de la GPU o a sus datos, pero, si este hilo es lento, es el resultado de algo que has hecho en el código de Dart. Skia, la biblioteca de gráficos se ejecuta en este hilo, que a veces se llama el hilo rasterizador. Se muestra en la fila inferior de la capa sobrepuesta de rendimiento.

  4. I/O thread
    Realiza tareas costosas (principalmente I/O) que de otro modo bloquearían ya sea la UI o la GPU. Este hilo no se muestra en la capa sobrepuesta de rendimiento.

Para más información sobre esos hilos, consulta Notas de la Arquitectua.

Cada frame debe ser creado y mostrado en 1/60 de segundo. (aproximadamente 16ms). Un frame que excede este límite (en cualquier gráfico) no se muestra, resultando en jank, y una barra roja vertical aparece en una o ambas gráficas. Si aparece una barra roja en el gráfico de UI, el código de Dart sale demasiado costoso. Si aparece una barra vertical roja en el gráfico de la GPU, la escena se hace demasiado complicada como para renderizar rápidamente.

Screenshot of performance overlay showing jank with red bars.
Las barras rojas verticales indican que el frame actual es muy costoso tanto para el renderizado como para el pintado.
Cuando ambas gráficas estén rojas, comienza por diagnosticar el hilo de la UI (Dart VM).


Visualización de la capa sobrepuesta de rendimiento

Puede alternar la visualización de la capa sobrepuesta de rendimiento como se indica a continuación:

  • Usando el Flutter Inspector
  • Desde la línea de comando
  • Programáticamente

Usando el Flutter Inspector

La manera más fácil de habilitar el widget PerformanceOverlay es habilitándolo en el Flutter Inspector, que está disponible a través del plugin Flutter para su IDE. La vista Inspector se abre de forma predeterminada cuando se ejecuta una aplicación. Si el inspector no está abierto, puedes mostrarlo de la siguiente manera.

En Android Studio e IntelliJ IDEA:

  1. Selecciona View > Tool Windows > Flutter Inspector.
  2. En la barra de herramientas, selecciona el icono que parece una biblioteca (icon that resembles a bookshelf).

IntelliJ Flutter Inspector Window

El Flutter Inspector está disponible en Android Studio e IntelliJ. Obtenga más información sobre lo que el Inspector puede hacer en el documento Inspecciona tu UI, así como el archivo Flutter Inspector talk presentado en el DartConf 2018.

En VS Code

  1. Seleciona View > Command Palette… para mostrar la paleta de comandos.
  2. En el campo de texto, escribes “performance” y selecciona Toggle Performance Overlay de la lista que aparece. Si este comando no está disponible, asegúrate de que la aplicación esté ejecutándose.

Desde la línea de Comando

Conmute la capa sobrepuesta de rendimiento con la tecla P desde la línea de comando.

Programáticamente

Puede habilitar programáticamente el widget PerformanceOverlay estableciendo la propiedad showPerformanceOverlay en true en el constructor de MaterialApp o WidgetsApp:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      showPerformanceOverlay: true,
      title: 'My Awesome App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'My Awesome App'),
    );
  }
}

Probablemente ya conozcas la aplicación de ejemplo de Flutter Gallery. Para utilizar la capa sobrepuesta de rendimiento con Flutter Gallery, usa la copia en el directorio de ejemplos que fue instalado con Flutter, y ejecuta la aplicación en modo profile. El programa se escribe de manera que el menú de la app le permita cambiar dinámicamente la ventana superpuesta, así como habilitar la comprobación de llamadas a saveLayer y la presencia de imágenes en caché.

Identificando problemas en la UI de gráficas

Si la capa sobrepuesta de rendimiento aparece en rojo en el gráfico de interfaz de usuario, comienza por perfilar la VM de Dart, incluso si el gráfico de la GPU también aparece en rojo. Para ello, utiliza Observatory, la herramienta para profile de Dart.

Mostrando el Observatory

El Observatory proporciona características como la creación de perfiles, el examen del heap, y la visualización de la cobertura de código. La vista timeline del Observatory le permite capturar una instantánea del stack en un momento dado. Al abrir el timeline del Observatory desde el Flutter Inspector, usarás una versión que ha sido personalizada para las aplicaciones Flutter.

Ve a la vista del timeline de Flutter desde el navegador así:

  1. Para abrir la vista del timeline, usa el ícono del gráfico de líneas (zig-zag line chart icon).

    (En su lugar, puedes abrir Observatory usando el icono del cronómetro (stopwatch icon used by Observatory), pero el enlace “view inspector” te lleva a la versión estándar del timeline, no a la versión personalizada para Flutter).

    IntelliJ Flutter Inspector Window

  2. En VS Code, abre la paleta de comandos e introduce “observatory”. Selecciona Flutter: Open Observatory Timeline de la lista que aparece. Si este comando no está disponible, asegúrate de que la aplicación esté ejecutándose.


Usando el timeline de Observatory

Identificación de problemas en el gráfico de la GPU

A veces, una escena da como resultado un árbol de capas que es fácil de construir, pero cuyo renderizado en el hilo de la GPU es costoso. Cuando esto sucede, el gráfico de UI no tiene color rojo, pero el gráfico de la GPU muestra color rojo. En este caso, tendrás que averiguar qué está haciendo tu código para que el código de renderizado sea lento. Los tipos específicos de cargas de trabajo son más difíciles para la GPU. Pueden implicar llamadas innecesarias a saveLayer, opacidades entrecruzadas con múltiples objetos, y acoplados o sombras en situaciones específicas.

Si sospechas que la fuente de la lentitud es durante una animación, usa la propiedad del botón timeDilation para ralentizar enormemente la animación.

También puedes reducir la velocidad de la animación utilizando el Flutter Inspector. En el menú de engranajes del inspector, selecciona Enable Slow Animations. Si deseas un mayor control de la velocidad de la animación, establece la propiedad timeDilation en tu código.

¿La lentitud está en el primer frame o en toda la animación? Si se trata de toda la animación, ¿el acoplado está causando la ralentización? Tal vez haya una forma alternativa de dibujar la escena que no utilice el acoplado. Por ejemplo, superponga esquinas opacas a un cuadrado en lugar de hacer recortes en un rectángulo redondeado. Si se trata de una escena estática que se está desvaneciendo, girando o manipulando de otra manera, tal vez un RepaintBoundary puede ayudar.

Comprobación de capas fuera de pantalla

El método saveLayer es uno de los métodos más costosos en el framework Flutter. Es útil cuando se aplica el post procesamiento a la escena, pero puede ralentizar tu aplicación y debería evitarse si no la necesitas. Incluso si no llama a saveLayer explícitamente, pueden producirse llamadas implícitas en su nombre. Puedes comprobar si tu escena está usando saveLayer con el switch PerformanceOverlayLayer.checkerboardOffscreenLayers.

Una vez que el switch esté habilitado, ejecuta la aplicación y busca cualquier imagen que este delineada con un cuadro parpadeante. La caja parpadea de frame a frame si un nuevo frame se está renderizando. Por ejemplo, tal vez tenga un grupo de objetos con opacidades que se renderizan utilizando saveLayer. En este caso, probablemente sea más eficaz aplicar una opacidad a cada widget individual, en lugar de un widget padre más arriba en el árbol de widgets. Lo mismo ocurre con otras operaciones potencialmente costosas, como acoplamiento o sombras.

Cuando encuentres llamadas a saveLayer, hazte estas preguntas:

  • ¿Necesita la aplicación este efecto?
  • ¿Se puede eliminar alguna de estas llamadas?
  • ¿Puedo aplicar el mismo efecto a un elemento individual en lugar de a un grupo?

Comprobación de imágenes no almacenadas en caché

Almacenamiento en caché de una imagen con RepaintBoundary es bueno, cuando tiene sentido.

Una de las operaciones más costosas, desde la perspectiva de los recursos, es renderizar una textura usando un archivo de imagen. Primero, la imagen comprimida se obtiene del almacenamiento persistente. La imagen se descomprime en la memoria del host (memoria de la GPU) y se transfiere. a la memoria del dispositivo (RAM).

En otras palabras, la I/O de imagenes puede ser costosa. La caché proporciona instantáneas de jerarquías complejas para que sea más fácil en los frames siguientes. Debido a que las entradas de caché de imágenes tipo raster o "bitmap image" son costosas de construir y ocupan mucha memoria de la GPU, almacena en caché imágenes sólo cuando sea absolutamente necesario.

Puedes ver qué imágenes están siendo almacenadas en caché activando el switch PerformanceOverlayLayer.checkerboardRasterCachedImages.

Ejecuta la app y busca imágenes renderizadas con un tablero de colores aleatorios, indicando que la imagen está almacenada en caché. A medida que interactúas con la escena, las imágenes en el tablero deben permanecer constantes; no quieres ver parpadeos, lo que indicaría que la imagen en caché se está volviendo a almacenar.

En la mayoría de los casos, deseas ver los tableros en imágenes estáticas, pero no en imágenes no estáticas. Si una imagen estática no está almacenada en caché, puedes guardarla en caché colocándola en un widget RepaintBoundary. Aunque el motor puede ignorar un límite de repintado si piensa que la imagen no es lo suficientemente compleja.

Indicadores de depuración

Flutter proporciona una amplia variedad de indicadores y funciones de depuración para ayudarle a depurar su aplicación en varios puntos a lo largo del ciclo de desarrollo. Para usar estas características, debe compilar en modo debug. La siguiente lista, aunque no completa, resalta algunas de los indicadores más útiles (y una función) desde la biblioteca de renderizado para depurar problemas de rendimiento.

  • debugDumpRenderTree()
    Llama a esta función, cuando no esté en una fase de diseño o repintado, para volcar el árbol de renderizado a la consola. (Pulsa t desde flutter run para llamar a este comando.) Busca por “RepaintBoundary” para ver diagnósticos sobre lo útil que es un límite.
  • debugPaintLayerBordersEnabled
  • debugRepaintRainbowEnabled
    Habilita esta propiedad y ejecuta tu aplicación para ver si hay partes de tu UI que no estén cambiando (por ejemplo, un encabezado estático) que estén rotando a través de muchos colores en la salida. Esas áreas son candidatas para agregar límites de repintado
  • debugPrintMarkNeedsLayoutStack
    Habilita esta propiedad si estás viendo más diseños de los que esperas (por ejemplo, en el timeline, en un profile o desde una sentencia “print” dentro de un método de diseño). Una vez activada, la consola se inunda de trazos de pila que muestran por qué cada objeto renderizado está siendo marcado como corrupto para el diseño.
  • debugPrintMarkNeedsPaintStacks
    Similar a debugPrintMarkNeedsLayoutStack, pero por exceso de pintado.

Puedes obtener más información sobre otros indicadores de depuración en Depurando Apps de Flutter.

Benchmarking

Puedes medir y hacer un seguimiento del rendimiento de tu aplicación escribiendo pruebas de referencia. La biblioteca Flutter Driver proporciona soporte para el benchmarking. Usando este framework de pruebas de integración, es posible generar métricas para realizar el seguimiento de lo siguiente:

  • Jank
  • Tamaño de descarga
  • Eficiencia de la batería
  • Tiempo de inicio

El seguimiento de estos puntos de referencia te permite estar informado cuando se introduce una regresión que afecta negativamente al rendimiento.

Para más información, consulta Pruebas de Integration, una sección en Prueba tu app.

Más información

Los siguientes recursos proporcionan más información sobre el uso de las herramientas de Flutter y depurando en Flutter: