L'ottimizzazione basata sul profilo (PGO) è una tecnica di ottimizzazione del compilatore ben nota. In PGO, i profili di runtime delle esecuzioni di un programma vengono utilizzati dal compilatore per fare scelte ottimali sull'inserimento in linea e sul layout del codice. Ciò si traduce in un miglioramento delle prestazioni e in una riduzione delle dimensioni del codice.
Per eseguire il deployment di PGO nell'applicazione o nella libreria, segui questi passaggi: 1. Identifica un carico di lavoro rappresentativo. 2. Raccogli i profili. 3. Utilizzare i profili in una release build.
Passaggio 1: identifica un carico di lavoro rappresentativo
Per prima cosa, identifica un benchmark o un carico di lavoro rappresentativo per la tua applicazione. Si tratta di un passaggio fondamentale, in quanto i profili raccolti dal carico di lavoro identificano le regioni calde e fredde nel codice. Quando si utilizzano i profili, il compilatore eseguirà ottimizzazioni aggressive e incorporamento nelle regioni attive. Il compilatore può anche scegliere di ridurre la dimensione del codice delle regioni fredde, scambiando le prestazioni.
L'identificazione di 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: - compilazione del codice nativo con la misurazione, - esecuzione dell'app misurata sul dispositivo e generazione dei profili, - unione/post-elaborazione dei profili sull'host.
Crea build con strumenti di misurazione
I profili vengono raccolti eseguendo il carico di lavoro dal passaggio 1 su una
build dell'applicazione instrumentata. Per generare una build con strumenti, aggiungi-fprofile-generate
ai flag del compilatore e del linker. Questo flag deve essere controllato da una variabile di build separata perché il flag non è necessario durante una build predefinita.
Genera profili
Quindi, esegui l'app con gli strumenti sul dispositivo e genera i profili.
I profili vengono raccolti in memoria quando viene eseguito il codice binario sottoposto a ispezione e vengono
scritti in un file all'uscita. Tuttavia, le funzioni registrate con atexit
non vengono
chiamate in un'app per Android: l'app viene semplicemente interrotta.
L'applicazione/il carico di lavoro deve eseguire un'operazione aggiuntiva per impostare un percorso per il file del profilo e poi attivare esplicitamente una 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 la libreria, generando un profilo separato per ogni libreria. Consulta questa pagina per altri specificatori di pattern utili.PROFILE_DIR
è una directory scrivibile dall'app. Consulta la demo per rilevare questa directory in fase di runtime. - Per attivare esplicitamente una scrittura del 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.
È sufficiente impostare la variabile di ambiente LLVM_PROFILE_FILE
su %t/default-%m.profraw
prima di eseguire il programma binario.
Profili di post-elaborazione
I file del profilo sono in formato .profraw. Devono essere prima recuperati dal
dispositivo utilizzando adb pull
. Dopo il recupero, utilizza l'utilità llvm-profdata
nell'NDK per convertire da .profraw
a .profdata
, in modo da passare 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 la mancata corrispondenza
della versione dei formati file del profilo.
Passaggio 3: usa i profili per creare l'applicazione
Usa il profilo del passaggio precedente durante una build di release della tua
applicazione passando -fprofile-use=<>.profdata
a compilatore e linker. I profili possono essere utilizzati anche con l'evoluzione del codice: il compilatore Clang è in grado di tollerare lievi discrepanze 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 compilazione arm64 della libreria possono essere utilizzati per tutte le architetture. L'avvertenza è che se nella libreria sono presenti percorsi di codice specifici per l'architettura (arm o x86 o 32 bit o 64 bit), devono essere utilizzati profili distinti per ogni configurazione.
Riassumendo
https://github.com/DanAlbert/ndk-samples/tree/pgo/pgo mostra una demo end-to-end sull'utilizzo di PGO da un'app. Fornisce ulteriori dettagli illustrati in questo documento.
- Le regole di build CMake mostrano come configurare una variabile CMake che crea 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 instrumentata, pgodemo.cpp scrive che i profili sono esecuzione del carico di lavoro.
- Una posizione in cui scrivere i profili viene ottenuta in fase di runtime in
MainActivity.kt
utilizzando
applicationContext.cacheDir.toString()
. - Per eseguire il pull dei profili dal dispositivo senza richiedere
adb root
, utilizza la formulaadb
qui.