Fichiers d'ordre

Le fichier d'ordre est une technique récente d'optimisation de l'éditeur de liens. Ces fichiers d'ordre sont des fichiers texte contenant des symboles représentant des fonctions. Les éditeurs de liens comme lld utilisent des fichiers d'ordre pour mettre en page les fonctions dans un ordre spécifique. Ces binaires ou bibliothèques avec des symboles ordonnés réduisent les erreurs de page et améliorent le temps de lancement d'un programme en raison du chargement efficace des symboles au démarrage à froid du programme.

Pour ajouter des fonctionnalités de fichier d'ordre à votre application, suivez ces trois étapes :

  1. Générer des profils et des fichiers de mappage
  2. Créer un fichier d'ordre à partir des profils et du fichier de mappage
  3. Utilisez le fichier d'ordre pendant la compilation pour mettre en page les symboles

Générer le fichier d'ordre

Pour générer un fichier d'ordre, vous devez suivre trois étapes :

  1. Créer une version instrumentée de l'appli qui écrit le fichier d'ordre
  2. Exécuter l'appli pour générer les profils
  3. Post-traiter les profils et le fichier de mappage

Créer une version instrumentée

Les profils sont générés en exécutant une version instrumentée de l'application. Une version instrumentée nécessite l'ajout de -forder-file-instrumentation aux indicateurs de compilation et de l'éditeur de liens, avec un ajout strict de -mllvm -orderfile-write-mapping=<filename>-mapping.txt aux indicateurs de compilation. L'indicateur d'instrumentation active l'instrumentation des fichiers d'ordre pour le profilage et charge la bibliothèque spécifique nécessaire au profilage. En revanche, l'indicateur de mappage génère simplement le fichier de mappage qui affiche le hachage MD5 pour chaque fonction dans le binaire ou la bibliothèque.

Veillez également à transmettre tout indicateur d'optimisation, à l'exception de -O0, car l'indicateur d'instrumentation et l'indicateur de mappage nécessitent tous deux un indicateur. Si aucun indicateur d'optimisation n'est transmis, le fichier de mappage n'est pas généré, et la version instrumentée peut générer des hachages incorrects dans le fichier de profil.

ndk-build

Assurez-vous d'utiliser APP_OPTIM=release pour que ndk-build utilise un mode d'optimisation autre que -O0. La compilation avec AGP est automatique pour les builds.

LOCAL_CFLAGS += \
    -forder-file-instrumentation \
    -mllvm -orderfile-write-mapping=mapping.txt \

LOCAL_LDFLAGS += -forder-file-instrumentation

CMake

Veillez à utiliser un CMAKE_BUILD_TYPE autre que Debug afin que CMake utilise un mode d'optimisation autre que -O0. La compilation avec AGP est automatique pour les builds.

target_compile_options(orderfiledemo PRIVATE
    -forder-file-instrumentation
    -mllvm -orderfile-write-mapping=mapping.txt
)
target_link_options(orderfiledemo PRIVATE -forder-file-instrumentation)

Autres systèmes de compilation

Compilez le code avec -forder-file-instrumentation -O1 -mllvm -orderfile-write-mapping=mapping.txt.

-O1 n'est pas obligatoire, mais n'utilisez pas -O0.

Omettez -mllvm -orderfile-write-mapping=mapping.txt lors de l'association.

Ces indicateurs ne sont pas nécessaires pour un build, qui doit donc être contrôlé par une variante de compilation. Pour plus de simplicité, vous pouvez configurer tout cela dans le fichier CMakeLists.txt, comme indiqué dans notre exemple.

Créer une bibliothèque de fichiers d'ordre

En plus des indicateurs, le fichier de profil doit être configuré et le binaire instrumenté doit déclencher explicitement une écriture de profil lors de son exécution.

  • Appelez __llvm_profile_set_filename(PROFILE_DIR "/<filename>-%m.profraw") pour configurer le chemin d'accès au profil. Bien que l'argument transmis soit <filename>-%m.profraw, le fichier de profil est enregistré sous <filename>-%m.profraw.order. Assurez-vous que PROFILE_DIR est accessible en écriture par l'appli et que vous avez accès au répertoire.
    • Étant donné que de nombreuses bibliothèques partagées sont profilées, %m est utile, car il se transforme en signature de module unique pour la bibliothèque, ce qui génère un profil distinct par bibliothèque. Pour plus de spécificateurs de modèle, consultez cette page.
  • Appelez __llvm_profile_initialize_file() pour configurer le fichier de profil
  • Appelez __llvm_orderfile_dump() pour écrire explicitement dans le fichier de profil.

Les profils sont collectés en mémoire, et la fonction de vidage les écrit dans le fichier. Vous devez vous assurer que la fonction de vidage est appelée à la fin du démarrage afin que votre fichier de profil contienne tous les symboles jusqu'à la fin du démarrage.

extern "C" {
extern int __llvm_profile_set_filename(const char*);
extern int __llvm_profile_initialize_file(void);
extern int __llvm_orderfile_dump(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_initialize_file();
  __llvm_orderfile_dump();
  return;
}

Exécuter le build pour les profils

Exécutez l'appli instrumentée sur un appareil physique ou virtuel pour générer les profils. Vous pouvez extraire les fichiers de profil à l'aide d'adb pull.

adb shell "run-as <package-name> sh -c 'cat /data/user/0/<package-name>/cache/default-%m.profraw.order' | cat > /data/local/tmp/default-%m.profraw.order"
adb pull /data/local/tmp/default-%m.profraw.order .

Comme indiqué précédemment, assurez-vous que vous pouvez accéder au dossier contenant le fichier de profil écrit. S'il s'agit d'un appareil virtuel, nous vous conseillons d'éviter les émulateurs avec le Play Store, car vous n'avez pas accès à de nombreux dossiers.

Post-traiter le profil et le fichier de mappage

Lorsque vous obtenez les profils, vous devez trouver le fichier de mappage et convertir chaque profil au format hexadécimal. En règle générale, vous pouvez trouver le fichier de mappage dans le dossier de compilation de l'appli. Une fois en possession des fichiers de mappage et de profil, vous pouvez utiliser notre script pour prendre un fichier de profil et le fichier de mappage correspondant pour générer un fichier d'ordre.

Linux/Mac/ChromeOS

hexdump -C default-%m.profraw.order > default-%m.prof
python3 create_orderfile.py --profile-file default-%m.prof --mapping-file <filename>-mapping.txt

Windows

certutil -f -encodeHex default-%m.profraw.order default-%m.prof
python3 create_orderfile.py --profile-file default-%m.prof --mapping-file <filename>-mapping.txt

Pour en savoir plus sur le script, consultez ce fichier README.

Utiliser le fichier d'ordre pour créer une application

Après avoir généré un fichier d'ordre, vous devez supprimer les indicateurs précédents ainsi que les fonctions des fichiers d'ordre, car ils sont uniquement destinés aux étapes de génération. Il vous suffit de transmettre -Wl,--symbol-ordering-file=<filename>.orderfile aux indicateurs de compilation et de l'éditeur de liens. Parfois, les symboles sont introuvables ou ne peuvent pas se déplacer et génèrent des avertissements afin que vous puissiez transmettre -Wl,--no-warn-symbol-ordering pour les supprimer.

ndk-build

LOCAL_CFLAGS += \
    -Wl,--symbol-ordering-file=<filename>.orderfile \
    -Wl,--no-warn-symbol-ordering \

LOCAL_LDFLAGS += \
    -Wl,--symbol-ordering-file=<filename>.orderfile \
    -Wl,--no-warn-symbol-ordering \

CMake

target_compile_options(orderfiledemo PRIVATE
    -Wl,--symbol-ordering-file=<filename>.orderfile
    -Wl,--no-warn-symbol-ordering
)
target_link_options(orderfiledemo PRIVATE
    -Wl,--symbol-ordering-file=<filename>.orderfile
    -Wl,--no-warn-symbol-ordering
)

Autres systèmes de compilation

Compilez le code avec -Wl,--symbol-ordering-file=<filename>.orderfile -Wl,--no-warn-symbol-ordering.

Pour en savoir plus, consultez l'exemple de fichier d'ordre.

Détails de l'implémentation du fichier d'ordre

Il existe de nombreuses façons de générer des fichiers d'ordre et de les utiliser pour créer. Le NDK utilise la méthode LLVM. Il est donc le plus utile pour vos bibliothèques partagées C ou C++ que pour l'appli Java ou Kotlin. Clang prend chaque nom de fonction (symbole) et crée un hachage MD5 de celle-ci, et renvoie cette relation sur un fichier de mappage. Le hachage MD5 d'une fonction est écrit dans le fichier de profil (format .profraw) lors de la première exécution de la fonction. Les exécutions ultérieures de la fonction n'écrivent pas son hachage MD5 dans le fichier de profil, car elles souhaitent éviter les doublons. Par conséquent, seule la première exécution de la fonction est enregistrée dans le fichier d'ordre. En parcourant le fichier de profil et le fichier de mappage, vous pouvez prendre chaque hachage MD5 et le remplacer par la fonction correspondante, puis obtenir un fichier d'ordre.

Vous trouverez des exemples de fichiers de profil au format hexadécimal et de fichiers de mappage sous les noms example.prof et example-mapping.txt, respectivement.