Ottimizzazione basata sul profilo

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&#39;avvertenza è che se nella libreria sono presenti percorsi di codice specifici per l&#39;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 formula adb qui.