Optymalizacja oparta na profilu to znana technika optymalizacji przez kompilatory. W PGO kompilator używa profili środowiska wykonawczego z wykonań programu do podejmowania optymalnych wyborów dotyczących wbudowania i układu kodu. Zwiększa to wydajność i zmniejsza rozmiar kodu.
Usługę PGO można wdrożyć w aplikacji lub bibliotece w ten sposób: 1. Określ reprezentatywny zbiór zadań. 2. Zbieraj profile. 3. używać profili w kompilacji wersji.
Krok 1. Zidentyfikuj reprezentatywne zadanie
Najpierw określ wartość referencyjną lub zbiór zadań reprezentatywnych dla Twojej aplikacji. Jest to kluczowy krok, ponieważ profile zbierane przez zadanie identyfikują w kodzie obszary gorące i zimne. Podczas korzystania z profili kompilator przeprowadza agresywne optymalizacje i wbudowuje w gorących regionach. Kompilator może też zmniejszyć rozmiar kodu w zimnych regionach, obniżając jednocześnie wydajność.
Określenie dobrego zbioru zadań jest też korzystne do śledzenia ogólnej wydajności.
Krok 2. Zbierz profile
Zbieranie profili obejmuje 3 kroki: – tworzenie natywnego kodu za pomocą narzędzi, – uruchomienie aplikacji z instrumentacją na urządzeniu i generowanie profili, – scalanie/przetwarzanie profili na hoście.
Utwórz modelowaną kompilację
Profile są gromadzone przez uruchomienie zadania z kroku 1 w instruowanej kompilacji aplikacji. Aby wygenerować kompilację instrumentalną, dodaj -fprofile-generate
do flag kompilatora i łącznika. Ta flaga powinna być kontrolowana przez oddzielną zmienną kompilacji, ponieważ jest ona niepotrzebna podczas kompilacji domyślnej.
Wygeneruj profile
Następnie uruchom aplikację z instrumentacją na urządzeniu i wygeneruj profile.
Profile są zbierane w pamięci przy uruchamianiu instrumentowanego pliku binarnego i zapisywane w pliku przy zamykaniu. Jednak funkcje zarejestrowane w atexit
nie są wywoływane w aplikacji na Androida – aplikacja po prostu zostaje zamknięta.
Aplikacja lub zadanie musi wykonać dodatkową pracę, aby ustawić ścieżkę do pliku profilu, a następnie jawnie aktywować zapis profilu.
- Aby ustawić ścieżkę pliku profilu, wywołaj
__llvm_profile_set_filename(PROFILE_DIR "/default-%m.profraw
.%m
jest przydatny, gdy masz wiele bibliotek udostępnionych. %m rozwija się do niepowtarzalnego podpisu modułu dla danej biblioteki, co powoduje utworzenie osobnego profilu dla każdej biblioteki. Inne przydatne specyfikatory wzorców znajdziesz tutaj. PROFILE_DIR to katalog, który można zapisywać z aplikacji. Zobacz prezentację, jak wykryć ten katalog w czasie działania. - Aby jawnie aktywować zapis w profilu, wywołaj funkcję
__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;
}
Uwaga: wygenerowanie pliku profilu jest prostsze, jeśli zadanie jest samodzielnym plikiem binarnym. Wystarczy, że ustawisz zmienną środowiskową LLVM_PROFILE_FILE
na %t/default-%m.profraw
przed uruchomieniem pliku binarnego.
Profile procesu końcowego
Pliki profili mają format .profraw. Należy je najpierw pobrać z urządzenia za pomocą adb pull
. Po pobraniu użyj narzędzia llvm-profdata
w pakiecie NDK, aby przekonwertować dane z .profraw
na .profdata
i przekazać je do kompilatora.
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-profdata \
merge --output=pgo_profile.profdata \
<list-of-profraw-files>
Aby uniknąć niezgodności wersji formatów plików profilu, użyj elementów llvm-profdata
i clang
z tej samej wersji NDK.
Krok 3. Użyj profili do utworzenia aplikacji
Użyj profilu z poprzedniego kroku podczas kompilacji wersji swojej aplikacji, przekazując -fprofile-use=<>.profdata
do kompilatora i tagu łączącego. Profili można używać nawet w trakcie rozwoju kodu – kompilator Clang może tolerować niewielkie rozbieżności między źródłem a profilami.
Uwaga: w przypadku większości bibliotek profile są wspólne w różnych architekturach. Na przykład profile wygenerowane z kompilacji biblioteki Arm64 można używać we wszystkich architekturach. Jeśli jednak w bibliotece istnieją ścieżki kodu związane z konkretną architekturą (arm a x86 lub 32-bitowe lub 64-bitowe), do każdej takiej konfiguracji należy używać osobnych profili.
Podsumowanie
https://github.com/DanAlbert/ndk-samples/tree/pgo/pgo zawiera kompleksową prezentację korzystania z usługi PGO z aplikacji. Zawiera ona dodatkowe informacje, które zostały przejrzane w tym dokumencie.
- Reguły kompilacji CMake pokazują, jak skonfigurować zmienną CMake, która tworzy kod natywny z instrumentacją. Jeśli zmienna kompilacji nie jest ustawiona, kod natywny jest optymalizowany przy użyciu wcześniej wygenerowanych profili PGO.
- W kompilacji z instrumentacją pgodemo.cpp zapisuje, że profile są wykonywaniem zadań.
- Lokalizacja profili z możliwością zapisu jest tworzona w czasie działania w MainActivity.kt za pomocą
applicationContext.cacheDir.toString()
. - Aby pobrać profile z urządzenia bez wymagania
adb root
, skorzystaj z przepisu naadb
, który znajdziesz tutaj.