Optimización guiada por perfil

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 receta adb aquí.