Die profilgestützte Optimierung (PGO) ist eine bekannte Compileroptimierungstechnik. In PGO werden Laufzeitprofile aus den Ausführungen eines Programms vom Compiler verwendet, um optimale Entscheidungen in Bezug auf Inline und Codelayout zu treffen. Dies führt zu einer verbesserten Leistung und einer reduzierten Codegröße.
So können Sie PGO in Ihrer Anwendung oder Bibliothek bereitstellen: 1. Identifizieren Sie eine repräsentative Arbeitslast. 2. Profile erfassen. 3. Verwenden Sie die Profile in einem Release-Build.
Schritt 1: Einen repräsentativen Arbeitslast ermitteln
Bestimmen Sie zuerst eine repräsentative Benchmark oder Arbeitslast für Ihre Anwendung. Dies ist ein wichtiger Schritt, da die aus der Arbeitslast erfassten Profile die heißen und kalten Regionen im Code identifizieren. Bei der Verwendung der Profile führt der Compiler aggressive Optimierungen und Inline-Vorgänge in den heißen Regionen durch. Der Compiler kann auch die Codegröße von selten genutzten Regionen reduzieren und gleichzeitig Leistungsabstriche machen.
Die Identifizierung einer guten Arbeitslast ist auch hilfreich, um die Leistung im Allgemeinen zu verfolgen.
Schritt 2: Profile erfassen
Die Profilerfassung umfasst drei Schritte: Erstellen von nativem Code mit Instrumentierung, Ausführen der instrumentierten Anwendung auf dem Gerät und Generieren von Profilen sowie Zusammenführen/Nachverarbeiten der Profile auf dem Host.
Instrumentierten Build erstellen
Die Profile werden erfasst, indem die Arbeitslast aus Schritt 1 für einen instrumentierten Build der Anwendung ausgeführt wird. Zum Generieren eines instrumentierten Builds fügen Sie den Compiler- und Verknüpfungs-Flags -fprofile-generate
hinzu. Dieses Flag sollte über eine separate Build-Variable gesteuert werden, da es bei einem Standard-Build nicht benötigt wird.
Profile generieren
Führen Sie als Nächstes die instrumentierte App auf dem Gerät aus und generieren Sie Profile.
Profile werden im Arbeitsspeicher erfasst, wenn das instrumentierte Binärprogramm ausgeführt wird, und beim Beenden in eine Datei geschrieben. Mit atexit
registrierte Funktionen werden jedoch nicht in einer Android-App aufgerufen. Die App wird einfach beendet.
Die Anwendung/Arbeitslast muss zusätzliche Arbeit leisten, um einen Pfad für die Profildatei festzulegen und dann explizit einen Profilschreibvorgang auszulösen.
- Rufen Sie
__llvm_profile_set_filename(PROFILE_DIR "/default-%m.profraw
auf, um den Pfad der Profildatei festzulegen.%m
ist nützlich, wenn es mehrere gemeinsam genutzte Bibliotheken gibt.%m
wird in eine eindeutige Modulsignatur für diese Bibliothek erweitert, was zu einem separaten Profil pro Bibliothek führt. Weitere nützliche Musterspezifizierer finden Sie hier.PROFILE_DIR
ist ein Verzeichnis, das von der App aus beschreibbar ist. In der Demo wird gezeigt, wie dieses Verzeichnis zur Laufzeit erkannt wird. - Rufen Sie die Funktion
__llvm_profile_write_file
auf, um einen Profilschreibvorgang explizit auszulösen.
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;
}
Hinweis: Das Erstellen der Profildatei ist einfacher, wenn es sich bei der Arbeitslast um ein eigenständiges Binärprogramm handelt. Legen Sie dazu einfach die Umgebungsvariable LLVM_PROFILE_FILE
auf %t/default-%m.profraw
fest, bevor Sie das Binärprogramm ausführen.
Nachbearbeitung von Profilen
Die Profildateien liegen im .profraw-Format vor. Sie müssen zuerst mit adb pull
vom Gerät abgerufen werden. Verwenden Sie nach dem Abruf das Dienstprogramm llvm-profdata
im NDK, um .profraw
in .profdata
zu konvertieren, das dann an den Compiler übergeben werden kann.
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-profdata \
merge --output=pgo_profile.profdata \
<list-of-profraw-files>
Verwenden Sie llvm-profdata
und clang
aus demselben NDK-Release, um Versionsabweichungen der Profildateiformate zu vermeiden.
Schritt 3: Profile zum Erstellen der Anwendung verwenden
Sie können das Profil aus dem vorherigen Schritt während eines Release-Builds Ihrer Anwendung verwenden. Übergeben Sie dazu -fprofile-use=<>.profdata
an den Compiler und die Verknüpfung. Die Profile können auch dann verwendet werden, wenn sich der Code weiterentwickelt. Der Clang-Compiler kann leichte Abweichungen zwischen der Quelle und den Profilen tolerieren.
Hinweis: Bei den meisten Bibliotheken sind die Profile für alle Architekturen gleich. Beispielsweise können aus dem arm64-Build der Bibliothek generierte Profile für alle Architekturen verwendet werden. Wenn die Bibliothek jedoch Architektur-spezifische Codepfade enthält (ARM oder x86 oder 32-Bit oder 64-Bit), sollten für jede solche Konfiguration separate Profile verwendet werden.
Zusammenfassung
Unter https://github.com/DanAlbert/ndk-samples/tree/pgo/pgo finden Sie eine End-to-End-Demo zur Verwendung von PGO in einer App. Dort werden zusätzliche Details beschrieben, die in diesem Dokument nur kurz erwähnt wurden.
- In den CMake-Buildregeln wird gezeigt, wie Sie eine CMake-Variable einrichten, mit der nativer Code mit Instrumentierung erstellt wird. Wenn die Build-Variable nicht festgelegt ist, wird nativer Code mithilfe von zuvor generierten PGO-Profilen optimiert.
- In einem instrumentierten Build schreibt pgodemo.cpp die Profile bei der Ausführung der Arbeitslast.
- Ein beschreibbarer Speicherort für die Profile wird zur Laufzeit in MainActivity.kt mit
applicationContext.cacheDir.toString()
abgerufen. - Wenn Sie Profile vom Gerät abrufen möchten, ohne dass
adb root
erforderlich ist, verwenden Sie diesesadb
-Schema.