L'ottimizzazione guidata dal profilo (PGO) è una tecnica di ottimizzazione del compilatore ben nota. In PGO, il compilatore utilizza i profili di runtime delle esecuzioni di un programma per fare scelte ottimali sull'incorporamento e sul layout del codice. Questo porta a un miglioramento delle prestazioni e a una riduzione delle dimensioni del codice.
Puoi eseguire il deployment di PGO nella tua applicazione o libreria seguendo questi passaggi: 1. Identifica un carico di lavoro rappresentativo. 2. Raccogli i profili. 3. Utilizzare i profili in una build di release.
Passaggio 1: identifica un carico di lavoro rappresentativo
Innanzitutto, identifica un benchmark o un carico di lavoro rappresentativo per la tua applicazione. Questo è un passaggio critico poiché i profili raccolti dal carico di lavoro identificano le regioni calde e fredde nel codice. Quando utilizzi i profili, il compilatore eseguirà ottimizzazioni aggressive e l'inserimento incorporato nelle regioni più attive. Il compilatore può anche scegliere di ridurre le dimensioni del codice delle regioni a freddo, a scapito delle prestazioni.
Identificare un buon carico di lavoro è utile anche per tenere traccia delle prestazioni in generale.
Passaggio 2: raccogli i profili
La raccolta dei profili prevede tre passaggi: - creazione di codice nativo con la strumentazione - esecuzione dell'app instrumentata sul dispositivo e generazione di profili e - unione/post-elaborazione dei profili sull'host.
Crea build strumentale
I profili vengono raccolti eseguendo il carico di lavoro dal passaggio 1 su una build dell'applicazione strumentata. Per generare una build con strumentazione, aggiungi
-fprofile-generate
ai flag del compilatore e del linker. Questo flag dovrebbe essere controllato da una variabile di build separata poiché non è necessario durante una build predefinita.
Genera profili
Quindi, esegui l'app con gli strumenti sul dispositivo e genera profili.
I profili vengono raccolti in memoria quando viene eseguito il programma binario strumentato e vengono scritti in un file all'uscita. Tuttavia, le funzioni registrate con atexit
non vengono
chiamate in un'app Android, l'app viene semplicemente interrotta.
L'applicazione/il carico di lavoro deve svolgere un ulteriore lavoro per impostare un percorso per il file del profilo e quindi attivare esplicitamente la scrittura del profilo.
- Per impostare il percorso del file del profilo, chiama
__llvm_profile_set_filename(PROFILE_DIR "/default-%m.profraw
.%m
è utile quando sono presenti più librerie condivise. %m" si espande in una firma del modulo univoca per quella libreria, il che genera un profilo separato per ogni libreria. Consulta qui per altri utili identificatori di pattern. PROFILE_DIR è una directory a cui è possibile scrivere dall'app. Consulta la demo per il rilevamento di questa directory in fase di runtime. - Per attivare esplicitamente la scrittura di un profilo, chiama la funzione
__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;
}
Nota: la generazione del file di profilo è più semplice se il carico di lavoro è un programma binario autonomo. Devi solo impostare la variabile di ambiente LLVM_PROFILE_FILE
su %t/default-%m.profraw
prima di eseguire il programma binario.
Profili post-elaborazione
I file del profilo sono in formato .profraw. Devono essere prima recuperate dal
dispositivo utilizzando adb pull
. Dopo il recupero, utilizza l'utilità llvm-profdata
nell'NDK per convertire da .profraw
a .profdata
, che possono poi essere passati al compilatore.
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-profdata \
merge --output=pgo_profile.profdata \
<list-of-profraw-files>
Utilizza llvm-profdata
e clang
della stessa release NDK per evitare che la versione
non corrisponda ai formati file del profilo.
Passaggio 3: utilizza i profili per creare l'applicazione
Utilizza il profilo del passaggio precedente durante una build di release della tua applicazione passando -fprofile-use=<>.profdata
al compilatore e al linker. I profili possono essere utilizzati anche con l'evolversi del codice: il compilatore Clang può tollerare una leggera mancata corrispondenza tra l'origine e i profili.
Nota: in generale, per la maggior parte delle librerie, i profili sono comuni a tutte le architetture. Ad esempio, i profili generati dalla build arm64 della libreria possono essere utilizzati per tutte le architetture. Il problema è che, se nella libreria sono presenti percorsi di codice specifici per l'architettura (arm o x86 o a 32 bit o 64 bit), per ciascuna configurazione devono essere utilizzati profili separati.
Riassumendo
https://github.com/DanAlbert/ndk-samples/tree/pgo/pgo mostra una demo end-to-end dell'utilizzo di PGO da un'app. Fornisce ulteriori dettagli che sono stati esaminati in questo documento.
- Le regole di build di CMake mostrano come configurare una variabile CMake che crei codice nativo con la strumentazione. Se la variabile di build non è impostata, il codice nativo viene ottimizzato utilizzando i profili PGO generati in precedenza.
- In una build con strumentazione, pgodemo.cpp scrive che i profili sono in esecuzione del carico di lavoro.
- Una posizione scrivibile per i profili si ottiene al runtime in
MainActivity.kt
utilizzando
applicationContext.cacheDir.toString()
. - Per estrarre i profili dal dispositivo senza richiedere
adb root
, usa la ricettaadb
qui.