Cómo depurar perfiles de Baseline

En este documento, se muestran las prácticas recomendadas para ayudar a diagnosticar problemas y garantizar que tus perfiles de Baseline funcionen correctamente para proporcionar el mayor beneficio.

Problemas de compilación

Si copiaste el ejemplo de perfiles de Baseline en la app de ejemplo de Now in Android, es posible que experimentes pruebas fallidas durante la tarea de perfiles de Baseline en las que se indique que las pruebas no se pueden ejecutar en un emulador:

./gradlew assembleDemoRelease
Starting a Gradle Daemon (subsequent builds will be faster)
Calculating task graph as no configuration cache is available for tasks: assembleDemoRelease
Type-safe project accessors is an incubating feature.

> Task :benchmarks:pixel6Api33DemoNonMinifiedReleaseAndroidTest
Starting 14 tests on pixel6Api33

com.google.samples.apps.nowinandroid.foryou.ScrollForYouFeedBenchmark > scrollFeedCompilationNone[pixel6Api33] FAILED
        java.lang.AssertionError: ERRORS (not suppressed): EMULATOR
        WARNINGS (suppressed):
        ...

Las fallas se producen porque Now in Android usa un dispositivo administrado por Gradle para la generación de perfiles de Baseline. Se esperan fallas, porque, por lo general, no debes ejecutar comparativas de rendimiento en un emulador. Sin embargo, como no recopilas métricas de rendimiento cuando generas perfiles de Baseline, puedes ejecutar la recopilación de perfiles de Baseline en emuladores para tu comodidad. Para usar perfiles de Baseline con un emulador, realiza la compilación y la instalación desde la línea de comandos, y establece un argumento para habilitar las reglas de los perfiles de Baseline:

installDemoRelease -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile

Como alternativa, puedes crear una configuración de ejecución personalizada en Android Studio para habilitar los perfiles de Baseline en emuladores seleccionando Run > Edit Configurations:

Se agregó una configuración de ejecución personalizada para crear perfiles de Baseline en Now in Android.
Figura 1. Se agregó una configuración de ejecución personalizada para crear perfiles de Baseline en Now in Android.

Problemas de instalación

Verifica que el APK o el AAB que compilas sea de una variante de compilación que incluya perfiles de Baseline. La manera más fácil de verificar esto es abrir el APK en Android Studio seleccionando Build > Analyze APK abriendo el APK y buscando el perfil en el archivo /assets/dexopt/baseline.prof:

Cómo buscar un perfil de Baseline con el visor de APK en Android Studio
Figura 2: Busca un perfil de Baseline con el visor de APK en Android Studio.

Los perfiles de Baseline deben compilarse en el dispositivo que ejecuta la app. En el caso de las instalaciones en la tienda de aplicaciones y las apps instaladas con PackageInstaller, la compilación en el dispositivo se realiza como parte del proceso de instalación de la app. Sin embargo, cuando la app se transfiere desde Android Studio o con herramientas de línea de comandos, la biblioteca de Jetpack ProfileInstaller es responsable de poner en cola los perfiles para su compilación durante el siguiente proceso de optimización de DEX en segundo plano. En esos casos, si deseas asegurarte de que se usen tus perfiles de Baseline, es posible que debas forzar la compilación de perfiles de Baseline. ProfileVerifier te permite consultar el estado de instalación y compilación del perfil, como se muestra en el siguiente ejemplo:

Kotlin

private const val TAG = "MainActivity"

class MainActivity : ComponentActivity() {
  ...
  override fun onResume() {
    super.onResume()
    lifecycleScope.launch {
      logCompilationStatus()
    }
  }

  private suspend fun logCompilationStatus() {
     withContext(Dispatchers.IO) {
        val status = ProfileVerifier.getCompilationStatusAsync().await()
        when (status.profileInstallResultCode) {
            RESULT_CODE_NO_PROFILE ->
                Log.d(TAG, "ProfileInstaller: Baseline Profile not found")
            RESULT_CODE_COMPILED_WITH_PROFILE ->
                Log.d(TAG, "ProfileInstaller: Compiled with profile")
            RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION ->
                Log.d(TAG, "ProfileInstaller: Enqueued for compilation")
            RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING ->
                Log.d(TAG, "ProfileInstaller: App was installed through Play store")
            RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST ->
                Log.d(TAG, "ProfileInstaller: PackageName not found")
            RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ ->
                Log.d(TAG, "ProfileInstaller: Cache file exists but cannot be read")
            RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE ->
                Log.d(TAG, "ProfileInstaller: Can't write cache file")
            RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION ->
                Log.d(TAG, "ProfileInstaller: Enqueued for compilation")
            else ->
                Log.d(TAG, "ProfileInstaller: Profile not compiled or enqueued")
        }
    }
}

Java


public class MainActivity extends ComponentActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onResume() {
        super.onResume();

        logCompilationStatus();
    }

    private void logCompilationStatus() {
         ListeningExecutorService service = MoreExecutors.listeningDecorator(
                Executors.newSingleThreadExecutor());
        ListenableFuture<ProfileVerifier.CompilationStatus> future =
                ProfileVerifier.getCompilationStatusAsync();
        Futures.addCallback(future, new FutureCallback<>() {
            @Override
            public void onSuccess(CompilationStatus result) {
                int resultCode = result.getProfileInstallResultCode();
                if (resultCode == RESULT_CODE_NO_PROFILE) {
                    Log.d(TAG, "ProfileInstaller: Baseline Profile not found");
                } else if (resultCode == RESULT_CODE_COMPILED_WITH_PROFILE) {
                    Log.d(TAG, "ProfileInstaller: Compiled with profile");
                } else if (resultCode == RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION) {
                    Log.d(TAG, "ProfileInstaller: Enqueued for compilation");
                } else if (resultCode == RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING) {
                    Log.d(TAG, "ProfileInstaller: App was installed through Play store");
                } else if (resultCode == RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST) {
                    Log.d(TAG, "ProfileInstaller: PackageName not found");
                } else if (resultCode == RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ) {
                    Log.d(TAG, "ProfileInstaller: Cache file exists but cannot be read");
                } else if (resultCode
                        == RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE) {
                    Log.d(TAG, "ProfileInstaller: Can't write cache file");
                } else if (resultCode == RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION) {
                    Log.d(TAG, "ProfileInstaller: Enqueued for compilation");
                } else {
                    Log.d(TAG, "ProfileInstaller: Profile not compiled or enqueued");
                }
            }

            @Override
            public void onFailure(Throwable t) {
                Log.d(TAG,
                        "ProfileInstaller: Error getting installation status: " + t.getMessage());
            }
        }, service);
    }
}

Los siguientes códigos de resultado proporcionan sugerencias sobre la causa de algunos problemas:

RESULT_CODE_COMPILED_WITH_PROFILE
Se instala, compila y usa el perfil cada vez que se ejecuta la app. Este es el resultado que deseas ver.
RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED
No se encontró ningún perfil en el APK o el AAB que se está ejecutando. Asegúrate de usar una variante de compilación que incluya perfiles de Baseline si ves este error y de que el APK contenga un perfil.
RESULT_CODE_NO_PROFILE
No se instaló ningún perfil para esta app cuando se instaló a través de la tienda de aplicaciones o el administrador de paquetes. El motivo principal de esto como código de error es que el instalador de perfiles no se ejecutó debido a que ProfileInstallerInitializer estaba inhabilitado. Ten en cuenta que, cuando se informa este error, aún se encuentra un perfil incorporado en el APK de la aplicación. Cuando no se encuentra un perfil incorporado, el código de error que se muestra es RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED.
RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION
Un perfil se encuentra en el APK o AAB, y se pone en cola para la compilación. Cuando ProfileInstaller instala un perfil, este se pone en cola para la compilación la próxima vez que el sistema ejecute la optimización DEX en segundo plano. El perfil no estará activo hasta que se complete la compilación. No intentes comparar tus perfiles de Baseline hasta que se complete la compilación. Es posible que debas forzar la compilación de los perfiles de Baseline. Este error no se producirá cuando se instale la app desde la tienda de aplicaciones o el administrador de paquetes en dispositivos que ejecuten Android 9 (nivel de API 28) y versiones posteriores, ya que la compilación se realiza durante la instalación.
RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING
Se instaló un perfil que no coincide y se compiló la app con él. Este es el resultado de la instalación a través de Google Play Store o el administrador de paquetes. Ten en cuenta que este resultado difiere de RESULT_CODE_COMPILED_WITH_PROFILE porque el perfil no coincidente solo compilará los métodos que todavía se compartan entre el perfil y la app. El perfil es efectivamente más pequeño de lo esperado y se compilarán menos métodos de los que se incluyeron en el perfil de Baseline.
RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE
ProfileVerifier no puede escribir el archivo de caché de los resultados de la verificación. Esto puede suceder porque hay algún problema con los permisos de la carpeta de la app o si no hay suficiente espacio libre en el disco en el dispositivo.
RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION
ProfileVerifieris running on an unsupported API version of Android. ProfileVerifier solo admite Android 9 (nivel de API 28) y versiones posteriores.
RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST
Se arroja una PackageManager.NameNotFoundException cuando se consulta el PackageManager del paquete de la app. Esto debería ocurrir rara vez. Intenta desinstalar la app y volver a instalar todo.
RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ
Existe un archivo de caché de resultados de verificación anterior, pero no se puede leer. Esto rara vez debería suceder. Intenta desinstalar la app y volver a instalar todo.

Usa ProfileVerifier en producción

En producción, puedes usar ProfileVerifier junto con bibliotecas de informes de estadísticas, como Google Analytics para Firebase, para generar eventos de estadísticas que indiquen el estado del perfil. Por ejemplo, esto te alerta rápidamente si se lanza una nueva versión de la app que no contiene perfiles de Baseline.

Fuerza la compilación de perfiles de Baseline

Si el estado de compilación de tus perfiles de Baseline es RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION, puedes forzar la compilación inmediata a través de adb:

adb shell cmd package compile -r bg-dexopt PACKAGE_NAME

Verifica el estado de compilación sin ProfileVerifier

Si no usas ProfileVerifier, puedes verificar el estado de compilación con adb, aunque no proporciona estadísticas tan detalladas como ProfileVerifier:

adb shell dumpsys package dexopt | grep -A 2 PACKAGE_NAME

El uso de adb produce algo similar a lo siguiente:

  [com.google.samples.apps.nowinandroid.demo]
    path: /data/app/~~dzJiGMKvp22vi2SsvfjkrQ==/com.google.samples.apps.nowinandroid.demo-7FR1sdJ8ZTy7eCLwAnn0Vg==/base.apk
      arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]
        [location is /data/app/~~dzJiGMKvp22vi2SsvfjkrQ==/com.google.samples.apps.nowinandroid.demo-7FR1sdJ8ZTy7eCLwAnn0Vg==/oat/arm64/base.odex]

El valor de estado indica el estado de compilación del perfil y es uno de los siguientes valores:

Estado de compilación Significado
speed‑profile Existe un perfil compilado y está en uso.
verify No existe ningún perfil compilado.

Un estado verify no significa que el APK o el AAB no contengan un perfil, ya que se puede poner en cola para la compilación de la siguiente tarea de optimización de DEX en segundo plano.

El valor del motivo indica qué activa la compilación del perfil y es uno de los siguientes valores:

Motivo Significado
install‑dm Cuando se instala la app, se compila un perfil de Baseline manualmente o a través de Google Play.
bg‑dexopt Se compiló un perfil mientras tu dispositivo estaba inactivo. Puede ser un perfil de Baseline o un perfil recopilado durante el uso de la app.
cmdline La compilación se activó con adb. Puede ser un perfil de Baseline o un perfil recopilado durante el uso de la app.

Problemas de rendimiento

En esta sección, se muestran algunas prácticas recomendadas para definir y comparar correctamente tus perfiles de Baseline para aprovecharlos al máximo.

Compara correctamente las métricas de inicio

Tus perfiles de Baseline serán más eficaces si tus métricas de inicio están bien definidas. Las dos métricas clave son el tiempo para la visualización inicial (TTID) y el tiempo para la visualización completa (TTFD).

El TTID ocurre cuando la app dibuja su primer fotograma. Es importante que sea lo más breve posible, ya que mostrar algo le indica al usuario que se está ejecutando la app. Incluso puedes mostrar un indicador de progreso indeterminado para indicar que la app es responsiva.

TTFD es el momento en que se puede interactuar con la app. Es importante que sea lo más breve posible para evitar que el usuario se frustre. Si indicas el TTFD correctamente, le haces saber al sistema que el código que se ejecuta en el camino al TTFD forma parte del inicio de la app. Como resultado, es más probable que el sistema coloque este código en el perfil.

Mantén los valores de TTID y TTFD tan bajos como sea posible para que tu app tenga una mayor capacidad de respuesta.

El sistema puede detectar el TTID, mostrarlo en Logcat y notificarlo como parte de las comparativas de inicio. Sin embargo, el sistema no puede determinar el TTFD y es responsabilidad de la app informar cuando alcanza un estado interactivo de obtenida por completo. Para ello, llama a reportFullyDrawn() o a ReportDrawn si usas Jetpack Compose. Si tienes varias tareas en segundo plano que debes completar antes de que la app se considere completamente dibujada, puedes usar FullyDrawnReporter, como se describe enMejora la precisión de los tiempos de inicio.

Perfiles de biblioteca y perfiles personalizados

Cuando se compara el impacto de los perfiles, puede ser difícil separar los beneficios de los perfiles de tu app de los perfiles que aportan las bibliotecas, como las de Jetpack. Cuando compilas tu APK, el complemento de Android para Gradle agrega cualquier perfil a las dependencias de la biblioteca, así como tu perfil personalizado. Esto es bueno para optimizar el rendimiento general y se recomienda para las compilaciones de lanzamiento. Sin embargo, dificulta la medición de la ganancia de rendimiento adicional que proviene de tu perfil personalizado.

Una forma rápida de ver de forma manual la optimización adicional que proporciona tu perfil personalizado es quitarla y ejecutar las comparativas. Luego, reemplázala y vuelve a ejecutar tus comparativas. Si comparas los dos, se mostrarán las optimizaciones proporcionadas por solo los perfiles de la biblioteca y los perfiles de la biblioteca junto con tu perfil personalizado.

Una forma automatizable de comparar perfiles es crear una nueva variante de compilación que contenga solo los perfiles de la biblioteca y no el perfil personalizado. Compara las comparativas de esta variante con la variante de lanzamiento que contiene los perfiles de biblioteca y tus perfiles personalizados. En el siguiente ejemplo, se muestra cómo configurar la variante que incluye solo los perfiles de biblioteca. Agrega una variante nueva llamada releaseWithoutCustomProfile al módulo del consumidor de perfiles, que suele ser el módulo de la app:

Kotlin

android {
  ...
  buildTypes {
    ...
    // Release build with only library profiles.
    create("releaseWithoutCustomProfile") {
      initWith(release)
    }
    ...
  }
  ...
}
...
dependencies {
  ...
  // Remove the baselineProfile dependency.
  // baselineProfile(project(":baselineprofile"))
}

baselineProfile {
  variants {
    create("release") {
      from(project(":baselineprofile"))
    }
  }
}

Groovy

android {
  ...
  buildTypes {
    ...
    // Release build with only library profiles.
    releaseWithoutCustomProfile {
      initWith(release)
    }
    ...
  }
  ...
}
...
dependencies {
  ...
  // Remove the baselineProfile dependency.
  // baselineProfile ':baselineprofile"'
}

baselineProfile {
  variants {
    release {
      from(project(":baselineprofile"))
    }
  }
}

En el ejemplo de código anterior, se quita la dependencia baselineProfile de todas las variantes y se aplica de manera selectiva solo a la variante release. Puede parecer contradictorio que los perfiles de biblioteca aún se agreguen cuando se quite la dependencia del módulo del productor de perfiles. Sin embargo, este módulo solo se encarga de generar tu perfil personalizado. El complemento de Android para Gradle aún se está ejecutando para todas las variantes y es responsable de incluir perfiles de biblioteca.

También debes agregar la variante nueva al módulo del generador de perfiles. En este ejemplo, el módulo del productor se llama :baselineprofile.

Kotlin

android {
  ...
    buildTypes {
      ...
      // Release build with only library profiles.
      create("releaseWithoutCustomProfile") {}
      ...
    }
  ...
}

Groovy

android {
  ...
    buildTypes {
      ...
      // Release build with only library profiles.
      releaseWithoutCustomProfile {}
      ...
    }
  ...
}

Para obtener comparativas solo con los perfiles de la biblioteca, ejecuta lo siguiente:

./gradlew :baselineprofile:connectedBenchmarkReleaseWithoutCustomProfileAndroidTest

Para obtener comparativas con los perfiles de la biblioteca y tu perfil personalizado, ejecuta lo siguiente:

./gradlew :baselineprofile:connectedBenchmarkReleaseAndroidTest

Ejecutar el código anterior en la app de ejemplo de Macrobenchmark muestra que existe una diferencia de rendimiento entre las dos variantes. Con los perfiles de biblioteca, las comparativas semicalientes de startupCompose muestran los siguientes resultados:

SmallListStartupBenchmark_startupCompose[mode=COLD]
timeToInitialDisplayMs   min  70.8,   median  79.1,   max 126.0
Traces: Iteration 0 1 2 3 4 5 6 7 8 9

Existen perfiles de biblioteca en muchas bibliotecas de Jetpack Compose, por lo que se realizan algunas optimizaciones con solo usar el complemento de Gradle para perfiles de Baseline. Sin embargo, hay optimizaciones adicionales cuando se usa el perfil personalizado:

SmallListStartupBenchmark_startupCompose[mode=COLD]
timeToInitialDisplayMs   min 57.9,   median 73.5,   max 92.3
Traces: Iteration 0 1 2 3 4 5 6 7 8 9

Evita el inicio de apps vinculadas a E/S

Si tu app realiza muchas llamadas de E/S o de red durante el inicio, esto puede afectar negativamente tanto el tiempo de inicio de la app como la precisión de las comparativas de inicio. Estas llamadas pesadas pueden tardar una cantidad de tiempo indeterminada que puede variar con el paso del tiempo y hasta entre iteraciones de la misma comparativa. Las llamadas de E/S suelen ser mejores que las de red, ya que estas últimas pueden verse afectadas por factores externos al dispositivo y del mismo dispositivo. Evita las llamadas de red durante el inicio. Cuando no se pueda evitar usar uno o el otro, utiliza E/S.

Te recomendamos que la arquitectura de tu app admita el inicio de la app sin llamadas de red o E/S, incluso si solo se usa cuando se realiza la comparativa de inicio. Esto ayuda a garantizar la menor variabilidad posible entre las diferentes iteraciones de tus comparativas.

Si tu app usa Hilt, puedes proporcionar implementaciones falsas vinculadas a la E/S cuando realizas comparativas en Microbenchmark y Hilt.

Abarca todos los recorridos importantes del usuario

Es importante abarcar con precisión todos los recorridos importantes del usuario en la generación de perfiles de Baseline. Los perfiles de Baseline no mejorarán los recorridos del usuario que no estén cubiertos. Los perfiles de referencia más efectivos incluyen todos los recorridos del usuario de inicio comunes, así como los recorridos del usuario en la app sensibles al rendimiento, como el desplazamiento por listas.