Introducción a la biblioteca de Jetpack Benchmark

La biblioteca de Jetpack Benchmark te permite obtener rápidamente comparativas de tu código basado en Kotlin o en Java dentro de Android Studio. La biblioteca se ocupa de la preparación, mide el rendimiento del código y los recuentos de asignaciones, y genera resultados de comparativas en la consola de Android Studio y en un archivo JSON con más detalles.

Entre los casos de uso se incluye desplazar un RecyclerView, realizar búsquedas en bases de datos y medir las partes lentas de tu código que desees agilizar.

Puedes usar la biblioteca en un entorno de integración continua (CI), como se describe en Cómo ejecutar comparativas en la integración continua.

Si todavía no implementaste AndroidX en un proyecto del que quieres generar comparativas, consulta Cómo migrar un proyecto existente con Android Studio.

Guía de inicio rápido

En esta sección, se proporciona un conjunto de pasos rápidos para generar comparativas sin necesidad de mover código a los módulos. Debido a que los pasos implican inhabilitar la depuración para obtener resultados de rendimiento precisos, no se confirmarán los cambios en el sistema de control del código fuente, aunque podría ser útil cuando quieras ejecutar mediciones únicas.

Para generar rápidamente comparativas únicas, completa los siguientes pasos:

  1. Agrega la biblioteca al archivo build.gradle del módulo:

    project_root/module_dir/build.gradle

    Groovy

    buildscript {
    ...
    dependencies {
        ...
        classpath "androidx.benchmark:benchmark-gradle-plugin:1.0.0"
    }
    }
    

    Kotlin

    dependencies {
        androidTestImplementation("androidx.benchmark:benchmark-junit4:1.0.0")
    }
    

    Groovy

    dependencies {
        androidTestImplementation "androidx.benchmark:benchmark-junit4:1.0.0"
    }
    
  2. Si deseas inhabilitar la depuración en el manifiesto de prueba, actualiza el elemento <application> a fin de forzar la inhabilitación de la depuración de manera temporal, como se muestra a continuación:

    project_root/module_dir/src/androidTest/AndroidManifest.xml

    <!-- Important: disable debuggable for accurate performance results -->
    <application
        android:debuggable="false"
        tools:ignore="HardcodedDebugMode"
        tools:replace="android:debuggable"/>
    
  3. Para agregar tu comparativa, agrega una instancia de la clase BenchmarkRule en un archivo de prueba del directorio androidTest. Consulta Cómo escribir comparativas para obtener más información.

    En el siguiente fragmento de código se muestra cómo agregar comparativas a una prueba de JUnit:

    Kotlin

    @RunWith(AndroidJUnit4::class)
    class MyBenchmark {
        @get:Rule
        val benchmarkRule = BenchmarkRule()
    
        @Test
        fun benchmarkSomeWork() = benchmarkRule.measureRepeated {
            doSomeWork()
        }
    }
    

    Java

    @RunWith(AndroidJUnit4.class)
    class MyBenchmark {
        @Rule
        public BenchmarkRule benchmarkRule = new BenchmarkRule();
    
        @Test
        public void benchmarkSomeWork() {
            final BenchmarkState state = benchmarkRule.getState();
            while (state.keepRunning()) {
                doSomeWork();
            }
        }
    }
    

Cuándo generar comparativas

Antes de escribir comparativas, se recomienda generar el perfil de tu código. De esta forma, podrás encontrar operaciones costosas que sería conveniente optimizar. También puede revelar el motivo de la lentitud, ya que muestra lo que sucede cuando se ejecutan las operaciones. Algunos ejemplos pueden ser ejecutarse en un subproceso de baja prioridad, suspenderse por requerir acceso al disco o llamar de manera inesperada a una función costosa, como la decodificación de un mapa de bits.

Si agregas puntos de seguimiento personalizados mediante la API de TraceCompat (o el wrapper -ktx) como se muestra a continuación, podrás verlos en Systrace o en el Generador de perfiles de CPU de Android Studio:

Kotlin

fun proccessBitmap(bitmap: Bitmap): Bitmap {
    trace("processBitmap") {
        // perform work on bitmap...
    }
}

Java

public Bitmap processBitmap(Bitmap bitmaptest) {
    TraceCompat.beginSection("processBitmap");
    try {
        // perform work on bitmap...
    } finally {
        TraceCompat.endSection();
    }
}

Qué comparar

Las comparativas son especialmente útiles para el trabajo de la CPU que se ejecuta muchas veces en la app. Algunos ejemplos adecuados son el desplazamiento de RecyclerView, las conversiones o el procesamiento de datos y los fragmentos de código que se usan de manera repetida.

Es difícil utilizar las comparativas para medir otros tipos de código. Debido a que las comparativas se ejecutan en un bucle, el código que no se ejecute con frecuencia o que tenga un rendimiento diferente cuando se lo llame varias veces, podría no ser apropiado para generar comparativas.

Almacenamiento en caché

Evita medir solo la caché. Por ejemplo, las comparativas de diseño de una vista personalizada podrían medir solo el rendimiento de la caché de diseño. Para evitar que esto suceda, puedes pasar diferentes parámetros de diseño en cada bucle. En otros casos, como cuando se mide el rendimiento del sistema de archivos, podría resultar difícil, ya que el SO almacena en caché el sistema de archivos mientras está en un bucle.

Código ejecutado con poca frecuencia

Es poco probable que Android Runtime (ART) compile mediante JIT el código que se ejecuta una vez durante el inicio de la app. Por lo tanto, generar comparativas de este código mientras se ejecuta en un bucle no es una manera realista de medir su rendimiento.

Para este tipo de código, en cambio, recomendamos llevar un registro o generar perfiles del código en tu app. Ten en cuenta que esto no significa que no puedas comparar el código en tu ruta de acceso de inicio, sino que deberías elegir código que se ejecute en un bucle y que pueda compilarse mediante JIT.

Configuración completa del proyecto

Para configurar la generación de comparativas regulares, en lugar de las comparativas únicas, debes aislarlas en su propio módulo. De esta manera, podrás asegurarte de que su configuración y los ajustes de debuggable en false sean independientes de las pruebas regulares.

Antes de agregar el módulo de comparativas, coloca el código y los recursos que deseas comparar en un módulo de biblioteca si aún no están en uno.

Nuestras muestras brindan ejemplos de cómo configurar un proyecto de esta manera.

Cómo definir propiedades de Android Studio

Si usas Android Studio 3.5, debes configurar manualmente las propiedades de Android Studio a fin de habilitar la compatibilidad con el asistente del módulo de comparativas. Esto no es necesario para Android Studio 3.6 o versiones posteriores.

Si deseas habilitar la plantilla de Android Studio para comparativas, haz lo siguiente:

  1. En Android Studio 3.5, haz clic en Help > Edit Custom Properties.

  2. Agrega la siguiente línea en el archivo que se abre:

    npw.benchmark.template.module=true

  3. Guarda el archivo y ciérralo.

  4. Reinicia Android Studio.

Cómo crear un módulo nuevo

La plantilla del módulo de comparativas configura automáticamente los ajustes para la generación de comparativas.

Si deseas usar la plantilla del módulo a fin de crear un módulo nuevo, haz lo siguiente:

  1. Haz clic con el botón derecho en tu proyecto o módulo y selecciona New > Module.

  2. Selecciona Benchmark Module y haz clic en Next.

    Figura 1: Módulo de comparativas

  3. Escribe un nombre para el módulo, selecciona el idioma y haz clic en Finish.

    Se crea un módulo configurado previamente para la generación de comparativas, con un directorio de comparativas y el valor debuggable configurado como false.

Cómo escribir comparativas

Las comparativas son pruebas de instrumentación estándar. Para crear comparativas, usa la clase BenchmarkRule que proporciona la biblioteca. Si deseas comparar actividades, usa ActivityTestRule o ActivityScenarioRule. Usa @UiThreadTest para comparar código de IU.

En el siguiente código se muestran comparativas de ejemplo:

Kotlin

@RunWith(AndroidJUnit4::class)
class ViewBenchmark {
    @get:Rule
    val benchmarkRule = BenchmarkRule()

    @Test
    fun simpleViewInflate() {
        val context = ApplicationProvider.getApplicationContext<Context>()
        val inflater = LayoutInflater.from(context)
        val root = FrameLayout(context)

        benchmarkRule.keepRunning {
            inflater.inflate(R.layout.test_simple_view, root, false)
        }
    }
}

Java

@RunWith(AndroidJUnit4::class)
public class ViewBenchmark {
    @Rule
    public BenchmarkRule benchmarkRule = new BenchmarkRule();

    @Test
    public void simpleViewInflate() {
        Context context = ApplicationProvider.getApplicationContext<Context>();
        final BenchmarkState state = benchmarkRule.getState();
        LayoutInflater inflater = LayoutInflater.from(context);
        FrameLayout root = new FrameLayout(context);

        while (state.keepRunning()) {
            inflater.inflate(R.layout.test_simple_view, root, false);
        }
    }
}

Puedes inhabilitar la sincronización para secciones de código que no quieras medir, como se muestra en el siguiente código de ejemplo:

Kotlin

@Test
fun bitmapProcessing() = benchmarkRule.measureRepeated {
    val input: Bitmap = runWithTimingDisabled { constructTestBitmap() }
    processBitmap(input)
}

Java

@Test
public void bitmapProcessing() {
    final BenchmarkState state = benchmarkRule.getState();
    while (state.keepRunning()) {
        state.pauseTiming();
        Bitmap input = constructTestBitmap();
        state.resumeTiming();

        processBitmap(input);
    }
}

Consulta Cómo ejecutar las comparativas para obtener más información.

Cómo ejecutar las comparativas

En Android Studio, ejecuta tus comparativas como lo harías con cualquier @Test. En Android Studio 3.4 y versiones posteriores, puedes ver el resultado que se envió a la consola.

Para ejecutar las comparativas, en el módulo, navega a benchmark/src/androidTest y presiona Control + Shift + F10 (Command + Shift + R en Mac). Los resultados de las comparativas aparecerán en la consola, como se muestra en la figura 2:

Resultado de comparativas en Android Studio

Figura 2: Resultado de comparativas en Android Studio

Desde la línea de comandos, ejecuta el comando connectedCheck regular:

./gradlew benchmark:connectedCheck

Cómo recopilar los datos

Hay un informe de comparativas completo con métricas adicionales e información sobre el dispositivo disponible en formato JSON.

El complemento de Gradle androidx.benchmark habilita el resultado de JSON de manera predeterminada. Si deseas habilitar manualmente los resultados de JSON en entornos de compilación que no sean de Gradle, debes pasar un argumento de instrumentación, androidx.benchmark.output.enable, configurado en true.

El siguiente es un ejemplo en el que se usa el comando adb shell am instrument:

adb shell am instrument -w -e "androidx.benchmark.output.enable" "true" com.android.foo/androidx.benchmark.junit4.AndroidBenchmarkRunner

De manera predeterminada, el informe de JSON está escrito en el dispositivo en la carpeta de descargas compartida externa del APK de prueba que, por lo general, se encuentra en:

/storage/emulated/0/Download/app_id-benchmarkData.json

Puedes configurar la ubicación en la que se guardan los informes de comparativas en el dispositivo. Para ello, usa el argumento de instrumentación additionalTestOutputDir.

adb shell am instrument -w -e "androidx.benchmark.output.enable" "true" -e "additionalTestOutputDir" "/path_to_a_directory_on_device_test_has_write_permissions_to/" com.android.foo/androidx.benchmark.junit4.AndroidBenchmarkRunner

Los informes de JSON se copian del dispositivo al host. Están escritos en la máquina anfitrión en la siguiente string:

project_root/module/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/device_id/app_id-benchmarkData.json

Android Gradle Plugin 3.6 y versiones posteriores

Cuando ejecutas comparativas desde la línea de comandos con Gradle, en los proyectos que usan el complemento de Gradle para Android 3.6 y versiones posteriores, puedes agregar la siguiente marca al archivo gradle.properties del proyecto:

android.enableAdditionalTestOutput=true

De esta manera, se habilita la función experimental del complemento de Gradle para Android a fin de extraer informes de comparativas de dispositivos con nivel de API 16 y posterior:

Android Gradle Plugin 3.5 y versiones anteriores

En el caso de los usuarios de versiones anteriores del complemento de Gradle para Android, el complemento androidx.benchmark de Gradle se ocupa de la copia de informes de comparativas de JSON del dispositivo al host.

Cuando uses el complemento de Android para Gradle 3.5 o una versión anterior, y especifiques el nivel de API 29, deberás agregar una marca al manifiesto de Android en el directorio androidTest de tus comparativas a fin de habilitar el comportamiento de almacenamiento externo heredado.

<manifest ... >
  <!-- This attribute is "false" by default on apps targeting Android 10. -->
  <application android:requestLegacyExternalStorage="true" ... >
    ...
  </application>
</manifest>

Consulta Cómo inhabilitar temporalmente el almacenamiento específico para obtener más información.

Estabilidad del reloj

En los dispositivos móviles, los relojes cambian de manera dinámica del estado alto (para el rendimiento) al estado bajo (a fin de ahorrar energía cuando el dispositivo se calienta). Estos relojes variables pueden hacer que los números de tus comparativas varíen en gran medida, por lo que la biblioteca proporciona maneras de abordar este problema.

Bloquear relojes (requiere permisos de administrador)

La mejor manera de estabilizar el rendimiento consiste en bloquear los relojes. De esta manera, se garantiza que los relojes nunca estén lo suficientemente altos para calentar el dispositivo, o bajos si las comparativas no están usando la CPU por completo. Aunque esta es la mejor manera de garantizar un rendimiento estable, la mayoría de los dispositivos no la admiten, ya que se necesitan permisos de administrador de adb.

Si quieres bloquear tus relojes, agrega el complemento auxiliar proporcionado en la ruta de clase del proyecto de nivel superior en el archivo build.gradle principal:

buildscript {
    ...
    dependencies {
        ...
        classpath("androidx.benchmark:benchmark-gradle-plugin:1.0.0")
    }
}

Aplica el complemento en el build.gradle de tu módulo de comparativas:

Groovy

apply plugin: com.android.app
apply plugin: androidx.benchmark
...

Kotlin

plugins {
    id("com.android.app")
    id("androidx.benchmark")
    ...
}

De esta manera, se agregan tareas de comparativas de Gradle a tu proyecto, como ./gradlew lockClocks y ./gradlew unlockClocks. Usa estas tareas para bloquear y desbloquear la CPU de un dispositivo mediante adb.

Si tienes varios dispositivos visibles para adb, usa la variable de entorno ANDROID_SERIAL a fin de especificar en qué dispositivo debería operar la tarea de Gradle:

ANDROID_SERIAL=device-id-from-adb-devices ./gradlew lockClocks

Modo de rendimiento sostenido

Window.setSustainedPerformanceMode() es una función compatible con algunos dispositivos que permite que una app opte por una frecuencia de CPU máxima más baja. Cuando se ejecuta en dispositivos compatibles, la biblioteca Benchmark usa una combinación de esta API y el lanzamiento de su propia actividad para evitar los modelos térmicos y estabilizar los resultados.

Esta funcionalidad está habilitada de forma predeterminada por el conjunto testInstrumentationRunner del complemento de Gradle. Si deseas usar un ejecutor personalizado, puedes crear una subclase de AndroidBenchmarkRunner y usarla como testInstrumentationRunner.

El ejecutor lanza una actividad opaca en pantalla completa para garantizar que las comparativas se ejecuten en primer plano y no se superponga ninguna otra app.

Pausado de la ejecución automática

Si no se usan ni el bloqueo de relojes ni el rendimiento sostenido, la biblioteca realiza una detección automática de modelos térmicos. Cuando están habilitadas, las comparativas internas se ejecutan periódicamente a fin de determinar cuándo la temperatura del dispositivo subió tanto como para disminuir el rendimiento de la CPU. Cuando se detecta un rendimiento reducido de la CPU, la biblioteca pausa la ejecución para permitir que el dispositivo se enfríe y vuelve a intentar ejecutar las comparativas actuales.

Errores de configuración

La biblioteca detecta las siguientes condiciones a fin de garantizar que tu proyecto y entorno estén configurados para un rendimiento acorde a un lanzamiento:

  • Debuggable se configura en false.
  • Se está utilizando un dispositivo físico, no un emulador.
  • Los relojes están bloqueados si el dispositivo tiene permisos de administrador.
  • El nivel de batería en el dispositivo es suficiente.

Si alguna de las comprobaciones anteriores falla, las comparativas mostrarán un error a fin de evitar mediciones imprecisas.

Para hacer que estos problemas pasen como advertencias y evitar que muestren un error y detengan las comparativas, pasa el tipo de errores que quieras suprimir en una lista separada por comas al argumento de instrumentación androidx.benchmark.suppressErrors, como se muestra a continuación:

adb shell am instrument -w -e "androidx.benchmark.suppressErrors" "DEBUGGABLE" com.android.foo/androidx.benchmark.junit4.AndroidBenchmarkRunner

Se puede configurar en Gradle de la siguiente manera:

Groovy

android {
    defaultConfig {
        testInstrumentationRunnerArgument 'androidx.benchmark.suppressErrors', 'DEBUGGABLE'
    }
}

Kotlin

android {
    defaultConfig {
        testInstrumentationRunnerArguments(mapOf(
            "androidx.benchmark.suppressErrors" to "DEBUGGABLE"
        ))
    }
}

Ten en cuenta que suprimir errores permite que las comparativas se ejecuten en un estado de configuración incorrecta, pero el resultado de las comparativas se dañará intencionalmente anteponiendo nombres de pruebas con el error. Es decir que ejecutar comparativas que se pueden depurar con la supresión antes mencionada antepondrá DEBUGGABLE_ a los nombres de pruebas.

Cómo generar perfiles

Puedes generar perfiles de comparativas para analizar el motivo por el que el código medido se ejecuta lentamente.

Agrega lo siguiente al archivo build.gradle de tu módulo de comparativas:

Groovy

android {
    defaultConfig {
        // must be one of: none, sampled, or method
        testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'sampled'
    }
}

Kotlin

android {
    defaultConfig {
        // must be one of: none, sampled, or method
        testInstrumentationRunnerArgument = mapOf(
            "androidx.benchmark.profiling.mode", "sampled"
        )
    }
}

Cuando ejecutes una comparativa, se copiará un archivo .trace de salida al host en el directorio junto con los resultados JSON. Ábrelo con Android Studio haciendo clic en File > Open a fin de inspeccionar los resultados de la generación de perfiles en el Generador de perfiles de CPU.

Registro de métodos

Con el registro de métodos, la comparativa se preparará antes de capturar un seguimiento de métodos, de modo que se registrará todos los métodos que llame tu comparativa. Ten en cuenta que los resultados de rendimiento se verán afectados de manera significativa por la sobrecarga de capturar la entrada/salida de cada método.

Generación de perfiles de métodos

Con el muestreo de métodos, la comparativa tomará muestras de seguimientos de pila en un intervalo de 100 microsegundos una vez finalizada la preparación. La comparativa se ejecutará en bucle más tiempo de lo habitual a fin de garantizar que se capturen suficientes muestras que permitan obtener resultados significativos. Esto afectará un poco los resultados de la medición.

A fin de obtener más información sobre el seguimiento y el muestreo de métodos, consulta la configuración para la generación de perfiles de CPU.

Muestras de comparativas

El código de comparativas de muestra está disponible en los siguientes proyectos:

Algunos de los proyectos de muestra incluyen lo siguiente:

  • BenchmarkSample: Muestra independiente que explica cómo usar un módulo de comparativas para medir el código y la IU

  • PagingWithNetworkSample : Muestra de componentes de la arquitectura de Android que explica cómo generar comparativas del rendimiento de RecyclerView

  • WorkManagerSample: Muestra de componentes de la arquitectura de Android que explica cómo generar comparativas de los trabajadores de WorkManager

Recursos adicionales

Para obtener más información sobre las comparativas, consulta los siguientes recursos adicionales.

Blogs

Cómo enviar comentarios

Si deseas informar errores o enviar solicitudes de funciones cuando usas las comparativas, consulta la herramienta de seguimiento de problemas de acceso público.