Effectuer le profilage de votre build

Les projets de plus grande envergure, ou ceux implémentant une logique de compilation personnalisée, peuvent nécessiter un examen plus approfondi du processus de compilation afin d'identifier les goulots d'étranglement. Pour ce faire, vous pouvez déterminer le délai nécessaire à Gradle pour exécuter chaque phase du cycle de compilation et chaque tâche de compilation. Par exemple, si votre profil de compilation montre que Gradle passe trop de temps à configurer votre projet, il peut vous suggérer de déplacer la logique de compilation personnalisée hors de la phase de configuration. De plus, si la tâche mergeDevDebugResources consomme une large part de la durée de compilation, cela peut indiquer que vous devez convertir vos images au format WebP ou désactiver le traitement PNG.

Si vous utilisez Android Studio 4.0 ou une version ultérieure, le meilleur moyen d'examiner les problèmes de performances de compilation consiste à utiliser l'outil Build Analyzer.

En outre, il existe deux options pour effectuer le profilage en dehors d'Android Studio :

  1. L'outil gradle-profiler autonome, un outil robuste pour analyser en profondeur votre compilation.

  2. L'option --profile Gradle, un outil pratique disponible depuis la ligne de commande Gradle.

Utiliser l'outil gradle-profiler autonome

Pour trouver la configuration de projet qui vous offre la meilleure vitesse de compilation, vous devez utiliser le profileur Gradle, un outil permettant de collecter des informations de profilage et d'analyse comparative pour les compilations Gradle. Le profileur Gradle vous permet de créer des scénarios de compilation et de les exécuter plusieurs fois, ce qui évite les écarts importants entre les résultats et assure leur reproductibilité.

Le mode d'analyse comparative permet de collecter des informations sur les builds de type propre et incrémentiel, tandis que le mode profilage permet de collecter des informations plus détaillées sur les exécutions, y compris les instantanés du processeur.

Voici quelques-unes des configurations de projet pour l'analyse comparative :

  • Versions du plug-in
  • Versions de Gradle
  • Paramètres JVM (taille du tas de mémoire, taille permgen, récupération de mémoire, etc.)
  • Nombre de nœuds de calcul Gradle (org.gradle.workers.max)
  • Options par plug-in à des fins d'optimisation des performances

Premiers pas

  • Installez le profileur Gradle en suivant ces instructions.
  • Exécuter : gradle-profiler --benchmark --project-dir <root-project> :app:assembleDebug

Cela permet de comparer une compilation entièrement à jour, car --benchmark exécute la tâche plusieurs fois, sans modifier le projet entre chacune. Il génère ensuite un rapport HTML dans le répertoire profile-out/, qui indique les durées compilation.

D'autres scénarios peuvent s'avérer plus utiles pour l'analyse comparative :

  • Modifications du code dans le corps d'une méthode au sein d'une classe où vous effectuez la plupart de vos tâches.
  • Modifications de l'API dans un module utilisé tout au long de votre projet. Bien que moins fréquent que les modifications apportées à votre code, cela a un impact plus important et il est utile de le mesurer.
  • Modifications de la mise en page pour simuler des itérations sur les opérations d'interface utilisateur.
  • Modifications de chaînes pour simuler les opérations de translation.
  • Compilations propres pour simuler les modifications apportées à la compilation elle-même (par exemple, mise à jour du plug-in Android Gradle, mise à jour Gradle ou modifications apportées à votre code de compilation sous buildSrc).

Afin de comparer ces cas d'utilisation, vous pouvez créer un scénario qui sera utilisé pour piloter l'exécution de gradle-profiler et appliquer les modifications appropriées à vos sources. Vous pouvez examiner plusieurs des scénarios courants ci-dessous.

Effectuer le profilage de différents paramètres de mémoire/processeur

Pour comparer différents paramètres de mémoire et de processeur, vous pouvez créer plusieurs scénarios qui utilisent des valeurs différentes pour org.gradle.jvmargs. Par exemple, vous pouvez créer des scénarios :

# <root-project>/scenarios.txt
clean_build_2gb_4workers {
    tasks = [":app:assembleDebug"]
    gradle-args = ["--max-workers=4"]
    jvm-args = ["-Xmx2048m"]
    cleanup-tasks = ["clean"]
}
clean_build_parallelGC {
    tasks = [":app:assembleDebug"]
    jvm-args = ["-XX:+UseParallelGC"]
    cleanup-tasks = ["clean"]
}

clean_build_G1GC_4gb {
    tasks = [":app:assembleDebug"]
    jvm-args = ["-Xmx4096m", "-XX:+UseG1GC"]
    cleanup-tasks = ["clean"]
}

L'exécution de gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt exécute trois scénarios et vous permet de comparer le temps nécessaire à :app:assembleDebug pour chacune de ces configurations.

Effectuer le profilage de différentes versions du plug-in Gradle

Pour connaître l'impact du changement de version du plug-in Gradle sur les durées de compilation, créez un scénario à des fins d'analyse comparative. Une certaine préparation est nécessaire pour permettre l'injection de la version du plug-in à partir du scénario. Modifiez votre racine build.gradle :

# <root-project>/build.gradle
buildscript {
    def agpVersion = providers.systemProperty("agpVersion").forUseAtConfigurationTime().orNull ?: '4.1.0'

    ext.kotlin = providers.systemProperty('kotlinVersion').forUseAtConfigurationTime().orNull ?: '1.4.0'

    dependencies {
        classpath "com.android.tools.build:gradle:$agpVersion"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin"
    }
}

Vous pouvez maintenant spécifier les versions du plug-in Android Gradle et du plug-in Kotlin à partir du fichier de scénarios, puis demander au scénario d'ajouter une méthode aux fichiers sources :

# <root-project>/scenarios.txt
non_abi_change_agp4.1.0_kotlin1.4.10 {
    tasks = [":app:assembleDebug"]
    apply-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    System-properties {
      "agpVersion" = "4.1.0"
      "kotlinVersion" = "1.4.10"
}

non_abi_change_agp4.2.0_kotlin1.4.20 {
    tasks = [":app:assembleDebug"]
    apply-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    System-properties {
      "agpVersion" = "4.2.0-alpha16"
      "kotlinVersion" = "1.4.20"
}

Effectuer le profilage d'une compilation incrémentielle

La plupart des compilations sont incrémentielles, ce qui en fait l'un des scénarios les plus importants en termes de profilage. Le profileur Gradle est parfaitement compatible avec le profilage des compilations incrémentielles. Il peut appliquer automatiquement des modifications à un fichier source en modifiant le corps d'une méthode, en ajoutant une méthode ou en modifiant une mise en page ou une ressource de chaîne. Par exemple, vous pouvez créer des scénarios incrémentiels comme suit :

# <root-project>/scenarios.txt
non_abi_change {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to = ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
}

abi_change {
    tasks = [":app:assembleDebug"]
    apply-abi-change-to = ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
}

layout_change {
    tasks = [":app:assembleDebug"]
    apply-android-layout-change-to = "app/src/main/res/your_layout_file.xml"
}
string_resource_change {
    tasks = [":app:assembleDebug"]
    apply-android-resource-value-change-to = "app/src/main/res/values/strings.xml"
}

L'exécution de gradle-profiler --benchmark --project-dir &lt;root-project> --scenario-file scenarios.txt génère le rapport HTML contenant les données d'analyse comparative.

Vous pouvez combiner des scénarios incrémentiels avec d'autres paramètres, tels que la taille du tas de mémoire, le nombre de nœuds de calcul ou la version de Gradle :

# <root-project>/scenarios.txt
non_abi_change_4g {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    jvm-args = ["-Xmx4096m"]
}

non_abi_change_4g_8workers {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    jvm-args = ["-Xmx4096m"]
    gradle-args = ["--max-workers=8"]
}

non_abi_change_3g_gradle67 {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    jvm-args = ["-Xmx3072m"]
    version = ["6.7"]
}

Effectuer le profilage d'une compilation propre

Pour comparer une compilation propre, vous pouvez créer un scénario qui sera utilisé pour piloter l'exécution du profileur Gradle :

# <root-project>/scenarios.txt
clean_build {
    tasks = [":app:assembleDebug"]
    cleanup-tasks = ["clean"]
}

Pour exécuter ce scénario, utilisez la commande suivante :

gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt

Utiliser l'option --profile Gradle

Pour générer et afficher un profil de compilation à partir de la ligne de commande Gradle, procédez comme suit :

  1. Ouvrez un terminal de ligne de commande à la racine de votre projet.
  2. Effectuez une compilation propre en saisissant la commande suivante. Lors du profilage de votre compilation, vous devez effectuer une compilation "propre" entre chaque compilation concernée, car Gradle ignore les tâches dont les entrées (code source, par exemple) ne changent pas. Ainsi, une deuxième compilation sans modification des entrées s'exécute toujours plus rapidement, car les tâches ne sont pas réexécutées. L'exécution de la tâche clean entre vos compilations vous permet d'effectuer le profilage du processus de compilation dans son intégralité.
    // On Mac or Linux, run the Gradle wrapper using "./gradlew".
    gradlew clean
    
  3. Exécutez une version de débogage de l'un de vos types de produit, comme "dev", avec les options suivantes :
    gradlew --profile --offline --rerun-tasks assembleFlavorDebug
    
    • --profile : active le profilage.
    • --offline : empêche Gradle de récupérer les dépendances en ligne. Cela permet de s'assurer que les retards découlant d'une tentative de mise à jour de vos dépendances par Gradle n'interfèrent pas avec vos données de profilage. Vous devez avoir compilé votre projet une fois pour vous assurer que Gradle a déjà téléchargé et mis en cache vos dépendances.
    • --rerun-tasks : force Gradle à réexécuter toutes les tâches et à ignorer les optimisations.
  4. Figure 1. Vue du projet indiquant l'emplacement des rapports sur les profils.

    Une fois la compilation terminée, utilisez la fenêtre Project (Projet) pour accéder au répertoire project-root/build/reports/profile/ (comme illustré dans la figure 1).

  5. Effectuez un clic droit sur le fichier profile-timestamp.html, puis sélectionnez Open in Browser > Default (Ouvrir dans le navigateur > Par défaut). Le rapport doit être semblable à celui illustré dans la figure 2. Vous pouvez inspecter chaque onglet du rapport pour en savoir plus sur votre compilation, comme l'onglet Task Execution (Exécution de tâche) qui indique le temps nécessaire à Gradle pour exécuter chaque tâche de compilation.

    Figure 2. Afficher un rapport dans un navigateur.

  6. Facultatif : Avant de modifier la configuration de votre projet ou compilation, répétez la commande de l'étape 3, mais en omettant l'indicateur --rerun-tasks. Gradle ne réexécute pas les tâches dont les entrées n'ont pas changé (indiquées par UP-TO-DATE dans l'onglet Task Execution du rapport, comme illustré dans la figure 3) pour tenter de gagner du temps, ce qui vous permet d'identifier les tâches en cours. Par exemple, si :app:processDevUniversalDebugManifest n'est pas marqué comme UP-TO-DATE, cela peut indiquer que la configuration de compilation met à jour le fichier manifeste à chaque compilation. Cependant, certaines tâches, comme :app:checkDevDebugManifest, doivent être exécutées lors de chaque compilation.

    Figure 3. Afficher les résultats d'exécution d'une tâche

Maintenant que vous disposez d'un rapport sur les profils de compilation, vous pouvez commencer à rechercher des possibilités d'optimisation en inspectant les informations contenues dans chaque onglet du rapport. Certains paramètres de compilation impliquent une expérimentation, car leurs avantages peuvent différer entre les projets et les postes de travail. Par exemple, les projets dotés d'un vaste codebase peuvent bénéficier de la minification de code afin de supprimer le code inutilisé et de réduire la taille de l'application. Pour les petits projets, il peut cependant s'avérer plus intéressant de désactiver la minification de code. En outre, l'augmentation de la taille de tas de mémoire Gradle (avec org.gradle.jvmargs) peut avoir un impact négatif sur les performances des machines à faible mémoire.

Après avoir modifié votre configuration de compilation, observez les résultats de vos modifications en répétant les étapes ci-dessus et en générant un nouveau profil de compilation. Par exemple, la figure 4 montre un rapport correspondant à une même application, après application de certaines optimisations de base décrites sur cette page.

Figure 4. Afficher un nouveau rapport après avoir optimisé la vitesse de compilation