Die profilgestützte Optimierung (PGO) ist eine bekannte Compiler-Optimierungstechnik. In PGO verwendet der Compiler Laufzeitprofile aus den Ausführungen eines Programms, um optimale Entscheidungen bezüglich Inline- und Codelayout zu treffen. Dies führt zu einer verbesserten Leistung und einer geringeren Codegröße.
PGO kann mit den folgenden Schritten in Ihrer Anwendung oder Bibliothek bereitgestellt werden: 1. Eine repräsentative Arbeitslast identifizieren. 2. Profile erfassen. 3. Verwenden Sie die Profile in einem Release-Build.
Schritt 1: Repräsentative Arbeitslast ermitteln
Identifizieren Sie zuerst eine repräsentative Benchmark oder Arbeitslast für Ihre Anwendung. Dies ist ein entscheidender Schritt, da die aus der Arbeitslast erfassten Profile die heißen und kalten Regionen im Code identifizieren. Wenn die Profile verwendet werden, führt der Compiler aggressive Optimierungen und das Inlineing in den heißen Regionen durch. Der Compiler kann auch die Codegröße von kalten Regionen reduzieren, ohne die Leistung zu beeinträchtigen.
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, Generieren von Profilen und Zusammenführen/Nachverarbeiten der Profile auf dem Host.
Instrumentierten Build erstellen
Die Profile werden erfasst, indem die Arbeitslast aus Schritt 1 auf einem instrumentierten Build der Anwendung ausgeführt wird. Fügen Sie den Compiler- und Verknüpfungs-Flags -fprofile-generate
hinzu, um einen instrumentierten Build zu generieren. Dieses Flag sollte über eine separate Build-Variable gesteuert werden, da es während eines Standard-Builds nicht benötigt wird.
Profile erstellen
Führen Sie als Nächstes die instrumentierte App auf dem Gerät aus und generieren Sie Profile.
Profile werden im Arbeitsspeicher erfasst, wenn die instrumentierte Binärdatei ausgeführt und beim Beenden in eine Datei geschrieben wird. 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 Profildateipfad festzulegen.%m
ist nützlich, wenn mehrere gemeinsam genutzte Bibliotheken vorhanden sind. %m wird zu einer eindeutigen 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, in das die Anwendung geschrieben werden kann. Informationen zum Erkennen dieses Verzeichnisses zur Laufzeit finden Sie in der Demo. - Wenn Sie einen Profilschreibvorgang explizit auslösen möchten, rufen Sie die Funktion
__llvm_profile_write_file
auf.
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: Wenn die Arbeitslast ein eigenständiges Binärprogramm ist, ist das Generieren der Profildatei einfacher. Legen Sie dazu einfach die Umgebungsvariable LLVM_PROFILE_FILE
auf %t/default-%m.profraw
fest, bevor Sie die Binärdatei ausführen.
Nachbearbeitung von Profilen
Die Profildateien haben das .profraw-Format. 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 und dann an den Compiler zu übergeben.
$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 Versionsunterschiede zwischen den Profildateiformaten zu vermeiden.
Schritt 3: Profile zum Erstellen einer Anwendung verwenden
Verwenden Sie das Profil aus dem vorherigen Schritt während eines Release-Builds Ihrer Anwendung. Dazu übergeben Sie -fprofile-use=<>.profdata
an den Compiler und den Linker. Die Profile können auch dann verwendet werden, wenn sich der Code weiterentwickelt. Der Clang-Compiler kann geringfügige Abweichungen zwischen der Quelle und den Profilen tolerieren.
Hinweis: Im Allgemeinen sind die Profile bei den meisten Bibliotheken in allen Architekturen gleich. Beispielsweise können Profile, die aus dem arm64-Build der Bibliothek generiert wurden, für alle Architekturen verwendet werden. Wenn es in der Bibliothek architekturspezifische Codepfade gibt (Verzweigung vs. x86 oder 32-Bit bzw. 64-Bit), sollten für jede solche Konfiguration separate Profile verwendet werden.
Zusammenfassung
https://github.com/DanAlbert/ndk-sample/tree/pgo/pgo zeigt eine End-to-End-Demo zur Verwendung von PGO in einer App. Es enthält zusätzliche Details, die in diesem Dokument überfliegen.
- In den Build-Regeln für CMake wird gezeigt, wie Sie eine CMake-Variable einrichten, die nativen Code mit Instrumentierung erstellt. 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 als Arbeitslastausführung.
- Ein beschreibbarer Speicherort für die Profile wird zur Laufzeit in MainActivity.kt mit
applicationContext.cacheDir.toString()
abgerufen. - Um Profile vom Gerät abzurufen, ohne dass
adb root
erforderlich ist, verwenden Sie dasadb
-Schema.