Améliorer les performances de l'application avec les profils de référence

Stay organized with collections Save and categorize content based on your preferences.

1. Avant de commencer

Dans cet atelier de programmation, vous apprendrez à générer des profils de référence pour optimiser les performances de votre application et découvrirez comment vérifier les gains de performances liés à l'utilisation de ces profils.

Conditions préalables

Cet atelier de programmation s'appuie sur l'atelier de programmation Inspecter les performances de l'application avec Macrobenchmark, qui explique comment mesurer les performances d'une application à l'aide de la bibliothèque Macrobenchmark.

Ce dont vous avez besoin

Objectifs de l'atelier

  • Générer des profils de référence pour optimiser les performances
  • Vérifier les gains de performances avec la bibliothèque Macrobenchmark

Points abordés

  • Générer des profils de référence
  • Comprendre les gains de performances liés à l'utilisation de profils de référence

2. Configuration

Pour commencer, clonez le dépôt GitHub à partir de la ligne de commande à l'aide de la commande suivante :

$ git clone --branch baselineprofiles-main  https://github.com/googlecodelabs/android-performance.git

Vous pouvez également télécharger deux fichiers ZIP :

Ouvrir le projet dans Android Studio

  1. Dans la fenêtre "Welcome to Android Studio" (Bienvenue dans Android Studio), sélectionnez c01826594f360d94.png Open an existing Project (Ouvrir un projet existant).
  2. Sélectionnez le dossier [Download Location]/android-performance/benchmarking (conseil : assurez-vous de sélectionner le répertoire benchmarking contenant build.gradle.)
  3. Une fois qu'Android Studio a importé le projet, assurez-vous de pouvoir exécuter le module app pour créer l'exemple d'application qui fera l'objet de l'analyse comparative.

3. Définition des profils de référence

Les profils de référence améliorent la vitesse d'exécution du code d'environ 30 % dès le premier lancement en évitant l'interprétation et les étapes de compilation juste à temps (JIT) pour les chemins de code inclus. En expédiant un profil de référence dans une application ou une bibliothèque, Android Runtime (ART) peut optimiser les chemins de code inclus via la compilation anticipée (ou compilation AOT), ce qui permet d'améliorer les performances pour chaque nouvel utilisateur et chaque mise à jour d'application. Cette optimisation guidée des profils permet aux applications d'optimiser le démarrage, de réduire les à-coups d'interaction et d'améliorer les performances d'exécution globales pour les utilisateurs finaux à partir du premier lancement.

Avantages des profils de référence

Avec un profil de référence, toutes les interactions utilisateur (telles que le démarrage d'une application, la navigation entre les écrans ou le défilement du contenu) sont plus fluides dès leur première exécution. Augmenter la vitesse et la réactivité d'une application entraîne une augmentation du nombre d'utilisateurs actifs par jour et un taux de visiteurs de retour moyen plus élevé.

Les profils de référence contribuent à guider l'optimisation des performances au-delà du démarrage de l'application en fournissant des interactions utilisateur courantes qui améliorent l'exécution dès le premier lancement. La compilation AOT guidée ne s'appuie pas sur les appareils des utilisateurs et peut être effectuée une fois pour chaque version sur un ordinateur de développement plutôt que sur un appareil mobile. En publiant des versions avec un profil de référence, les optimisations d'applications sont disponibles beaucoup plus rapidement qu'en utilisant uniquement les profils cloud.

Lorsque vous n'utilisez pas de profil de référence, le code de l'application fait l'objet d'une compilation JIT dans la mémoire après avoir été interprété, ou dans un fichier odex en arrière-plan lorsque l'appareil est inactif. Les utilisateurs bénéficient d'une expérience moins optimale la première fois qu'ils exécutent une application après l'avoir installée ou mise à jour, jusqu'à ce que les nouveaux chemins aient été optimisés. Cette amélioration des performances a été mesurée à environ 30 % pour de nombreuses applications.

4. Configurer le module d'analyse comparative

En tant que développeur d'applications, vous pouvez générer automatiquement des profils de référence à l'aide de la bibliothèque Jetpack Macrobenchmark. Pour générer des profils de référence, vous pouvez utiliser le module créé pour l'analyse comparative de votre application, en y apportant quelques modifications supplémentaires.

Désactiver l'obscurcissement pour les profils de référence

Si l'obscurcissement est activé dans votre application, vous devez le désactiver pour les analyses comparatives.

Pour ce faire, ajoutez un fichier ProGuard dans le module :app, désactivez l'obscurcissement à cet endroit et ajoutez le fichier ProGuard dans le type de compilation benchmark.

Créez un fichier nommé benchmark-rules.pro dans le module :app. Le fichier doit être placé dans le dossier /app/ à côté du fichier build.gradle spécifique au module. 27bd3b1881011d06.png

Dans ce fichier, ajoutez -dontobfuscate comme dans l'extrait de code suivant afin de désactiver l'obscurcissement :

# Disables obfuscation for benchmark builds.
-dontobfuscate

Modifiez ensuite le type de compilation benchmark dans le build.gradle spécifique au module :app, puis ajoutez le fichier que vous avez créé. Comme nous utilisons le type de compilation de version initWith, cette ligne ajoute le fichier ProGuard benchmark-rules.pro aux fichiers de version ProGuard.

buildTypes {
   release {
      // ...
   }

   benchmark {
      initWith buildTypes.release
      // ...
      proguardFiles('benchmark-rules.pro')
   }
}

Écrivons maintenant une classe de générateur de profils de référence.

5. Écrire un générateur de profils de référence

En général, vous générez des profils de référence pour les parcours utilisateur classiques de votre application.

Dans notre exemple, vous pouvez identifier les trois parcours suivants :

  1. Démarrer l'application (ce qui est essentiel pour la plupart des applications)
  2. Faire défiler la liste des collations
  3. Accéder aux détails des collations

Pour générer les profils de référence, nous allons ajouter une classe de test BaselineProfileGenerator dans le module :macrobenchmark. Cette classe utilisera une règle de test BaselineProfileRule et contiendra une méthode de test permettant de générer le profil. Le point d'entrée pour la génération du profil est la fonction collectBaselineProfile. Il ne nécessite que deux paramètres :

  • packageName, qui est le package de votre application
  • profileBlock (dernier paramètre lambda)
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {

   @get:Rule
   val rule = BaselineProfileRule()

   @Test
   fun generate() {
       rule.collectBaselineProfile("com.example.macrobenchmark_codelab") {
           // TODO Add interactions for the typical user journeys
       }
   }
}

Dans le lambda profileBlock, spécifiez les interactions qui couvrent les parcours utilisateur typiques de votre application. La bibliothèque exécutera la commande profileBlock plusieurs fois et collectera les classes et les fonctions appelées pour qu'elles soient optimisées et pour générer le profil de référence sur l'appareil.

Vous pouvez consulter les grandes lignes du générateur de profils de base qui couvre les parcours typiques dans l'extrait suivant :

@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {

   @get:Rule
   val rule = BaselineProfileRule()

   @Test
   fun generate() {
       rule.collectBaselineProfile("com.example.macrobenchmark_codelab") {
           startApplicationJourney() // TODO Implement
           scrollSnackListJourney() // TODO Implement
           goToSnackDetailJourney() // TODO Implement
       }
   }
}

À présent, écrivons les interactions pour chaque parcours mentionné. Vous pouvez les écrire en tant que fonction d'extension de MacrobenchmarkScope afin d'avoir accès aux paramètres et aux fonctions qu'elle fournit. Vous pourrez ainsi réutiliser les interactions avec les analyses comparatives pour vérifier les gains de performances.

Parcours de démarrage de l'application

Pour le parcours de démarrage de l'application (startApplicationJourney), vous devez couvrir les interactions suivantes :

  1. Appuyez sur "Home" (Accueil) pour vous assurer que l'état de l'application est redémarré.
  2. Démarrez l'activité par défaut et attendez que le premier frame s'affiche.
  3. Attendez que le contenu soit chargé et affiché, et que l'utilisateur puisse interagir avec lui.
fun MacrobenchmarkScope.startApplicationJourney() {
   pressHome()
   startActivityAndWait()
   val contentList = device.findObject(By.res("snack_list"))
   // Wait until a snack collection item within the list is rendered
   contentList.wait(Until.hasObject(By.res("snack_collection")), 5_000)
}

Parcours de défilement d'une liste

Pour le parcours de défilement d'une liste de collations (scrollSnackListJourney), vous pouvez suivre ces interactions :

  1. Recherchez l'élément d'interface correspondant à la liste de collations.
  2. Définissez les marges de manœuvre pour qu'elles ne déclenchent pas la navigation système.
  3. Faites défiler la liste et attendez que l'interface utilisateur s'arrête.
fun MacrobenchmarkScope.scrollSnackListJourney() {
   val snackList = device.findObject(By.res("snack_list"))
   // Set gesture margin to avoid triggering gesture navigation
   snackList.setGestureMargin(device.displayWidth / 5)
   snackList.fling(Direction.DOWN)
   device.waitForIdle()
}

Parcours d'accès aux informations détaillées

Le dernier parcours (goToSnackDetailJourney) met en œuvre les interactions suivantes :

  1. Recherchez la liste des collations et toutes les collations que vous pouvez utiliser.
  2. Sélectionnez un élément dans la liste.
  3. Cliquez sur l'élément et attendez que l'écran de détails soit chargé. Vous pouvez parti du fait que la liste de collations ne s'affiche plus à l'écran.
fun MacrobenchmarkScope.goToSnackDetailJourney() {
   val snackList = device.findObject(By.res("snack_list"))
   val snacks = snackList.findObjects(By.res("snack_item"))
   // Select random snack from the list
   snacks[Random.nextInt(snacks.size)].click()
   // Wait until the screen is gone = the detail is shown
   device.wait(Until.gone(By.res("snack_list")), 5_000)
}

Vous avez défini toutes les interactions nécessaires à l'exécution du générateur de profils de base. Toutefois, vous devez encore définir l'appareil sur lequel il doit s'exécuter.

6. Préparer un appareil géré par Gradle

Pour générer des profils de référence, vous devez d'abord disposer d'un émulateur userdebug. Si vous souhaitez automatiser leur processus de création, vous pouvez utiliser les appareils gérés par Gradle. Pour en savoir plus sur les appareils par gérés Gradle, consultez notre documentation.

Commencez par définir l'appareil géré par Gradle dans le fichier build.gradle du module :macrobenchmark, comme dans l'extrait de code suivant :

testOptions {
    managedDevices {
        devices {
            pixel2Api31(com.android.build.api.dsl.ManagedVirtualDevice) {
                device = "Pixel 2"
                apiLevel = 31
                systemImageSource = "aosp"
            }
        }
    }
}

Pour générer des profils de référence, vous devez utiliser Android 9 (API 28) ou une version ultérieure en mode root.

Dans le cas présent, nous utilisons Android 11 (niveau d'API 31). L'image système aosp permet l'accès en mode root.

L'appareil géré par Gradle vous permet d'exécuter des tests dans un émulateur Android sans avoir à le lancer et à le supprimer manuellement. Après avoir ajouté la définition à build.gradle, vous pourrez exécuter la nouvelle tâche pixel2Api31[BuildVariant]AndroidTest. Nous utiliserons cette tâche à l'étape suivante pour générer le profil de référence.

7. Générer le profil de référence

Une fois que l'appareil géré par Gradle est prêt, vous pouvez démarrer le test du générateur.

Exécuter le générateur à partir de la configuration d'exécution

Les appareils gérés par Gradle nécessitent d'exécuter le test en tant que tâche Gradle. Pour ne pas perdre de temps, nous avons créé une configuration qui spécifie la tâche avec tous les paramètres nécessaires à son exécution.

Pour l'exécuter, localisez la configuration d'exécution generateBaselineProfile et cliquez sur le bouton Run (Exécuter) 229e32fcbe68452f.png.

8f6b7c9a5da6585.png

Le test créera l'image de l'émulateur définie précédemment, exécutera les interactions plusieurs fois, puis supprimera l'émulateur et enverra la sortie à Android Studio.

4b5b2d0091b4518c.png

Exécuter le générateur à partir de la ligne de commande (facultatif)

Pour exécuter le générateur à partir de la ligne de commande, vous pouvez exploiter la tâche créée par l'appareil géré par Gradle : :macrobenchmark:pixel2Api31BenchmarkAndroidTest.

Cette commande exécute tous les tests du projet, lesquels échoueraient, car le module contient également des analyses comparatives afin de vérifier ultérieurement les gains de performances.

Dès lors, vous pouvez filtrer la classe que vous souhaitez exécuter avec le paramètre -P android.testInstrumentationRunnerArguments.class et spécifier l'objet com.example.macrobenchmark.BaselineProfileGenerator que vous avez écrit précédemment.

La commande complète se présente comme suit :

./gradlew :macrobenchmark:pixel2Api31BenchmarkAndroidTest -P android.testInstrumentationRunnerArguments.class=com.example.macrobenchmark.BaselineProfileGenerator

8. Appliquer le profil de référence généré

Une fois le générateur terminé, vous devez effectuer plusieurs opérations pour que le profil de référence fonctionne avec votre application.

Placez le fichier de profils de référence générés dans le dossier src/main (à côté d'AndroidManifest.xml). Pour récupérer le fichier, vous pouvez le copier à partir du dossier managed_device_android_test_additional_output/, situé dans /macrobenchmark/build/outputs/, comme illustré dans la capture d'écran suivante.

b104f315f06b3578.png

Vous pouvez également cliquer sur le lien results dans la sortie Android Studio et enregistrer le contenu, ou utiliser la commande adb pull affichée dans la sortie.

Ensuite, remplacez le nom du fichier par baseline-prof.txt.

8973f012921669f6.png

Ajoutez ensuite la dépendance profileinstaller au module :app.

dependencies {
  implementation("androidx.profileinstaller:profileinstaller:1.2.0")
}

L'ajout de cette dépendance vous permet d'effectuer les opérations suivantes :

  • Analyse comparative locale des profils de référence
  • Utilisation des profils de référence sur Android 7 (niveau d'API 24) et Android 8 (niveau d'API 26), qui ne sont pas compatibles avec les profils cloud
  • Utilisation des profils de référence sur les appareils qui ne disposent pas des services Google Play

Enfin, cliquez sur l'icône 1079605eb7639c75.png pour synchroniser le projet avec les fichiers Gradle.

40cb2ba3d0b88dd6.png

À l'étape suivante, nous découvrirons comment vérifier les performances d'une application avec des profils de référence.

9. Vérifier l'amélioration des performances au démarrage

Nous avons maintenant généré le profil de référence et l'avons ajouté à notre application. Vérifions qu'il a l'effet souhaité sur ses performances.

Revenons à la classe ExampleStartupBenchmark qui contient une analyse comparative pour mesurer le démarrage d'une application. Vous devrez modifier légèrement le test startup() pour le réutiliser avec différents modes de compilation. Cela vous permet de comparer les écarts lorsque vous utilisez des profils de référence.

Paramètre CompilationMode

Le paramètre CompilationMode définit la manière dont l'application est précompilée dans le code machine. Il propose les options suivantes :

  • DEFAULT : l'application est précompilée partiellement à l'aide de profils de référence, le cas échéant. Cette option est utilisée si aucun paramètre compilationMode n'est appliqué.
  • None() : l'état de la compilation est réinitialisé, et l'application n'est pas précompilée. La compilation juste à temps (JIT) reste activée lors de l'exécution de l'application.
  • Partial() : l'application est précompilée avec des profils de référence et/ou des exécutions d'échauffement.
  • Full() : l'ensemble du code de l'application est précompilé. Il s'agit de la seule option disponible sur Android 6 (API 23) ou version antérieure.

Si vous souhaitez commencer à optimiser les performances de votre application, vous pouvez choisir le mode de compilation DEFAULT, car les performances seront similaires à celles obtenues lorsque l'application sera installée depuis Google Play. Si vous souhaitez comparer les gains de performances offerts par les profils de référence, vous pouvez évaluer les résultats du mode de compilation None et Partial.

Modifier le test de démarrage avec un autre mode de compilation

Tout d'abord, supprimez l'annotation @Test de la méthode startup (car les tests JUnit ne peuvent pas comporter de paramètres), puis ajoutez le paramètre compilationMode et utilisez-le dans la fonction measureRepeated :

// Remove @Test annotation and add the compilationMode parameter.
fun startup(compilationMode: CompilationMode) = benchmarkRule.measureRepeated(
   packageName = "com.google.samples.apps.sunflower",
   metrics = listOf(StartupTimingMetric()),
   iterations = 5,
   compilationMode = compilationMode, // Set the compilation mode
   startupMode = StartupMode.COLD
) {
   pressHome()
   startActivityAndWait()
}

Maintenant, ajoutez deux fonctions de test avec un autre mode de compilation (CompilationMode). Le premier utilisera CompilationMode.None, ce qui signifie qu'avant chaque analyse comparative, l'état de l'application sera réinitialisé et aucun code précompilé ne sera fourni.

@Test
fun startupCompilationNone() = startup(CompilationMode.None())

Le deuxième test utilisera CompilationMode.Partial, qui charge les profils de référence et précompile les classes et les fonctions spécifiées à partir du profil avant d'exécuter l'analyse comparative.

@Test
fun startupCompilationPartial() = startup(CompilationMode.Partial())

Vous pouvez éventuellement ajouter une troisième méthode qui précompile l'ensemble de l'application avec CompilationMode.Full. Il s'agit de la seule option disponible pour Android 6 (API 23) ou version antérieure, car le système n'exécute que des applications entièrement précompilées.

@Test
fun startupCompilationFull() = startup(CompilationMode.Full())

Ensuite, exécutez les analyses comparatives comme auparavant (sur un appareil physique) et attendez que Macrobenchmark mesure les temps de démarrage avec différents modes de compilation.

Une fois les analyses comparatives terminées, ces délais s'affichent dans la sortie Android Studio, comme illustré dans la capture d'écran suivante :

98e01ce602447001.png

Sur la capture d'écran, vous pouvez constater que le temps de démarrage de l'application est différent pour chaque mode de compilation (CompilationMode). Les valeurs médianes sont indiquées dans le tableau suivant :

timeToInitialDisplay [ms]

timeToFullDisplay [ms]

Aucun

396,8

818,1

Complet

373,9

755

Partiel

352,9

720,9

Intuitivement, la compilation None génère le pire des résultats, car l'appareil doit effectuer le plus de compilation JIT au démarrage de l'application. Paradoxalement, la compilation Full n'est pas optimale. Dans ce cas, tout est compilé. Par conséquent, le fichier odex de l'application est très volumineux. Le système doit donc généralement effectuer beaucoup plus d'E/S au démarrage de l'application. Les meilleures performances sont celles du cas Partial qui utilise le profil de référence. En effet, la compilation partielle trouve un juste milieu entre la compilation du code qui est le plus susceptible d'être utilisé et la non-compilation du code non critique, qui n'a pas à être chargé immédiatement.

10. Vérifier l'amélioration des performances de défilement

Comme à l'étape précédente, vous pouvez mesurer et vérifier les analyses comparatives du défilement. Modifions la classe ScrollBenchmarks comme précédemment, en ajoutant un paramètre au test scroll, ainsi que d'autres tests avec un paramètre de mode de compilation différent.

Ouvrez le fichier ScrollBenchmarks.kt, modifiez la fonction scroll() pour ajouter le paramètre compilationMode :

fun scroll(compilationMode: CompilationMode) {
        benchmarkRule.measureRepeated(
            compilationMode = compilationMode, // Set the compilation mode
            // ...

Vous allez maintenant définir plusieurs tests avec des paramètres différents :

@Test
fun scrollCompilationNone() = scroll(CompilationMode.None())

@Test
fun scrollCompilationPartial() = scroll(CompilationMode.Partial())

Exécutez les analyses comparatives comme auparavant pour obtenir des résultats, tels que ceux illustrés dans la capture d'écran suivante :

e856a7dad7dccd72.png

D'après les résultats, vous pouvez constater que CompilationMode.Partial présente en moyenne un temps de rendu plus court de 0,4 ms, ce qui n'est pas nécessairement visible pour les utilisateurs. En revanche, pour les autres centiles, les résultats sont plus évidents. Pour P99, la différence est de 36,9 ms, soit plus de trois frames ignorés (le Pixel 7 a un FPS de 90, soit environ 11 ms).

11. Félicitations

Félicitations, vous avez mené à bien cet atelier de programmation et avez amélioré les performances de votre application grâce aux profils de référence.

Et maintenant ?

Consultez notre dépôt GitHub d'exemples de performances, qui contient le code Macrobenchmark et d'autres exemples de performances. Consultez également l'application exemple Now In Android, une application réelle qui utilise les analyses comparatives et les profils de référence pour améliorer les performances.

Documents de référence