Écrire un microbenchmark

Pour apprendre à utiliser la bibliothèque Microbenchmark en apportant des modifications au code de votre application, consultez la section Démarrage rapide. Pour savoir comment effectuer une configuration complète avec des modifications plus complexes de votre codebase, consultez la section Configuration complète du projet.

Démarrage rapide

Cette section explique comment tester l'analyse comparative et exécuter des mesures ponctuelles sans avoir à déplacer le code dans des modules. Pour obtenir des résultats de performances précis, ces étapes impliquent la désactivation du débogage dans votre application. Vous devez donc les conserver dans une copie de travail locale sans effectuer de commit des modifications dans votre système de contrôle des sources.

Pour effectuer une analyse comparative ponctuelle, procédez comme suit :

  1. Ajoutez la bibliothèque au fichier build.gradle ou build.gradle.kts de votre module :

    Kotlin

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

    Groovy

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

    Utilisez une dépendance implementation plutôt qu'une dépendance androidTestImplementation. Si vous utilisez androidTestImplementation, les benchmarks ne s'exécutent pas, car le fichier manifeste de la bibliothèque n'est pas fusionné dans le fichier manifeste de l'application.

  2. Mettez à jour le type de compilation debug pour qu'il ne soit pas débogable:

    Kotlin

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

    Groovy

    android {
        ...
        buildTypes {
            debug {
                debuggable false
            }
        }
    }
    
  3. Remplacez testInstrumentationRunner par AndroidBenchmarkRunner:

    Kotlin

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

    Groovy

    android {
        ...
        defaultConfig {
            testInstrumentationRunner "androidx.benchmark.junit4.AndroidBenchmarkRunner"
        }
    }
    
  4. Ajoutez une instance de BenchmarkRule dans un fichier de test du répertoire androidTest pour ajouter votre benchmark. Pour en savoir plus sur l'écriture des benchmarks, consultez Créer une classe Microbenchmark.

    L'extrait de code suivant montre comment ajouter un benchmark à un test d'instrumentation :

    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()
                );
           }
        }
    }
    

Pour savoir comment écrire un benchmark, passez à l'étape Créer une classe Microbenchmark.

Configuration complète du projet

Pour configurer une analyse comparative régulière plutôt que des analyses comparatives ponctuelles, isolez les benchmarks dans leur propre module. Cela permet de s'assurer que leur configuration, par exemple pour définir debuggable sur false, est distincte des tests standards.

Comme Microbenchmark exécute directement votre code, placez le code que vous souhaitez comparer dans un module Gradle distinct et définissez la dépendance sur ce module, comme illustré dans la figure 1.

Structure de l&#39;application
Figure 1. Structure de l'application avec les modules Gradle :app, :microbenchmark et :benchmarkable, permettant aux microbenchmarks de comparer le code dans le module :benchmarkable

Pour ajouter un module Gradle, vous pouvez utiliser l'assistant de module dans Android Studio. L'assistant crée un module préconfiguré pour l'analyse comparative, avec un répertoire de benchmark ajouté et debuggable défini sur false.

  1. Effectuez un clic droit sur votre projet ou votre module dans le panneau Project (Projet) d'Android Studio, puis cliquez sur New > Module (Nouveau > Module).

  2. Sélectionnez Benchmark dans le volet Templates (Modèles).

  3. Sélectionnez Microbenchmark comme type de module de benchmark.

  4. Saisissez "microbenchmark" comme nom de module.

  5. Cliquez sur Finish (Terminer).

Configurer un nouveau module de bibliothèque
Figure 2. Ajouter un module Gradle dans Android Studio Bumblebee

Une fois le module créé, modifiez son fichier build.gradle ou build.gradle.kts et ajoutez androidTestImplementation au module contenant le code à analyser :

Kotlin

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

Groovy

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

Créer une classe Microbenchmark

Les benchmarks sont des tests d'instrumentation standards. Pour créer un benchmark, utilisez la classe BenchmarkRule fournie par la bibliothèque. Pour comparer les activités, utilisez ActivityScenario ou ActivityScenarioRule. Pour comparer le code de l'interface utilisateur, utilisez @UiThreadTest.

Le code suivant présente un exemple de benchmark :

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();
        }
    }
}
    

Désactiver la durée pour la configuration

Vous pouvez désactiver la durée pour les sections de code que vous ne souhaitez pas mesurer à l'aide du bloc runWithTimingDisabled{}. Ces sections représentent généralement du code que vous devez exécuter à chaque itération du benchmark.

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));
}
    

Essayez de réduire la quantité de travail effectuée dans le bloc measureRepeated et dans runWithTimingDisabled. Le bloc measureRepeated est exécuté plusieurs fois et peut affecter le temps total nécessaire à l'exécution du benchmark. Si vous devez vérifier certains résultats d'un benchmark, vous pouvez revendiquer le dernier résultat au lieu de le faire à chaque itération du benchmark.

Exécuter le benchmark

Dans Android Studio, exécutez votre benchmark comme vous le feriez avec n'importe quel @Test à l'aide de la gouttière située en regard de votre classe ou méthode de test, comme illustré dans la figure 3.

Exécuter le microbenchmark
Figure 3. Exécuter un test Microbenchmark depuis la gouttière située en regard d'une classe de test

À partir de la ligne de commande, vous pouvez également exécuter connectedCheck pour lancer tous les tests du module Gradle spécifié :

./gradlew benchmark:connectedCheck

Ou un seul test :

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

Résultats du benchmark

Après une exécution réussie du microbenchmark, les métriques s'affichent directement dans Android Studio. Un rapport de benchmark complet avec des métriques supplémentaires et des informations sur les appareils est disponible au format JSON.

Résultats du microbenchmark
Figure 4. Résultats du microbenchmark

Les rapports JSON et les traces de profilage sont également copiés automatiquement depuis l'appareil vers l'hôte. Ils sont écrits sur la machine hôte à l'emplacement suivant :

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

Par défaut, le rapport JSON est écrit sur le disque de l'appareil, dans le dossier multimédia partagé externe de l'APK de test, qui se trouve généralement à cet emplacement : /storage/emulated/0/Android/media/**app_id**/**app_id**-benchmarkData.json.

Erreurs de configuration

La bibliothèque détecte les conditions suivantes pour vous assurer que votre projet et votre environnement sont configurés pour offrir des performances précises :

  • Débogable défini sur false.
  • Un appareil physique est en cours d'utilisation (les émulateurs ne sont pas acceptés).
  • Les horloges sont verrouillées si l'appareil est en mode root.
  • Niveau de batterie suffisant sur l'appareil (au moins 25 %).

Si l'une des vérifications précédentes échoue, le benchmark signale une erreur afin de décourager les mesures inexactes.

Pour supprimer certains types d'erreurs en tant qu'avertissements et les empêcher d'arrêter le benchmark, transmettez le type d'erreur dans une liste d'éléments séparés par une virgule à l'argument d'instrumentation androidx.benchmark.suppressErrors.

Vous pouvez définir ce paramètre à partir de votre script Gradle, comme dans l'exemple suivant :

Kotlin

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

Groovy

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

Vous pouvez également supprimer les erreurs à partir de la ligne de commande :

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

La suppression des erreurs permet au benchmark de s'exécuter dans un état mal configuré, et la sortie du benchmark est intentionnellement renommée en ajoutant des préfixes aux noms de test avec l'erreur. Par exemple, l'exécution d'un benchmark débogable avec la suppression incluse dans l'extrait précédent ajoute le préfixe DEBUGGABLE_ aux noms de test.