La optimización guiada por perfil (PGO) es una técnica conocida de optimización del compilador. En PGO, el compilador usa perfiles de tiempo de ejecución de las ejecuciones de un programa para tomar decisiones óptimas sobre la integración y el diseño del código. Esto mejora el rendimiento y reduce el tamaño del código.
La PGO se puede implementar en tu aplicación o biblioteca con los siguientes pasos: 1. Identifica una carga de trabajo representativa. 2. Recopila perfiles. 3. Usa los perfiles en una compilación de lanzamiento.
Paso 1: Identifica una carga de trabajo representativa
Primero, identifica una comparativa o carga de trabajo representativa para la aplicación. Este es un paso fundamental, ya que los perfiles recopilados de la carga de trabajo identifican las regiones calientes y frías en el código. Cuando se usan los perfiles, el compilador realiza optimizaciones agresivas y la alineación en las regiones calientes. El compilador también puede reducir el tamaño de código de las regiones frías a la vez que cambia el rendimiento.
Identificar una buena carga de trabajo también es beneficioso para hacer un seguimiento del rendimiento en general.
Paso 2: Recopila perfiles
La recopilación de perfiles consta de tres pasos: - compilar el código nativo con instrumentación - ejecutar la app instrumentada en el dispositivo y generar perfiles - combinar y realizar el procesamiento posterior de los perfiles en el host
Cómo crear compilaciones instrumentadas
Los perfiles se recopilan mediante la ejecución de la carga de trabajo del paso 1 en una compilación instrumentada de la aplicación. Para generar una compilación instrumentada, agrega -fprofile-generate
a las marcas del compilador y del vinculador. Esta marca debe controlarse mediante una variable de compilación independiente, ya que no es necesaria durante una compilación predeterminada.
Cómo generar perfiles
A continuación, ejecuta la app instrumentada en el dispositivo y genera perfiles.
Los perfiles se recopilan en la memoria cuando se ejecuta el objeto binario instrumentado y se escriben en un archivo en la salida. Sin embargo, no se llama a las funciones registradas con atexit
en una app para Android; solo se cierra la app.
La aplicación o la carga de trabajo debe realizar un trabajo adicional a fin de establecer una ruta de acceso para el archivo de perfil y, luego, activar de forma explícita una escritura de perfil.
- Para configurar la ruta de acceso del archivo de perfil, llama a
__llvm_profile_set_filename(PROFILE_DIR "/default-%m.profraw
.%m
es útil cuando hay varias bibliotecas compartidas.%m
se expande a una firma de módulo única para esa biblioteca, lo que genera un perfil independiente por biblioteca. Consulta aquí para obtener otros especificadores de patrones útiles.PROFILE_DIR
es un directorio que puede escribir desde la app. Consulta la demostración para detectar este directorio en el tiempo de ejecución. - Para activar de manera explícita una escritura de perfil, llama a la función
__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;
}
NB: Generar el archivo de perfil es más simple si la carga de trabajo es un objeto binario independiente. Solo configura la variable de entorno LLVM_PROFILE_FILE
como %t/default-%m.profraw
antes de ejecutar el objeto binario.
Perfiles posteriores al proceso
Los archivos de perfil están en formato .profraw. Primero, se deben recuperar del dispositivo mediante adb pull
. Después de la recuperación, usa la utilidad llvm-profdata
en el NDK para convertir de .profraw
a .profdata
, que luego se puede pasar al compilador.
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-profdata \
merge --output=pgo_profile.profdata \
<list-of-profraw-files>
Usa llvm-profdata
y clang
de la misma versión del NDK para evitar que las versiones no coincidan con los formatos de archivo del perfil.
Paso 3: Usa los perfiles para compilar la aplicación
Usa el perfil del paso anterior durante una compilación de lanzamiento de tu aplicación pasando -fprofile-use=<>.profdata
al compilador y al vinculador. Los perfiles se pueden usar incluso a medida que evoluciona el código; el compilador Clang puede tolerar una leve discrepancia entre la fuente y los perfiles.
NB: En general, para la mayoría de las bibliotecas, los perfiles son comunes en todas las arquitecturas. Por ejemplo, los perfiles generados a partir de la compilación arm64 de la biblioteca se pueden usar para todas las arquitecturas. Sin embargo, debes tener en cuenta que, si hay rutas de acceso de código específicas de la arquitectura de la biblioteca (arm frente a x86 o 32 bits frente a 64 bits), se deben usar perfiles individuales para cada configuración.
Revisión general
https://github.com/DanAlbert/ndk-samples/tree/pgo/pgo muestra una demostración de extremo a extremo para usar PGO desde una app. Proporciona detalles adicionales que se analizaron en este documento.
- Las reglas de compilación de CMake muestran cómo configurar una variable de CMake que compile código nativo con instrumentación. Cuando no se configura la variable de compilación, el código nativo se optimiza mediante perfiles de PGO generados previamente.
- En una compilación instrumentada, pgodemo.cpp escribe los perfiles que son ejecución de la carga de trabajo.
- Se obtiene una ubicación que admite escritura para los perfiles en el tiempo de ejecución en MainActivity.kt mediante
applicationContext.cacheDir.toString()
. - Para extraer perfiles del dispositivo sin requerir
adb root
, usa la recetaadb
aquí.