Perfiles de Baseline

Los perfiles de Baseline son una lista de clases y métodos incluidos en un APK que usa Android Runtime (ART) durante la instalación para precompilar rutas de acceso críticas al código máquina. Es un tipo de optimización guiada por perfil (PGO, por su sigla en inglés) que permite a las apps optimizar el inicio, reducir los bloqueos y mejorar el rendimiento para los usuarios finales.

Cómo funcionan los perfiles de Baseline

Las reglas de perfil se compilan en un formato binario en el APK, en assets/dexopt/baseline.prof.

Durante la instalación, ART lleva a cabo una compilación anticipada de métodos (AOT, por su sigla en inglés) en el perfil, lo que hace que esos métodos se ejecuten más rápido. Si el perfil contiene métodos usados en el inicio de la app o durante el procesamiento de fotogramas, el usuario experimentará tiempos de inicio más rápidos o menos bloqueos.

Mientras desarrollas tu app o biblioteca, procura definir perfiles de Baseline para abarcar rutas activas específicas durante recorridos críticos del usuario en los que el tiempo de procesamiento o la latencia sean importantes, como el inicio, las transiciones o el desplazamiento.

Luego, los perfiles de Baseline se enviarán directamente a los usuarios (a través de Google Play) junto con el APK.

En este diagrama, se muestra el flujo de trabajo de perfil de Baseline desde la carga hasta la entrega a usuarios finales y cómo se relaciona ese flujo de trabajo con los perfiles de nube.

Motivos para usar perfiles de Baseline

El tiempo de inicio es un componente crítico para mejorar el compromiso de los usuarios con tu aplicación. Aumentar la velocidad y capacidad de respuesta de una app genera más usuarios activos por día y una tasa de visitas promedio más alta.

Los perfiles de nube también optimizan estas mismas interacciones, pero solo están disponibles para los usuarios un día o más después del lanzamiento de una actualización y no admiten Android 7 (nivel de API 24) hasta Android 8 (nivel de API 26).

Comportamiento de la compilación en las versiones de Android

Las versiones de la plataforma de Android usan diferentes enfoques de compilación de apps, cada uno con una compensación de rendimiento correspondiente. Los perfiles de Baseline mejoran los métodos de compilación anteriores, ya que proporcionan un perfil para todas las instalaciones.

Versión de Android Método de compilación Enfoque de optimización
Android 5 (nivel de API 21) y Android 6 (nivel de API 23) AOT completa Toda la app está optimizada durante la instalación, lo que acorta los tiempos de espera, aumenta el uso de espacio en el disco y la memoria RAM, y aumenta los tiempos de carga de código desde el disco, lo que puede aumentar el tiempo de inicio en frío.
Android 7 (nivel de API 24) hasta Android 8.1 (nivel de API 27) AOT parcial (perfil de Baseline) androidx.profileinstaller instala los perfiles de Baseline en la primera ejecución, cuando el módulo de la app define esta dependencia. ART puede mejorar aún más esta función agregando más reglas de perfil durante el uso de la app y compilándolas cuando el dispositivo esté inactivo. Esto optimiza el espacio en disco y el tiempo de carga de código desde el disco, lo que reduce el tiempo de espera para la app.
Android 9 (nivel de API 28) y versiones posteriores AOT parcial (modelo de referencia + perfil de nube) Play usa los perfiles de Baseline durante las instalaciones de la app para optimizar los APK y los perfiles de nube (si están disponibles). Después de la instalación, los perfiles de ART se suben a Play y se agregan, y luego se proporcionan como perfiles de nube a otros usuarios cuando instalan o actualizan la app.

Cómo crear perfiles de Baseline

Crea reglas de perfil automáticamente con BaselineProfileRule

Como desarrollador de apps, puedes generar perfiles automáticamente para cada versión de la app con la biblioteca de Jetpack Macrobenchmark.

Sigue estos pasos para crear perfiles de Baseline con la biblioteca de Macrobenchmark:

  1. Agrega una dependencia a la biblioteca de ProfileInstaller en el objeto build.gradle de tu app para habilitar la compilación de perfiles de Baseline locales y de Play Store. Esta es la única manera de transferir un perfil de Baseline de forma local.

    dependencies {
         implementation("androidx.profileinstaller:profileinstaller:1.2.0-alpha01")
    }
    
  2. Configura un módulo de Macrobenchmark en tu proyecto de Gradle.

  3. Define una prueba nueva llamada BaselineProfileGenerator que se vea de la siguiente manera:

    @ExperimentalBaselineProfilesApi
    @RunWith(AndroidJUnit4::class)
    class BaselineProfileGenerator {
        @get:Rule val baselineProfileRule = BaselineProfileRule()
    
        @Test
        fun startup() =
            baselineProfileRule.collectBaselineProfile(packageName = "com.example.app") {
                pressHome()
                // This block defines the app's critical user journey. Here we are interested in
                // optimizing for app startup. But you can also navigate and scroll
                // through your most important UI.
                startActivityAndWait()
            }
    }
    
  4. Conecta un userdebug o el emulador de Android Open Source Project (AOSP) con Android 9 o versiones posteriores.

  5. Ejecuta adb root para asegurarte de tener permisos de administrador.

  6. Ejecuta la prueba y busca la ubicación del perfil generado en logcat. Busca la etiqueta de registro Benchmark.

    com.example.app D/Benchmark: Usable output directory: /storage/emulated/0/Android/media/com.example.app
    
    # List the output baseline profile
    $ ls /storage/emulated/0/Android/media/com.example.app
    SampleStartupBenchmark_startup-baseline-prof.txt
    
  7. Extrae el archivo generado de tu dispositivo.

    $ adb pull storage/emulated/0/Android/media/com.example.app/SampleStartupBenchmark_startup-baseline-prof.txt .
    
  8. Cambia el nombre del archivo generado a baseline-prof.txt y cópialo en el directorio src/main del módulo de tu aplicación.

Cómo definir manualmente reglas de perfil

Puedes definir manualmente reglas de perfil en una app o un módulo de biblioteca. Para ello, crea un archivo llamado baseline-prof.txt ubicado en el directorio src/main. Esta es la misma carpeta que contiene el archivo AndroidManifest.xml.

El archivo especifica una regla por línea. Cada regla representa un patrón para buscar coincidencias de métodos o clases en la app o biblioteca que se debe optimizar.

La sintaxis de estas reglas es un superconjunto del formato de perfil de ART legible (HRF, por su sigla en inglés) cuando se usa adb shell profman --dump-classes-and-methods. La sintaxis es muy similar a la sintaxis para descriptores y firmas, pero también permite que los comodines simplifiquen el proceso de escritura de reglas.

En los siguientes ejemplos, se muestran algunas reglas del perfil de Baseline que se incluyen en la biblioteca de Jetpack Compose:

HSPLandroidx/compose/runtime/ComposerImpl;->updateValue(Ljava/lang/Object;)V
HSPLandroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)I
HLandroidx/compose/runtime/ComposerImpl;->validateNodeExpected()V
PLandroidx/compose/runtime/CompositionImpl;->applyChanges()V
HLandroidx/compose/runtime/ComposerKt;->findLocation(Ljava/util/List;I)I
Landroidx/compose/runtime/ComposerImpl;

Sintaxis de reglas

Estas reglas adoptan una de dos formas para segmentar métodos o clases.

[FLAGS][CLASS_DESCRIPTOR]->[METHOD_SIGNATURE]

Una regla de clase usa el siguiente patrón:

[CLASS_DESCRIPTOR]
Sintaxis Descripción
FLAGS Representa uno o más de los caracteres H, S y P para indicar si este método debe marcarse como Hot, Startup o Post Startup en relación con el tipo de inicio.

Un método con la marca H indica que es un método "caliente", lo que significa que es llamado muchas veces durante la vida útil de la app.

Un método con la marca S indica que es un método llamado en el inicio.

Un método con la marca P indica que es un método activo que no se relaciona con el inicio.

Una clase presente en este archivo indica que se usa durante el inicio y debe asignarse previamente en el montón para evitar el costo de cargarla. El compilador de ART utiliza varias estrategias de optimización, como la compilación AOT de estos métodos y la optimización de diseños en el archivo AOT generado.
CLASS_DESCRIPTOR Es el descriptor de la clase del método de destino. Por ejemplo, androidx.compose.runtime.SlotTable tendría un descriptor de Landroidx/compose/runtime/SlotTable;. Nota: L está precedido por el formato ejecutable Dalvik (DEX).
METHOD_SIGNATURE Firma del método, incluidos el nombre, los tipos de parámetros y los tipos de datos que se muestran del método. Por ejemplo, el método

// LayoutNode.kt

fun isPlaced():Boolean {
// ...
}

en LayoutNode tiene la firma isPlaced()Z.

Estos patrones pueden incluir comodines para tener una sola regla que abarque varios métodos o clases. Para obtener asistencia guiada cuando escribas con la sintaxis de reglas en Android Studio, consulta el complemento Perfiles de Baseline para Android.

Un ejemplo de una regla de comodín podría verse de la siguiente manera:

HSPLandroidx/compose/ui/layout/**->**(**)**

Tipos admitidos en reglas de perfil de Baseline

Las reglas del perfil de Baseline admiten los siguientes tipos. Para obtener información detallada sobre estos tipos, consulta el formato ejecutable Dalvik (DEX).

Carácter Tipo Descripción
B byte Byte firmados
C char Punto de código de caracteres Unicode codificado en UTF-16
D double Valor de punto flotante de doble precisión
F float Valor de punto flotante de precisión simple
I int Entero
J long Entero largo
S short Entero corto firmado
V void Nulo
Z boolean Verdadero o falso
L (nombre de clase) reference Una instancia de un nombre de clase

Además, las bibliotecas pueden definir reglas que se empaquetarán en artefactos de AAR. Cuando compilas un APK para incluir estos artefactos, las reglas se combinan (de manera similar a como se realiza la combinación de manifiestos) y se compilan en un perfil de ART binario compacto que es específico del APK.

ART aprovecha este perfil cuando se usa el APK en los dispositivos para compilar mediante AOT un subconjunto específico de la aplicación al momento de la instalación en Android 9 (nivel de API 28) o Android 7 (nivel de API 24) cuando se usa Instalador de perfiles.

Notas adicionales

A la hora de crear perfiles de Baseline, debes tener en cuenta otros aspectos:

  • En las versiones de Android 5 a Android 6 (niveles de API 21 y 23), la AOT ya está compilada durante la instalación.

  • Las aplicaciones depurables nunca se compilan mediante AOT para ayudar a solucionar problemas.

  • Los archivos de reglas deben llamarse baseline-prof.txt y ubicarse en el directorio raíz del conjunto de orígenes principal (debe ser un archivo del mismo nivel que el archivo AndroidManifest.xml).

  • Estos archivos solo se utilizarán si usas el complemento de Android para Gradle 7.1.0-alpha05 o una versión posterior (Android Studio Bumblebee Canary 5).

  • Por el momento, Bazel no admite la lectura ni la combinación de perfiles de Baseline en un APK.

  • Los perfiles de referencia no pueden pesar más de 1.5 MB comprimidos. Por lo tanto, las bibliotecas y las aplicaciones deben esforzarse por definir un pequeño conjunto de reglas de perfil que maximicen el impacto.

  • Las reglas amplias que compilan demasiadas aplicaciones pueden ralentizar el inicio debido al aumento del acceso al disco. Deberías probar el rendimiento de tus perfiles de Baseline.

Cómo medir las mejoras

Cómo automatizar la medición con la biblioteca de Macrobenchmark

Macrobenchmarks te permite controlar la compilación de medición previa a través de la API de CompilationMode, incluido el uso de BaselineProfile.

Si ya configuraste una prueba BaselineProfileRule en un módulo de Macrobenchmark, puedes definir una prueba nueva en ese módulo para evaluar su rendimiento:

@RunWith(AndroidJUnit4::class)
class BaselineProfileBenchmark {
  @get:Rule
  val benchmarkRule = MacrobenchmarkRule()

  @Test
  fun startupNoCompilation() {
    startup(CompilationMode.None())
  }

  @Test
  fun startupBaselineProfile() {
    startup(CompilationMode.Partial(
      baselineProfileMode = BaselineProfileMode.Require
    ))
  }

  private fun startup(compilationMode: CompilationMode) {
    benchmarkRule.measureRepeated(
      packageName = "com.example.app",
      metrics = listOf(StartupTimingMetric()),
      iterations = 10,
      startupMode = StartupMode.COLD,
      compilationMode = compilationMode
    ) { // this = MacrobenchmarkScope
        pressHome()
        startActivityAndWait()
    }
  }
}

A continuación, se muestra un ejemplo de resultados aprobados de una prueba:

Estos son los resultados de una pequeña prueba. Las apps más grandes se beneficiarán más de los perfiles de Baseline.

Ten en cuenta que, si bien el ejemplo anterior analiza StartupTimingMetric, hay otras métricas importantes que vale la pena tener en cuenta, como Bloqueos (métricas de fotogramas), que se pueden medir con Jetpack Macrobenchmark.

Cómo medir manualmente las mejoras de la app

Primero, midamos el inicio de la app no optimizado como referencia.

PACKAGE_NAME=com.example.app

# Force Stop App
adb shell am force-stop $PACKAGE_NAME
# Reset compiled state
adb shell cmd package compile --reset $PACKAGE_NAME

# Measure App startup
# This corresponds to `Time to initial display` metric
# For additional info https://developer.android.com/topic/performance/vitals/launch-time#time-initial
adb shell am start-activity -W -n $PACKAGE_NAME/.ExampleActivity \
 | grep "TotalTime"

A continuación, transferiremos el perfil de Baseline.

# Unzip the Release APK first
unzip release.apk

# Create a ZIP archive
# Note: The name should match the name of the APK
# Note: Copy baseline.prof{m} and rename it to primary.prof{m}
cp assets/dexopt/baseline.prof primary.prof
cp assets/dexopt/baseline.profm primary.profm

# Create an archive
zip -r release.dm primary.prof primary.profm

# Confirm that release.dm only contains the two profile files:
unzip -l release.dm
# Archive:  release.dm
#   Length      Date    Time    Name
# ---------  ---------- -----   ----
#      3885  1980-12-31 17:01   primary.prof
#      1024  1980-12-31 17:01   primary.profm
# ---------                     -------
#                               2 files

# Install APK + Profile together
adb install-multiple release.apk release.dm

Para verificar si el paquete se optimizó durante la instalación, ejecuta el siguiente comando:

# Check dexopt state
adb shell dumpsys package dexopt | grep -A 1 $PACKAGE_NAME

El resultado debe indicar que se compiló el paquete.

[com.example.app]
  path: /data/app/~~YvNxUxuP2e5xA6EGtM5i9A==/com.example.app-zQ0tkJN8tDrEZXTlrDUSBg==/base.apk
  arm64: [status=speed-profile] [reason=install-dm]

Ahora podemos medir el rendimiento de inicio de la app como lo hicimos antes, pero sin restablecer el estado compilado.

# Force Stop App
adb shell am force-stop $PACKAGE_NAME

# Measure App startup
adb shell am start-activity -W -n $PACKAGE_NAME/.ExampleActivity \
 | grep "TotalTime"