Cómo mejorar el rendimiento de una app con los perfiles de Baseline

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

1. Antes de comenzar

En este codelab, aprenderás a generar perfiles de Baseline para optimizar el rendimiento de tu aplicación y a verificar los beneficios de usar los perfiles de Baseline.

Requisitos previos

Este codelab se basa en el codelab Cómo inspeccionar el rendimiento de una app con Macrobenchmark, que muestra cómo medir el rendimiento de las apps con la biblioteca de Macrobenchmark.

Requisitos

Actividades

  • Generar perfiles de Baseline para optimizar el rendimiento
  • Verificar los aumentos del rendimiento con la biblioteca de Macrobenchmark

Qué aprenderás

  • Cómo generar perfiles de Baseline
  • Cómo comprender las mejoras de rendimiento de los perfiles de Baseline

2. Cómo prepararte

Para comenzar, clona el repositorio de GitHub desde la línea de comandos usando el siguiente comando:

$ git clone --branch baselineprofiles-main  https://github.com/googlecodelabs/android-performance.git

Como alternativa, puedes descargar dos archivos ZIP:

Abre el proyecto en Android Studio

  1. En la ventana Welcome to Android Studio, selecciona c01826594f360d94.png Open an Existing Project.
  2. Selecciona la carpeta [Download Location]/android-performance/benchmarking (sugerencia: Asegúrate de seleccionar el directorio benchmarking que contiene build.gradle).
  3. Cuando Android Studio haya importado el proyecto, asegúrate de que puedes ejecutar el módulo app para compilar la aplicación de ejemplo que compararemos.

3. ¿Qué son los perfiles de Baseline?

Los perfiles de Baseline mejoran la velocidad de ejecución del código en aproximadamente un 30% desde el primer lanzamiento, ya que evitan la interpretación y los pasos de compilación just-in-time (JIT) para las instrucciones de código incluidas. Cuando se envía un perfil de Baseline a una app o biblioteca, Android Runtime (ART) puede optimizar las instrucciones de código incluidas a través de la compilación anticipada (AOT), lo que brinda mejoras de rendimiento para cada usuario nuevo y con cada actualización de la app. Esta optimización guiada por perfil (PGO) permite que las apps optimicen el inicio, reduzcan los bloqueos de interacción y mejoren el rendimiento general del entorno de ejecución para los usuarios finales desde el primer lanzamiento.

Beneficios de los perfiles de Baseline

Con un perfil de Baseline, todas las interacciones del usuario (como el inicio de la app, la navegación entre pantallas o el desplazamiento por el contenido) son más fluidas desde la primera vez que se ejecutan. El aumento de la velocidad y la capacidad de respuesta de una app genera más usuarios activos por día y un promedio más alto de la tasa de visitas recurrentes.

Los perfiles de Baseline ayudan a guiar la optimización más allá del inicio de la app, ya que proporcionan interacciones comunes del usuario que mejoran el tiempo de ejecución de la app desde el primer lanzamiento. La compilación anticipada guiada no depende de los dispositivos de los usuarios y se puede realizar una vez por versión en una máquina de desarrollo en lugar de un dispositivo móvil. Cuando se envían versiones con un perfil de Baseline, las optimizaciones de las apps están disponibles mucho más rápido que si solo se basara en los perfiles de Cloud.

Cuando no se usa un perfil de Baseline, todo el código de la app se compila de forma JIT en la memoria después de que se interpreta o en un archivo odex en segundo plano cuando el dispositivo está inactivo. Los usuarios tienen una experiencia menos óptima durante la primera vez que ejecutan una app después de instalarla o actualizarla hasta que se hayan optimizado las instrucciones nuevas. Este aumento del rendimiento se midió en alrededor del 30% para muchas apps.

4. Configura el módulo de comparativas

Como desarrollador de apps, puedes generar automáticamente perfiles de Baseline mediante la biblioteca de Jetpack Macrobenchmark. Para ello, puedes usar el mismo módulo creado con el fin de generar comparativas de tu aplicación y agregarle algunos cambios adicionales.

Cómo inhabilitar la ofuscación en los perfiles de Baseline

Si la app tiene habilitada la ofuscación, deberás inhabilitarla para las comparativas.

Para ello, agrega un archivo ProGuard adicional al módulo :app e inhabilita la ofuscación allí, y agrega el archivo ProGuard al archivo buildType de benchmark.

Crea un archivo nuevo llamado benchmark-rules.pro en el módulo :app. El archivo debe colocarse en la carpeta /app/, junto al archivo build.gradle específico del módulo. 27bd3b1881011d06.png

En este archivo, agrega -dontobfuscate para inhabilitar la ofuscación, como se muestra en el siguiente fragmento:

# Disables obfuscation for benchmark builds.
-dontobfuscate

A continuación, modifica el buildType de benchmark en el build.gradle específico del módulo :app y agrega el archivo que creaste. Como estamos usando el buildType de lanzamiento initWith, esta línea agregará el archivo ProGuard benchmark-rules.pro a los archivos ProGuard de lanzamiento.

buildTypes {
   release {
      // ...
   }

   benchmark {
      initWith buildTypes.release
      // ...
      proguardFiles('benchmark-rules.pro')
   }
}

Ahora, escribamos una clase de generador de perfiles de Baseline.

5. Escribe un generador de perfiles de Baseline

Por lo general, deberías generar perfiles de Baseline para los recorridos típicos de los usuarios de tu app.

En nuestro ejemplo, podrías identificar estos tres recorridos:

  1. Iniciar la aplicación (esto será fundamental para la mayoría de las aplicaciones)
  2. Desplazarse por la lista de bocadillos
  3. Ir al detalle del bocadillo

A fin de generar los perfiles de Baseline, agregaremos una nueva clase de prueba BaselineProfileGenerator en el módulo :macrobenchmark. Esta clase usará una regla de prueba BaselineProfileRule y contendrá un método para generar el perfil. El punto de entrada a los efectos de generar el perfil es la función collectBaselineProfile. Solo requiere dos parámetros:

  • packageName (el paquete de tu app)
  • profileBlock (el último parámetro de la lambda)
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {

   @get:Rule
   val rule = BaselineProfileRule()

   @Test
   fun generate() {
       rule.collectBaselineProfile("com.example.macrobenchmark_codelab") {
           // TODO Add interactions for the typical user journeys
       }
   }
}

En la lambda de profileBlock, puedes especificar las interacciones que cubren los recorridos típicos de los usuarios de tu app. La biblioteca ejecutará el profileBlock varias veces y recopilará las clases y funciones llamadas que se optimizarán, y generará el perfil de Baseline en el dispositivo.

Puedes revisar el esquema de nuestro generador de perfiles de Baseline que abarca los recorridos típicos en el siguiente fragmento:

@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {

   @get:Rule
   val rule = BaselineProfileRule()

   @Test
   fun generate() {
       rule.collectBaselineProfile("com.example.macrobenchmark_codelab") {
           startApplicationJourney() // TODO Implement
           scrollSnackListJourney() // TODO Implement
           goToSnackDetailJourney() // TODO Implement
       }
   }
}

Ahora, escribamos interacciones para cada recorrido mencionado. Puedes escribirla como la función de extensión de MacrobenchmarkScope a fin de tener acceso a los parámetros y las funciones que proporciona. Escribirla de esta manera te permite reutilizar las interacciones con las comparativas y así verificar las mejoras en el rendimiento.

Cómo iniciar el recorrido de la aplicación

Para el recorrido de inicio de la app (startApplicationJourney), deberás cubrir las siguientes interacciones:

  1. Presionar el botón de inicio para asegurarte de que se reinicie el estado de la app
  2. Iniciar la Actividad predeterminada y esperar a que se renderice el primer fotograma
  3. Esperar hasta que el contenido se cargue y se renderice, y el usuario pueda interactuar con él
fun MacrobenchmarkScope.startApplicationJourney() {
   pressHome()
   startActivityAndWait()
   val contentList = device.findObject(By.res("snack_list"))
   // Wait until a snack collection item within the list is rendered
   contentList.wait(Until.hasObject(By.res("snack_collection")), 5_000)
}

Recorrido por la lista de desplazamiento

Para obtener el recorrido por la lista de desplazamiento de bocadillos (scrollSnackListJourney), puedes seguir estas interacciones:

  1. Busca el elemento de la IU de la lista de bocadillos.
  2. Establece los márgenes de gestos de modo que no se active la navegación del sistema.
  3. Desplázate por la lista y espera hasta que la IU se detenga.
fun MacrobenchmarkScope.scrollSnackListJourney() {
   val snackList = device.findObject(By.res("snack_list"))
   // Set gesture margin to avoid triggering gesture navigation
   snackList.setGestureMargin(device.displayWidth / 5)
   snackList.fling(Direction.DOWN)
   device.waitForIdle()
}

Cómo ir al recorrido detallado

El último recorrido (goToSnackDetailJourney) implementa estas interacciones:

  1. Buscar la lista de bocadillos y todos aquellos con los que puedas trabajar
  2. Seleccionar un elemento de la lista
  3. Hacer clic en el elemento y esperar hasta que se cargue la pantalla de detalles Puedes aprovechar el hecho de que la lista de bocadillos ya no aparecerá en la pantalla.
fun MacrobenchmarkScope.goToSnackDetailJourney() {
   val snackList = device.findObject(By.res("snack_list"))
   val snacks = snackList.findObjects(By.res("snack_item"))
   // Select random snack from the list
   snacks[Random.nextInt(snacks.size)].click()
   // Wait until the screen is gone = the detail is shown
   device.wait(Until.gone(By.res("snack_list")), 5_000)
}

Ya definiste todas las interacciones necesarias para que nuestro generador de perfiles de Baseline esté listo para ejecutarse, pero primero debes definir en qué dispositivo deberá ejecutarse.

6. Prepara un dispositivo administrado por Gradle

Para generar perfiles de Baseline, primero debes tener un emulador de userdebug listo. A fin de automatizar el proceso de creación de perfiles de Baseline, puedes usar los dispositivos administrados por Gradle. Obtén más información sobre los dispositivos administrados por Gradle en nuestra documentación.

Primero, define el dispositivo administrado por Gradle en el archivo build.gradle del módulo :macrobenchmark, como se muestra en el siguiente fragmento:

testOptions {
    managedDevices {
        devices {
            pixel2Api31(com.android.build.api.dsl.ManagedVirtualDevice) {
                device = "Pixel 2"
                apiLevel = 31
                systemImageSource = "aosp"
            }
        }
    }
}

Para generar perfiles de Baseline, debes usar Android 9 (nivel de API 28) o versiones posteriores, con permisos de administrador.

En nuestro caso, usaremos Android 11 (nivel de API 31), y la imagen del sistema aosp es capaz de acceder con permisos de administrador.

El dispositivo administrado por Gradle te permite ejecutar pruebas en un emulador de Android sin necesidad de iniciarlo y desmontarlo de forma manual. Después de agregar la definición a build.gradle, la tarea pixel2Api31[BuildVariant]AndroidTest nueva estará disponible para ejecutarse. Usaremos esa tarea en el siguiente paso a fin de generar el perfil de Baseline.

7. Genera el perfil de Baseline

Una vez que tengas listo el dispositivo administrado por Gradle, podrás iniciar la prueba del generador.

Cómo ejecutar el generador desde la configuración de ejecución

Los dispositivos administrados por Gradle requieren que se ejecute la prueba como una tarea de Gradle. A los efectos de comenzar rápidamente, creamos una configuración de ejecución que especifica la tarea con todos los parámetros necesarios para ejecutarla.

Para ejecutarla, busca la configuración de ejecución generateBaselineProfile y haz clic en el botón Ejecutar 229e32fcbe68452f.png.

8f6b7c9a5da6585.png

La prueba creará la imagen del emulador definida anteriormente, ejecutará las interacciones varias veces y, luego, quitará el emulador y proporcionará el resultado a Android Studio.

4b5b2d0091b4518c.png

Cómo ejecutar el generador desde la línea de comandos (opcional)

Para ejecutar el generador desde la línea de comandos, puedes aprovechar la tarea creada por el dispositivo administrado por Gradle: :macrobenchmark:pixel2Api31BenchmarkAndroidTest.

Este comando ejecuta todas las pruebas del proyecto, que fallarían, ya que el módulo también contiene comparativas para una verificación posterior de las mejoras en el rendimiento.

Para ello, puedes filtrar la clase que deseas ejecutar con el parámetro -P android.testInstrumentationRunnerArguments.class y especificar el com.example.macrobenchmark.BaselineProfileGenerator que escribiste antes.

El comando completo se verá de la siguiente manera:

./gradlew :macrobenchmark:pixel2Api31BenchmarkAndroidTest -P android.testInstrumentationRunnerArguments.class=com.example.macrobenchmark.BaselineProfileGenerator

8. Aplica el perfil de Baseline generado

Una vez que el generador termine de manera correcta, debes seguir varios pasos de modo que el perfil de Baseline funcione con tu app.

Debes colocar el archivo generado de perfiles de Baseline en tu carpeta src/main (junto a AndroidManifest.xml). Para recuperar el archivo, puedes copiarlo desde la carpeta managed_device_android_test_additional_output/, que se encuentra en /macrobenchmark/build/outputs/, como se muestra en la siguiente captura de pantalla.

b104f315f06b3578.png

Como alternativa, puedes hacer clic en el vínculo de results en el resultado de Android Studio y guardar el contenido, o bien usar el comando adb pull impreso en el resultado.

A continuación, debes cambiar el nombre del archivo a baseline-prof.txt.

8973f012921669f6.png

Luego, agrega la dependencia profileinstaller a tu módulo :app.

dependencies {
  implementation("androidx.profileinstaller:profileinstaller:1.2.0")
}

Agregar esta dependencia te permite hacer lo siguiente:

  • Realizar comparativas locales de los perfiles de Baseline
  • Usar perfiles de Baseline en Android 7 (nivel de API 24) y Android 8 (nivel de API 26), que no son compatibles con los perfiles de Cloud
  • Usar los perfiles de Baseline en dispositivos que no cuenten con los Servicios de Google Play

Por último, sincroniza el proyecto con archivos de Gradle haciendo clic en el ícono 1079605eb7639c75.png.

40cb2ba3d0b88dd6.png

En el siguiente paso, veremos cómo comprobar cuánto mejor es el rendimiento de la app con los perfiles de Baseline.

9. Verifica la mejora del rendimiento de inicio

Ya generamos el perfil de Baseline y lo agregamos a nuestra app. Verifiquemos que tenga el efecto deseado en el rendimiento de nuestra app.

Volvamos a nuestra clase ExampleStartupBenchmark que contiene una comparativa que mide el inicio de la app. Deberás hacer unos pequeños cambios a la prueba de startup() de modo que se vuelva a usar con diferentes modos de compilación. Esto te permitirá comparar la diferencia cuando uses perfiles de Baseline.

CompilationMode

El parámetro CompilationMode define cómo se compila previamente la aplicación en código máquina. Tiene las siguientes opciones:

  • DEFAULT: Hace una compilación previa parcial de la app mediante perfiles de Baseline, si están disponibles (esto se usa si no se aplica ningún parámetro compilationMode).
  • None(): Restablece el estado de compilación de la app y no realiza una compilación previa. La compilación justo a tiempo (JIT) continúa habilitada durante la ejecución de la app.
  • Partial(): Hace una compilación previa de la app con perfiles de Baseline o ejecuciones de preparación.
  • Full(): Hace una compilación previa de todo el código de la aplicación. Esta es la única opción en Android 6 (API 23) y versiones anteriores.

Si quieres comenzar a optimizar el rendimiento de tu aplicación, puedes elegir el modo de compilación DEFAULT, que será similar a cuando se instala la app desde Google Play. Si deseas comparar los beneficios de rendimiento que ofrecen los perfiles de Baseline, puedes hacerlo comparando los resultados de los modos de compilación None y Partial.

Cómo modificar la prueba de inicio con diferentes CompilationMode

Primero, quitaremos la anotación @Test del método startup (porque las pruebas JUnit no pueden tener parámetros), agregaremos el parámetro compilationMode y lo usaremos en nuestra función measureRepeated:

// Remove @Test annotation and add the compilationMode parameter.
fun startup(compilationMode: CompilationMode) = benchmarkRule.measureRepeated(
   packageName = "com.google.samples.apps.sunflower",
   metrics = listOf(StartupTimingMetric()),
   iterations = 5,
   compilationMode = compilationMode, // Set the compilation mode
   startupMode = StartupMode.COLD
) {
   pressHome()
   startActivityAndWait()
}

Ahora que tienes eso, agreguemos dos funciones de prueba con diferentes CompilationMode. La primera usará CompilationMode.None, lo que significa que, antes de cada comparativa, se restablecerá el estado de la app y esta no tendrá código ya compilado.

@Test
fun startupCompilationNone() = startup(CompilationMode.None())

La segunda prueba utilizará CompilationMode.Partial, que carga los perfiles de Baseline y realiza una compilación previa de las clases y funciones especificadas desde el perfil antes de ejecutar las comparativas.

@Test
fun startupCompilationPartial() = startup(CompilationMode.Partial())

De manera opcional, puedes agregar un tercer método que pueda hacer una compilación previa de toda la aplicación mediante CompilationMode.Full. Esta es la única opción en Android 6 (nivel de API 23) o versiones anteriores porque el sistema solo ejecuta apps con una compilación previa completa.

@Test
fun startupCompilationFull() = startup(CompilationMode.Full())

A continuación, ejecuta las comparativas como lo hiciste antes (en un dispositivo físico) y espera a que Macrobenchmark mida los tiempos de inicio con diferentes modos de compilación.

Después de que se completen las comparativas, podrás ver los tiempos en el resultado de Android Studio, como se muestra en la siguiente captura de pantalla:

98e01ce602447001.png

En la captura de pantalla, puedes ver que la hora de inicio de la app es diferente para cada CompilationMode. Los valores de la mediana se muestran en la siguiente tabla:

timeToInitialDisplay [ms]

timeToFullDisplay [ms]

Ninguno

396.8

818.1

Completo

373.9

755.0

Parcial

352.9

720.9

De manera intuitiva, la compilación de None tiene el peor rendimiento, ya que el dispositivo debe realizar la mayor compilación de JIT durante el inicio de la app. Lo que puede ser contradictorio es que la compilación de Full no tiene el mejor rendimiento. Como todo está compilado en este caso, los archivos odex de la app son grandes y, por lo tanto, el sistema en general debe hacer mucho más IO durante el inicio de la app. El mejor rendimiento es el caso de Partial que usa el perfil de Baseline. Esto se debe a que la compilación parcial logra un equilibrio en la compilación del código que con mayor probabilidad se usará, pero no se hace una compilación previa del código que no resulta crítico de modo que no deba cargarse de inmediato.

10. Verifica la mejora en el rendimiento de desplazamiento

Al igual que en el paso anterior, puedes medir y verificar las comparativas de desplazamiento. Modifiquemos la clase ScrollBenchmarks de la misma manera que antes: agregando un parámetro a la prueba scroll y más pruebas con un parámetro de modo de compilación diferente.

Abre el archivo ScrollBenchmarks.kt y modifica la función scroll() a fin de agregar el parámetro compilationMode:

fun scroll(compilationMode: CompilationMode) {
        benchmarkRule.measureRepeated(
            compilationMode = compilationMode, // Set the compilation mode
            // ...

Ahora, define varias pruebas que usen un parámetro diferente:

@Test
fun scrollCompilationNone() = scroll(CompilationMode.None())

@Test
fun scrollCompilationPartial() = scroll(CompilationMode.Partial())

Ejecuta las comparativas como antes a los efectos de obtener resultados, como en la siguiente captura de pantalla:

e856a7dad7dccd72.png

En los resultados, puedes ver que, en promedio, CompilationMode.Partial tiene una latencia de fotogramas 0.4 ms más corta, lo cual puede no ser evidente para los usuarios, pero en los otros percentiles los resultados resultan más obvios. Para P99, la diferencia es de 36.9 ms, que es más de 3 fotogramas omitidos (Pixel 7 ejecuta 90 FPS, por lo que es de aproximadamente 11 ms).

11. Felicitaciones

¡Felicitaciones! Completaste correctamente este codelab y mejoraste el rendimiento de tu app con los perfiles de Baseline.

¿Qué sigue?

Consulta nuestro repositorio de GitHub de ejemplos de rendimiento, que contiene Macrobenchmark y otros ejemplos de rendimiento. Además, consulta la app de ejemplo de Ahora en Android, una aplicación real que usa comparativas y perfiles de Baseline a fin de mejorar el rendimiento.

Documentos de referencia