Déboguer les profils de référence

Ce document présente les bonnes pratiques pour analyser les problèmes et vous assurer que vos profils de référence fonctionnent correctement pour en tirer le meilleur parti.

Problèmes de compilation

Si vous avez copié l'exemple de profils de référence dans l'appli exemple Now in Android, cela peut se traduire par des échecs du test, notamment lors de la tâche indiquant que les tests ne peuvent pas être exécutés sur un émulateur :

./gradlew assembleDemoRelease
Starting a Gradle Daemon (subsequent builds will be faster)
Calculating task graph as no configuration cache is available for tasks: assembleDemoRelease
Type-safe project accessors is an incubating feature.

> Task :benchmarks:pixel6Api33DemoNonMinifiedReleaseAndroidTest
Starting 14 tests on pixel6Api33

com.google.samples.apps.nowinandroid.foryou.ScrollForYouFeedBenchmark > scrollFeedCompilationNone[pixel6Api33] FAILED
        java.lang.AssertionError: ERRORS (not suppressed): EMULATOR
        WARNINGS (suppressed):
        ...

Ces échecs se produisent, car l'appli Now in Android utilise un appareil géré par Gradle pour générer des profils de référence. Il est normal que vous rencontriez ces échecs, car il n'est pas conseillé d'exécuter des analyses comparatives des performances sur un émulateur. Toutefois, comme vous ne collectez pas de métriques de performances lorsque vous générez des profils de référence, vous pouvez exécuter la collecte de profils de référence sur des émulateurs pour plus de commodité. Pour utiliser des profils de référence avec un émulateur, effectuez la compilation et l'installation à partir de la ligne de commande, puis définissez un argument pour activer les règles de profils de référence :

installDemoRelease -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile

Vous pouvez également créer une configuration d'exécution personnalisée dans Android Studio pour activer les profils de référence sur les émulateurs en sélectionnant Run > Edit Configurations (Exécuter > Modifier les configurations) :

Ajouter une configuration d'exécution personnalisée pour créer des profils de référence dans l'appli Now in Android
Figure 1. Ajoutez une configuration d'exécution personnalisée pour créer des profils de référence dans Now in Android.

Problèmes d'installation

Vérifiez que l'APK ou l'AAB que vous compilez provient d'une variante de compilation qui inclut des profils de référence. Le moyen le plus simple de vérifier cela consiste à ouvrir l'APK dans Android Studio en sélectionnant Build > Analyze APK (Compiler > Analyser l'APK). Ouvrez ensuite votre fichier APK, et recherchez le profil dans le fichier /assets/dexopt/baseline.prof :

Vérifier un profil de référence à l'aide du lecteur d'APK dans Android Studio
Figure 2. Vérifier un profil de référence à l'aide du lecteur d'APK dans Android Studio.

Les profils de référence doivent être compilés sur l'appareil qui exécute l'appli. Pour les installations sur la plate-forme de téléchargement d'applications et pour les applis installées à l'aide de PackageInstaller, la compilation sur l'appareil s'effectue lors du processus d'installation de l'appli. Toutefois, lorsque l'appli est téléchargée indépendamment d'Android Studio ou à l'aide d'outils de ligne de commande, la bibliothèque Jetpack ProfileInstaller est chargée de mettre les profils en file d'attente avant qu'ils ne soient compilés lors du prochain processus d'optimisation DEX en arrière-plan. Dans ce cas, si vous souhaitez vous assurer que vos profils de référence sont utilisés, vous devrez peut-être forcer la compilation des profils de référence. ProfileVerifier vous permet d'interroger l'état de l'installation et de la compilation du profil, comme illustré dans l'exemple suivant :

Kotlin

private const val TAG = "MainActivity"

class MainActivity : ComponentActivity() {
  ...
  override fun onResume() {
    super.onResume()
    lifecycleScope.launch {
      logCompilationStatus()
    }
  }

  private suspend fun logCompilationStatus() {
     withContext(Dispatchers.IO) {
        val status = ProfileVerifier.getCompilationStatusAsync().await()
        when (status.profileInstallResultCode) {
            RESULT_CODE_NO_PROFILE ->
                Log.d(TAG, "ProfileInstaller: Baseline Profile not found")
            RESULT_CODE_COMPILED_WITH_PROFILE ->
                Log.d(TAG, "ProfileInstaller: Compiled with profile")
            RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION ->
                Log.d(TAG, "ProfileInstaller: Enqueued for compilation")
            RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING ->
                Log.d(TAG, "ProfileInstaller: App was installed through Play store")
            RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST ->
                Log.d(TAG, "ProfileInstaller: PackageName not found")
            RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ ->
                Log.d(TAG, "ProfileInstaller: Cache file exists but cannot be read")
            RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE ->
                Log.d(TAG, "ProfileInstaller: Can't write cache file")
            RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION ->
                Log.d(TAG, "ProfileInstaller: Enqueued for compilation")
            else ->
                Log.d(TAG, "ProfileInstaller: Profile not compiled or enqueued")
        }
    }
}

Java


public class MainActivity extends ComponentActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onResume() {
        super.onResume();

        logCompilationStatus();
    }

    private void logCompilationStatus() {
         ListeningExecutorService service = MoreExecutors.listeningDecorator(
                Executors.newSingleThreadExecutor());
        ListenableFuture<ProfileVerifier.CompilationStatus> future =
                ProfileVerifier.getCompilationStatusAsync();
        Futures.addCallback(future, new FutureCallback<>() {
            @Override
            public void onSuccess(CompilationStatus result) {
                int resultCode = result.getProfileInstallResultCode();
                if (resultCode == RESULT_CODE_NO_PROFILE) {
                    Log.d(TAG, "ProfileInstaller: Baseline Profile not found");
                } else if (resultCode == RESULT_CODE_COMPILED_WITH_PROFILE) {
                    Log.d(TAG, "ProfileInstaller: Compiled with profile");
                } else if (resultCode == RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION) {
                    Log.d(TAG, "ProfileInstaller: Enqueued for compilation");
                } else if (resultCode == RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING) {
                    Log.d(TAG, "ProfileInstaller: App was installed through Play store");
                } else if (resultCode == RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST) {
                    Log.d(TAG, "ProfileInstaller: PackageName not found");
                } else if (resultCode == RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ) {
                    Log.d(TAG, "ProfileInstaller: Cache file exists but cannot be read");
                } else if (resultCode
                        == RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE) {
                    Log.d(TAG, "ProfileInstaller: Can't write cache file");
                } else if (resultCode == RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION) {
                    Log.d(TAG, "ProfileInstaller: Enqueued for compilation");
                } else {
                    Log.d(TAG, "ProfileInstaller: Profile not compiled or enqueued");
                }
            }

            @Override
            public void onFailure(Throwable t) {
                Log.d(TAG,
                        "ProfileInstaller: Error getting installation status: " + t.getMessage());
            }
        }, service);
    }
}

Les codes de résultat suivants fournissent des indices sur la cause de certains problèmes :

RESULT_CODE_COMPILED_WITH_PROFILE
Le profil est installé, compilé et utilisé chaque fois que l'application est exécutée. Voici le résultat que vous devez obtenir.
RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED
Aucun profil n'a été trouvé dans l'APK ni l'AAB en cours d'exécution. Si cette erreur se produit, assurez-vous d'utiliser une variante de compilation qui inclut des profils de référence et que l'APK contient un profil.
RESULT_CODE_NO_PROFILE
Aucun profil n'a été installé pour cette appli lors de son installation via une plate-forme de téléchargement d'applications ou un gestionnaire de paquets. Ce code d'erreur apparaît, car que le programme d'installation du profil ne s'est pas exécuté, ProfileInstallerInitializer étant désactivé. Notez que lorsque cette erreur a été signalée, un profil intégré a tout de même été détecté dans l'APK de l'appli. Lorsqu'un profil intégré est introuvable, le code d'erreur renvoyé est RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED.
RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION
Un profil a été détecté dans l'APK ou l'AAB et a été mis en file d'attente pour compilation. Lorsqu'un profil est installé par ProfileInstaller, il est mis en file d'attente avant d'être compilé lors de la prochaine optimisation DEX en arrière-plan du système. Le profil n'est pas actif tant que la compilation n'est pas terminée. N'essayez pas d'effectuer une analyse comparative de vos profils de référence tant que la compilation n'est pas terminée. Vous devrez peut-être forcer la compilation des profils de référence. Cette erreur ne se produit pas lorsque l'appli est installée à partir de la plate-forme de téléchargement d'applications ou du gestionnaire de paquets sur des appareils équipés d'Android 9 (API 28) ou d'une version ultérieure, car la compilation s'effectue lors de l'installation.
RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING
Un profil qui ne correspond pas est installé et l'appli a été compilée avec ce même profil. Ceci est dû à une installation via le Google Play Store ou le gestionnaire de paquets. Notez que ce résultat diffère de RESULT_CODE_COMPILED_WITH_PROFILE, car le profil qui ne correspond pas ne compile que les méthodes encore partagées entre le profil et l'application. Le profil est en fait plus petit que prévu, et moins de méthodes seront compilées que celles incluses dans le profil de référence.
RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE
ProfileVerifier ne peut pas écrire le fichier de cache des résultats de validation. Cela peut être lié à un problème avec les autorisations du dossier de l'appli ou si l'espace disque disponible sur l'appareil est insuffisant.
RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION
ProfileVerifieris running on an unsupported API version of Android. ProfileVerifier n'est compatible qu'avec Android 9 (niveau d'API 28) et les versions ultérieures.
RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST
Lorsque PackageManager est interrogé pour le package d'applis,
un code PackageManager.NameNotFoundException est généré . Cela ne se produit que rarement. Essayez de désinstaller l'appli, puis de tout réinstaller.
RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ
Un précédent fichier de cache de résultats de validation existe, mais ne peut être lu. Cela ne se produit que rarement. Essayez de désinstaller l'appli, puis de tout réinstaller.

Utiliser ProfileVerifier en production

En production, vous pouvez utiliser ProfileVerifier conjointement avec des bibliothèques de rapports d'analyse telles que Google Analytics pour Firebase pour générer des événements d'analyse indiquant l'état du profil. Par exemple, vous serez rapidement averti lorsqu'une nouvelle version de l'appli ne contenant pas de profils de référence sortira.

Forcer la compilation des profils de référence

Si la compilation de vos profils de référence est à l'état RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION, vous pouvez forcer la compilation instantanée à l'aide de adb :

adb shell cmd package compile -r bg-dexopt PACKAGE_NAME

Vérifier l'état de la compilation sans ProfileVerifier

Si vous n'utilisez pas ProfileVerifier, vous pouvez vérifier l'état de la compilation à l'aide de adb, bien qu'il ne fournisse pas d'insights aussi détaillés que ProfileVerifier :

adb shell dumpsys package dexopt | grep -A 2 PACKAGE_NAME

L'utilisation de adb produit un résultat semblable à celui-ci :

  [com.google.samples.apps.nowinandroid.demo]
    path: /data/app/~~dzJiGMKvp22vi2SsvfjkrQ==/com.google.samples.apps.nowinandroid.demo-7FR1sdJ8ZTy7eCLwAnn0Vg==/base.apk
      arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]
        [location is /data/app/~~dzJiGMKvp22vi2SsvfjkrQ==/com.google.samples.apps.nowinandroid.demo-7FR1sdJ8ZTy7eCLwAnn0Vg==/oat/arm64/base.odex]

La valeur d'état indique l'état de compilation du profil et correspond à l'une des valeurs suivantes :

État de compilation Signification
speed‑profile Un profil compilé existe et est en cours d'utilisation.
verify Aucun profil compilé n'existe.

L'état verify ne signifie pas que l'APK ou l'AAB ne contient aucun profil, car ils peuvent être mis en file d'attente avant d'être compilés à la prochaine tâche d'optimisation DEX en arrière-plan.

La valeur de motif indique ce qui déclenche la compilation du profil. Il s'agit de l'une des valeurs suivantes :

Motif Signification
install‑dm Un profil de référence a été compilé manuellement ou par Google Play lors de l'installation de l'appli.
bg‑dexopt Un profil a été compilé alors que votre appareil était inactif. Il peut s'agir d'un profil de référence ou d'un profil collecté lors de l'utilisation de l'appli.
cmdline La compilation a été déclenchée à l'aide d'adb. Il peut s'agir d'un profil de référence ou d'un profil collecté lors de l'utilisation de l'appli.

Problèmes de performance

Cette section présente quelques bonnes pratiques pour effectuer une analyse comparative correcte de vos profils de référence et bien les définir afin d'en tirer le meilleur parti.

Effectuer une analyse comparative correcte des métriques de démarrage

Vos profils de référence seront plus efficaces si vos métriques de démarrage sont bien définies. Les deux métriques clés sont le délai d'affichage initial (TTID) et le délai d'affichage total (TTFD).

Le TTID correspond au moment où l'appli affiche sa première frame. Il est important que ce délai soit aussi court que possible, car lorsqu'un élément s'affiche, l'utilisateur comprend que l'appli est en cours d'exécution. Vous pouvez d'ailleurs afficher un indicateur de progression indéterminé pour indiquer que l'appli répond bien.

Le TTFD correspond au moment où il devient possible d'interagir avec l'appli. Il est important que ce délai soit aussi court que possible pour éviter la frustration de l'utilisateur. Si vous signalez correctement le TTFD, vous indiquez au système que le code exécuté sur le chemin du TTFD fait partie du démarrage de l'application. Par conséquent, il y a davantage de chances pour que le système place ce code dans le profil.

Pour que votre appli réponde bien, le TTID et le TTFD doivent être les plus courts possible.

Le système peut détecter le TTID, l'afficher dans Logcat et le signaler dans le cadre des analyses comparatives de départ. Toutefois, le système ne peut pas déterminer le TTFD. De plus, il est de la responsabilité de l'appli de signaler lorsqu'elle atteint un état interactif entièrement dessiné. Pour ce faire, appelez reportFullyDrawn() ou ReportDrawn si vous utilisez Jetpack Compose. Si vous avez plusieurs tâches en arrière-plan que vous devez toutes effectuer avant que l'appli soit considérée comme entièrement dessinée, vous pouvez utiliser FullyDrawnReporter, tel que décrit dans la section Améliorer la précision du temps de démarrage.

Profils de bibliothèque et profils personnalisés

Lors de l'analyse comparative de l'impact des profils, il peut être difficile de séparer les avantages des profils de votre application des profils fournis par des bibliothèques, telles que les bibliothèques Jetpack. Lorsque vous compilez votre APK, le plug-in Android Gradle ajoute tous les profils dans les dépendances de bibliothèque, ainsi que votre profil personnalisé. Cette approche est utile pour optimiser les performances globales et est recommandée pour vos builds. Toutefois, il est difficile de mesurer les gains de performances supplémentaires générés par votre profil personnalisé.

Un moyen rapide de voir manuellement l'optimisation supplémentaire fournie par votre profil personnalisé consiste à le supprimer et à exécuter vos analyses comparatives. Ensuite, remplacez-le et exécutez à nouveau vos analyses comparatives. La comparaison des deux vous montrera les optimisations fournies uniquement par les profils de bibliothèque, ainsi que par les profils de bibliothèque et votre profil personnalisé.

Vous pouvez automatiquement comparer les profils en créant une variante de compilation qui ne contient que les profils de bibliothèque, et non votre profil personnalisé. Comparez les analyses comparatives de cette variante à la variante de version qui contient à la fois les profils de bibliothèque et vos profils personnalisés. L'exemple suivant montre comment configurer la variante qui n'inclut que des profils de bibliothèque. Ajoutez une variante nommée releaseWithoutCustomProfile à votre module de client de profil, qui est généralement votre module d'application:

Kotlin

android {
  ...
  buildTypes {
    ...
    // Release build with only library profiles.
    create("releaseWithoutCustomProfile") {
      initWith(release)
    }
    ...
  }
  ...
}
...
dependencies {
  ...
  // Remove the baselineProfile dependency.
  // baselineProfile(project(":baselineprofile"))
}

baselineProfile {
  variants {
    create("release") {
      from(project(":baselineprofile"))
    }
  }
}

Groovy

android {
  ...
  buildTypes {
    ...
    // Release build with only library profiles.
    releaseWithoutCustomProfile {
      initWith(release)
    }
    ...
  }
  ...
}
...
dependencies {
  ...
  // Remove the baselineProfile dependency.
  // baselineProfile ':baselineprofile"'
}

baselineProfile {
  variants {
    release {
      from(project(":baselineprofile"))
    }
  }
}

L'exemple de code précédent supprime la dépendance baselineProfile de toutes les variantes et l'applique de manière sélective uniquement à la variante release. Il peut sembler contre-intuitif que l'ajout des profils de bibliothèque se poursuit lorsque la dépendance au module du producteur de profil est supprimée. Toutefois, ce module ne sert qu'à générer votre profil personnalisé. Le plug-in Android Gradle est toujours en cours d'exécution pour toutes les variantes et doit inclure les profils de bibliothèque.

Vous devez également ajouter la nouvelle variante au module du générateur de profils. Dans cet exemple, le module producteur est nommé :baselineprofile.

Kotlin

android {
  ...
    buildTypes {
      ...
      // Release build with only library profiles.
      create("releaseWithoutCustomProfile") {}
      ...
    }
  ...
}

Groovy

android {
  ...
    buildTypes {
      ...
      // Release build with only library profiles.
      releaseWithoutCustomProfile {}
      ...
    }
  ...
}

Pour effectuer une analyse comparative uniquement avec les profils de bibliothèque, exécutez la commande suivante:

./gradlew :baselineprofile:connectedBenchmarkReleaseWithoutCustomProfileAndroidTest

Pour effectuer une analyse comparative avec les profils de bibliothèque et votre profil personnalisé, exécutez la commande suivante:

./gradlew :baselineprofile:connectedBenchmarkReleaseAndroidTest

L'exécution du code précédent sur l'application exemple Macrobenchmark montre qu'il existe une différence de performances entre les deux variantes. Avec uniquement les profils de bibliothèque, le benchmark chaleureux startupCompose affiche les résultats suivants:

SmallListStartupBenchmark_startupCompose[mode=COLD]
timeToInitialDisplayMs   min  70.8,   median  79.1,   max 126.0
Traces: Iteration 0 1 2 3 4 5 6 7 8 9

Il existe des profils de bibliothèque dans de nombreuses bibliothèques Jetpack Compose. Il suffit donc d'utiliser le plug-in Baseline Profile Gradle pour optimiser les performances. Toutefois, il existe des optimisations supplémentaires lorsque vous utilisez le profil personnalisé:

SmallListStartupBenchmark_startupCompose[mode=COLD]
timeToInitialDisplayMs   min 57.9,   median 73.5,   max 92.3
Traces: Iteration 0 1 2 3 4 5 6 7 8 9

Éviter le démarrage de l'application lié aux E/S

Si votre appli effectue de nombreux appels d'E/S ou appels réseau au démarrage, cela peut affecter son temps de démarrage et la précision de votre analyse comparative de départ. Ces appels lourds peuvent s'étendre sur des durées indéterminées, qui peuvent varier dans le temps, et même entre les itérations d'une même analyse comparative. Les appels d'E/S sont généralement meilleurs que les appels réseau, car ces derniers peuvent être affectés par des facteurs externes à l'appareil et sur l'appareil lui-même. Évitez les appels réseau au démarrage. Lorsqu'il s'avère inévitable d'avoir recours à l'une ou l'autre de ces options, privilégiez les E/S.

Nous vous recommandons de faire en sorte que l'architecture de votre appli prenne en charge le démarrage de l'appli sans appels réseau ou appels d'E/S, même si ce n'est utile que lors de l'analyse comparative de départ. Cela permet de garantir la variabilité la plus faible possible entre les différentes itérations de vos analyses comparatives.

Si votre application utilise Hilt, vous pouvez fournir de fausses implémentations liées aux E/S lors de l'analyse comparative dans Microbenchmark et Hilt.

Recouvrir tous les parcours utilisateur importants

Il est important de recouvrir avec précision tous les parcours utilisateur importants lors de la génération de profils de référence. Les parcours utilisateur non couverts ne seront pas améliorés par les profils de référence. Les profils de référence les plus efficaces incluent tous les parcours utilisateur courants de démarrage ainsi que les parcours utilisateur dans l'application sensibles aux performances, tels que les listes déroulantes.