Optimisation guidée par le profil

L'optimisation guidée par profil (PGO) est une technique bien connue d'optimisation du compilateur. Dans PGO, le compilateur utilise les profils issus des exécutions d'un programme pour faire des choix optimaux concernant l'intégration et la mise en page du code. Cela permet d'améliorer les performances et de réduire la taille du code.

Pour déployer PGO dans votre application ou votre bibliothèque, procédez comme suit : 1. Identifiez une charge de travail représentative. 2. Collectez les profils. 3. Utilisez les profils dans un build.

Étape 1 : Identifier une charge de travail représentative

Commencez par identifier une référence ou une charge de travail représentative de votre application. Cette étape est essentielle, car les profils collectés à partir de la charge de travail permettent d'identifier les zones actives et inactives dans le code. Lors de l'utilisation des profils, le compilateur effectue des optimisations agressives et intègre les zones actives. Il peut également choisir de réduire la taille du code des régions inactives tout en compromettant les performances.

Il est également utile d'identifier une charge de travail représentative pour suivre les performances en général.

Étape 2 : Collecter les profils

La collecte de profils comprend trois étapes : - Compiler le code natif avec l'instrumentation, - Exécuter l'application instrumentée sur l'appareil et générer des profils - Fusionner/post-traiter les profils sur l'hôte

Créer un build instrumenté

Les profils sont collectés en exécutant la charge de travail de l'étape 1 sur un build instrumenté de l'application. Pour générer un build instrumenté, ajoutez -fprofile-generate aux indicateurs de compilation et d'association. Cet indicateur doit être contrôlé par une variable de compilation distincte, car il n'est pas nécessaire lors d'une compilation par défaut.

Générer des profils

Exécutez ensuite l'application instrumentée sur l'appareil et générez des profils. Les profils sont collectés en mémoire lorsque le binaire instrumenté est exécuté et est écrit dans un fichier à la sortie. Cependant, les fonctions enregistrées avec atexit ne sont pas appelées dans une application Android. L'application est simplement fermée.

L'application ou la charge de travail doit définir le chemin d'accès au fichier de profil, puis déclencher explicitement une écriture de profil.

  • Pour définir le chemin d'accès au fichier de profils, appelez __llvm_profile_set_filename(PROFILE_DIR "/default-%m.profraw. %m est utile lorsqu'il existe plusieurs bibliothèques partagées. %m se convertit en une signature de module unique pour cette bibliothèque, ce qui génère un profil distinct par bibliothèque. Cliquez ici pour consulter d'autres spécificateurs de modèle utiles. PROFILE_DIR est un répertoire accessible en écriture depuis l'application. Consultez la démonstration permettant de détecter ce répertoire au moment de l'exécution.
  • Pour déclencher explicitement une écriture de profil, appelez la fonction __llvm_profile_write_file.
extern "C" {
extern int __llvm_profile_set_filename(const char*);
extern int __llvm_profile_write_file(void);
}

#define PROFILE_DIR "<location-writable-from-app>"
void workload() {
  // ...
  // run workload
  // ...

  // set path and write profiles after workload execution
  __llvm_profile_set_filename(PROFILE_DIR "/default-%m.profraw");
  __llvm_profile_write_file();
  return;
}

Remarque : La génération du fichier de profils est plus simple si la charge de travail est un binaire autonome. Dans ce cas, il suffit de définir la variable d'environnement LLVM_PROFILE_FILE sur %t/default-%m.profraw avant d'exécuter le binaire.

Post-traiter les profils

Les fichiers de profil sont au format .profraw. Ils doivent d'abord être récupérés sur l'appareil via adb pull. Après la récupération, servez-vous de l'utilitaire llvm-profdata dans le kit NDK pour convertir .profraw en .profdata, qui pourra ensuite être transmis au compilateur.

$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-profdata \
    merge --output=pgo_profile.profdata \
    <list-of-profraw-files>

Utilisez les propriétés llvm-profdata et clang de la même version de NDK pour éviter les incohérences de version entre les formats de fichiers de profils.

Étape 3 : Utiliser les profils pour créer l'application

Transmettez -fprofile-use=<>.profdata au compilateur et à l'outil d'association pour utiliser le profil de l'étape précédente lors de la compilation du build de votre application. Les profils peuvent être utilisés même lorsque le code évolue. Le compilateur Clang tolère une légère incohérence entre la source et les profils.

Remarque : En général, les profils sont communs à toutes les architectures pour la plupart des bibliothèques. Par exemple, les profils générés à partir du build arm64 de la bibliothèque sont compatibles avec toutes les architectures. Toutefois, s'il existe des chemins de code spécifiques à l'architecture dans la bibliothèque (arm par rapport à x86 ou 32 bits par rapport à 64 bits), des profils distincts sont nécessaires pour chaque configuration.

Synthèse

https://github.com/DanAlbert/ndk-samples/tree/pgo/pgo présente une démonstration complète illustrant comment utiliser PGO à partir d'une application. Il fournit des détails supplémentaires que nous n'avons pas abordés dans ce document.

  • Les règles de compilation CMake montrent comment configurer une variable CMake qui compile du code natif avec l'instrumentation. Lorsque la variable de compilation n'est pas définie, le code natif est optimisé à l'aide de profils PGO déjà générés.
  • Dans un build instrumenté, pgodemo.cpp écrit que les profils correspondent à l'exécution de la charge de travail.
  • Un emplacement accessible en écriture pour les profils est généré au moment de l'exécution dans le fichier MainActivity.kt avec applicationContext.cacheDir.toString().
  • Pour extraire des profils de l'appareil sans nécessiter adb root, utilisez la méthode adb que vous trouverez ici.