Cómo escribir una microcomparativa

Para aprender a usar la biblioteca de Microbenchmark con la incorporación de cambios a tu código de la aplicación, consulta la sección Guía de inicio rápido. Si quieres aprender a completar una configuración entera con cambios más complejos en la base de código, consulta la sección Configuración completa del proyecto.

Guía de inicio rápido

En esta sección, se muestra cómo probar las comparativas y ejecutar mediciones únicas sin necesidad de mover código a los módulos. Para obtener resultados de rendimiento precisos, estos pasos implican inhabilitar la depuración en tu app, por lo que debes conservar esta configuración en una copia de trabajo local sin confirmar los cambios en el sistema fuente de control.

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

  1. Agrega la biblioteca al archivo build.gradle o build.gradle.kts de tu módulo:

    Kotlin

    dependencies {
        implementation("androidx.benchmark:benchmark-junit4:1.2.4")
    }
    

    Groovy

    dependencies {
        implementation 'androidx.benchmark:benchmark-junit4:1.2.4'
    }
    

    Usa una dependencia implementation en lugar de una dependencia androidTestImplementation. Si usas androidTestImplementation, las comparativas no se ejecutan porque el manifiesto de la biblioteca no se combina con el manifiesto de la app.

  2. Actualiza el tipo de compilación debug para que no se pueda depurar:

    Kotlin

    android {
        ...
        buildTypes {
            debug {
                isDebuggable = false
            }
        }
    }
    

    Groovy

    android {
        ...
        buildTypes {
            debug {
                debuggable false
            }
        }
    }
    
  3. Cambia testInstrumentationRunner a AndroidBenchmarkRunner:

    Kotlin

    android {
        ...
        defaultConfig {
            testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner"
        }
    }
    

    Groovy

    android {
        ...
        defaultConfig {
            testInstrumentationRunner "androidx.benchmark.junit4.AndroidBenchmarkRunner"
        }
    }
    
  4. Agrega una instancia de BenchmarkRule a un archivo de prueba del directorio androidTest para agregar tus comparativas. Consulta Cómo crear una clase de Microbenchmark para descubrir cómo escribir comparativas.

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

    Kotlin

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

    Java

    @RunWith(AndroidJUnit4.class)
    class SampleBenchmark {
        @Rule
        public BenchmarkRule benchmarkRule = new BenchmarkRule();
    
        @Test
        public void benchmarkSomeWork() {
                BenchmarkRuleKt.measureRepeated(
                    (Function1<BenchmarkRule.Scope, Unit>) scope -> doSomeWork()
                );
           }
        }
    }
    

Si deseas aprender a escribir comparativas, avanza a Cómo crear una clase de Microbenchmark.

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. Esto ayuda a garantizar que su configuración, como establecer debuggable en false, sea independiente de las pruebas regulares.

Debido a que Microbenchmark ejecuta tu código directamente, coloca el código que deseas comparar en un módulo de Gradle separado y establece la dependencia en ese módulo, como se muestra en la figura 1.

estructura de app
Figura 1: Estructura de la app con módulos de Gradle :app, :microbenchmark y :benchmarkable, que permite que las microcomparativas comparen código en el módulo :benchmarkable

Para agregar un nuevo módulo de Gradle, puedes usar el asistente de módulos en Android Studio. El asistente crea un módulo configurado previamente para la generación de comparativas, con un directorio de comparativas y el valor debuggable configurado como false.

  1. Haz clic con el botón derecho en tu proyecto o módulo desde el panel Project en Android Studio y, luego, haz clic en New > Module.

  2. Selecciona Benchmark en el panel Templates.

  3. Selecciona Microbenchmark como tipo de módulo de comparativas.

  4. Como nombre de módulo, escribe "microbenchmark".

  5. Haz clic en Finish.

Cómo configurar un nuevo módulo de biblioteca
Figura 2: Incorporación de un nuevo módulo de Gradle en Android Studio Bumblebee

Después de crear el módulo, cambia su archivo build.gradle o build.gradle.kts, y agrega androidTestImplementation al módulo que contiene el código para crear comparativas:

Kotlin

dependencies {
    // The module name might be different.
    androidTestImplementation(project(":benchmarkable"))
}

Groovy

dependencies {
    // The module name might be different.
    androidTestImplementation project(':benchmarkable')
}

Cómo crear una clase de Microbenchmark

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 ActivityScenario 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 SampleBenchmark {
    @get:Rule
    val benchmarkRule = BenchmarkRule()

    @Test
    fun benchmarkSomeWork() {
        benchmarkRule.measureRepeated {
            doSomeWork()
        }
    }
}
    

Java

@RunWith(AndroidJUnit4.class)
class SampleBenchmark {
    @Rule
    public BenchmarkRule benchmarkRule = new BenchmarkRule();

    @Test
    public void benchmarkSomeWork() {
        final BenchmarkState state = benchmarkRule.getState();
        while (state.keepRunning()) {
            doSomeWork();
        }
    }
}
    

Cómo inhabilitar la sincronización

Puedes inhabilitar la sincronización de las secciones de código que no quieres medir con el bloque runWithTimingDisabled{}. Por lo general, estas secciones representan algún código que necesitas ejecutar en cada iteración de la comparativa.

Kotlin

// using random with the same seed, so that it generates the same data every run
private val random = Random(0)

// create the array once and just copy it in benchmarks
private val unsorted = IntArray(10_000) { random.nextInt() }

@Test
fun benchmark_quickSort() {
    // ...
    benchmarkRule.measureRepeated {
        // copy the array with timing disabled to measure only the algorithm itself
        listToSort = runWithTimingDisabled { unsorted.copyOf() }

        // sort the array in place and measure how long it takes
        SortingAlgorithms.quickSort(listToSort)
    }

    // assert only once not to add overhead to the benchmarks
    assertTrue(listToSort.isSorted)
}
    

Java

private final int[] unsorted = new int[10000];

public SampleBenchmark() {
    // Use random with the same seed, so that it generates the same data every
    // run.
    Random random = new Random(0);

    // Create the array once and copy it in benchmarks.
    Arrays.setAll(unsorted, (index) -> random.nextInt());
}

@Test
public void benchmark_quickSort() {
    final BenchmarkState state = benchmarkRule.getState();

    int[] listToSort = new int[0];

    while (state.keepRunning()) {
        
        // Copy the array with timing disabled to measure only the algorithm
        // itself.
        state.pauseTiming();
        listToSort = Arrays.copyOf(unsorted, 10000);
        state.resumeTiming();
        
        // Sort the array in place and measure how long it takes.
        SortingAlgorithms.quickSort(listToSort);
    }

    // Assert only once, not to add overhead to the benchmarks.
    assertTrue(SortingAlgorithmsKt.isSorted(listToSort));
}
    

Intenta minimizar la cantidad de trabajo realizado dentro del bloque measureRepeated y dentro de runWithTimingDisabled. El bloque measureRepeated se ejecuta varias veces y puede afectar el tiempo general necesario para ejecutar la comparativa. Si necesitas verificar algunos resultados de una comparativa, puedes confirmar el último resultado en lugar de hacerlo en cada iteración de la comparativa.

Cómo ejecutar las comparativas

En Android Studio, ejecuta tus comparativas como lo haces con cualquier @Test usando la acción del margen junto a tu clase o método de prueba, como se muestra en la figura 3.

Ejecutar Microbenchmark
Figura 3: Ejecución de la prueba de Microbenchmark con la acción del margen junto a una clase de prueba

Como alternativa, desde la línea de comandos, ejecuta connectedCheck para ejecutar todas las pruebas del módulo de Gradle especificado:

./gradlew benchmark:connectedCheck

También puedes ejecutar una sola prueba:

./gradlew benchmark:connectedCheck -P android.testInstrumentationRunnerArguments.class=com.example.benchmark.SampleBenchmark#benchmarkSomeWork

Resultados de comparativas

Después de ejecutar Microbenchmark con éxito, se muestran las métricas directamente en Android Studio, así como un informe de comparativas completo con información adicional del dispositivo y métricas disponibles en formato JSON.

Resultados de Microbenchmark
Figura 4: Resultados de Microbenchmark

Los informes de JSON y cualquier registro de generación de perfiles también se copian automáticamente del dispositivo al host. Están escritos en la máquina anfitrión en la siguiente ubicación:

project_root/module/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/device_id/

De forma predeterminada, el informe de JSON se escribe en el disco del dispositivo, en la carpeta externa del APK de prueba de contenido multimedia compartido que, por lo general, se encuentra en /storage/emulated/0/Android/media/**app_id**/**app_id**-benchmarkData.json.

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 está configurado en false.
  • Se está usando un dispositivo físico; no se admiten emuladores.
  • Los relojes están bloqueados si el dispositivo tiene permisos de administrador.
  • El nivel de batería en el dispositivo es suficiente (es de al menos 25%).

Si falla alguna de las verificaciones anteriores, las comparativas informan un error para evitar mediciones imprecisas.

Para hacer que algunos tipos de error específicos pasen como advertencias y evitar que detengan las comparativas, pasa el tipo de error en una lista separada por comas al argumento de instrumentación androidx.benchmark.suppressErrors.

Puedes configurar esto desde tu secuencia de comandos de Gradle, como se muestra en el siguiente ejemplo:

Kotlin

android {
    defaultConfig {
       …
      testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "DEBUGGABLE,LOW-BATTERY"
    }
}

Groovy

android {
    defaultConfig {
       …
      testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "DEBUGGABLE,LOW-BATTERY"
    }
}

También puedes suprimir errores desde la línea de comandos:

$ ./gradlew :benchmark:connectedCheck -P andoidtestInstrumentationRunnerArguments.androidx.benchmark.supperssErrors=DEBUGGABLE,LOW-BATTERY

Ten en cuenta que suprimir errores permite que las comparativas se ejecuten en un estado de configuración incorrecta, pero se le cambiará intencionalmente el nombre del resultado de las comparativas anteponiendo nombres de pruebas con el error. Por ejemplo, ejecutar una comparativa depurable con la supresión del fragmento anterior antepone DEBUGGABLE_ a los nombres de prueba.