Optymalizacja z przewodnikiem (PGO) to dobrze znana technika optymalizacji kompilatora. W PGO kompilator korzysta z profilów czasu wykonywania pochodzących z wykonywań programu, aby podejmować optymalne decyzje dotyczące wstawiania i układu kodu. Spowoduje to poprawę wydajności i zmniejszenie rozmiaru kodu.
PGO można wdrożyć w aplikacji lub bibliotece, wykonując te czynności: 1. Określ reprezentatywny zbiór zadań. 2. Zbierać profile. 3. Użyj profili w wersji wydania.
Krok 1. Zidentyfikuj reprezentatywny strumień zadań
Najpierw określ reprezentatywny benchmark lub zbiór zadań dla swojej aplikacji. Jest to kluczowy krok, ponieważ profile zebrane z zadania identyfikują najbardziej aktywne i „zimne” regiony w kodzie. Gdy używasz profili, kompilator wykonuje agresywną optymalizację i wstawianie w miejscach, w których występują często wykonywane instrukcje. Kompilator może też zmniejszyć rozmiar kodu w „zimnych regionach” przy jednoczesnym obniżeniu wydajności.
Identyfikacja prawidłowego zbioru zadań pomaga też śledzić ogólną wydajność.
Krok 2. Zbierz profile
Zbieranie profili obejmuje 3 etapy: - kompilowanie kodu natywnego z użyciem instrumentacji, - uruchamianie na urządzeniu aplikacji z użyciem instrumentacji i generowanie profili, - łączenie lub post-processing profili na hoście.
Utwórz kompilację instrumentalną
Profile są zbierane przez uruchomienie zbioru zadań z etapu 1 na wersji aplikacji z instrumentacją. Aby wygenerować kompilację zinstruktowaną, dodaj -fprofile-generate do flag kompilatora i flagi łączącej. Ten parametr powinien być kontrolowany przez osobną zmienną kompilacji, ponieważ nie jest on potrzebny podczas kompilacji domyślnej.
Generowanie profili
Następnie uruchom zinstrumentowaną aplikację na urządzeniu i wygeneruj profile.
Profil jest zbierany w pamięci podczas uruchamiania skompilowanego binarnego i zapisuje się do pliku po zakończeniu działania. Jednak funkcje zarejestrowane w usłudze atexit nie są wywoływane w aplikacjach na Androida – po prostu się zamyka.
Aplikacja lub obciążenie musi wykonać dodatkowe czynności, aby ustawić ścieżkę do pliku profilu, a następnie wyraźnie wywołać zapis profilu.
- Aby ustawić ścieżkę pliku profilu, wywołaj
__llvm_profile_set_filename(PROFILE_DIR "/default-%m.profraw.%mjest przydatna, gdy masz wiele bibliotek udostępnionych.%mrozwija się do unikalnej sygnatury modułu dla danej biblioteki, co powoduje powstanie osobnego profilu dla każdej biblioteki. Inne przydatne specyfikatory wzorów znajdziesz tutaj.PROFILE_DIRto katalog, który można zapisać z aplikacji. Aby dowiedzieć się, jak wykrywać ten katalog w czasie wykonywania, zapoznaj się z demonstracją. - Aby jawnie wywołać zapis 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: generowanie pliku profilu jest prostsze, jeśli obciążenie jest samodzielnym binarnym plikiem. Przed uruchomieniem binarnego pliku ustaw zmienną środowiskową LLVM_PROFILE_FILE na %t/default-%m.profraw.
Profile przetwarzania końcowego
Pliki profilu mają format .profraw. Najpierw należy je pobrać z urządzenia za pomocą funkcji adb pull. Po pobraniu użyj w NDK narzędzia llvm-profdata, aby przekonwertować plik .profraw na .profdata, który można następnie przekazać kompilatorowi.
$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 tworzenia aplikacji
Użyj profilu z poprzedniego kroku podczas tworzenia wersji aplikacji, przekazując wartość -fprofile-use=<>.profdata do kompilatora i linkera. Profilów można używać nawet podczas ewolucji kodu – kompilator Clang może tolerować niewielkie niezgodności między źródłem a profilami.
Uwaga: w większości bibliotek profile są wspólne dla wszystkich architektur. Na przykład profile wygenerowane na podstawie wersji biblioteki arm64 mogą być używane na wszystkich architekturach. Problem polega jednak na tym, że jeśli w bibliotece są ścieżki kodu specyficzne dla danej architektury (1 z modułów x86 lub 32-bitowych lub 64-bitowych), dla każdej takiej konfiguracji należy użyć osobnego profilu.
Podsumowanie
Strona https://github.com/DanAlbert/ndk-samples/tree/pgo/pgo zawiera kompleksową demonstrację korzystania z PGO w aplikacji. Zawiera dodatkowe informacje, które zostały skrócone w tym dokumencie.
- Reguły kompilacji CMake pokazują, jak skonfigurować zmienną CMake, która kompiluje kod natywny z użyciem instrumentacji. Jeśli zmienna kompilacji nie jest ustawiona, kod natywny jest optymalizowany za pomocą wcześniej wygenerowanych profili PGO.
- W kompilacji z instrumentacją plik pgodemo.cpp zapisuje profile w ramach wykonywania zbioru zadań.
- Miejsce do zapisu profili jest uzyskiwane w czasie działania w pliku MainActivity.kt za pomocą funkcji
applicationContext.cacheDir.toString(). - Aby pobrać profile z urządzenia bez wymagania
adb root, użyjadbprzepisu tutaj.