基于概要文件的优化

基于概要文件的优化 (PGO) 是一种众所周知的编译器优化技术。在 PGO 中,程序执行的运行时概要文件被编译器用来就内联和代码布局做出最佳选择。这将提高性能并减少代码大小。

PGO 可以通过以下步骤部署到您的应用或库中:1. 确定代表性工作负载。2. 收集概要文件。3. 在发布版本中使用概要文件。

步骤 1:确定代表性工作负载

首先,为您的应用确定一个代表性的基准测试或工作负载。这是一个关键步骤,因为从工作负载中收集的概要文件标识了代码中的热点和冷点区域。使用概要文件时,编译器将在热点区域执行积极的优化和内联。编译器还可以选择减小冷点区域的代码大小,同时权衡性能。

确定良好的工作负载也有助于总体跟踪性能。

步骤 2:收集概要文件

概要文件收集涉及三个步骤:- 使用检测工具构建原生代码,- 在设备上运行检测工具生成的应用并生成概要文件,以及 - 在主机上合并/后处理概要文件。

创建检测工具生成的构建

通过在应用的检测工具生成的构建上运行步骤 1 中的工作负载来收集概要文件。要生成检测工具生成的构建,请将-fprofile-generate添加到编译器和链接器标志。此标志应由单独的构建变量控制,因为在默认构建期间不需要此标志。

生成概要文件

接下来,在设备上运行检测工具生成的应用并生成概要文件。当运行检测工具生成的二进制文件时,概要文件会收集到内存中,并在退出时写入文件。但是,使用atexit注册的函数不会在 Android 应用中调用 - 应用只是被终止。

应用程序/工作负载需要额外的工作来设置配置文件的路径,然后显式地触发配置文件写入。

  • 要设置配置文件路径,请调用__llvm_profile_set_filename(PROFILE_DIR "/default-%m.profraw%m在有多个共享库时很有用。 %m会扩展为该库的唯一模块签名,从而为每个库生成单独的配置文件。有关其他有用的模式说明符,请参见此处PROFILE_DIR 是应用程序可写入的目录。请参阅演示,了解如何在运行时检测此目录。
  • 要显式触发配置文件写入,请调用__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;
}

注意:如果工作负载是独立二进制文件,则生成配置文件更简单——只需在运行二进制文件之前将LLVM_PROFILE_FILE环境变量设置为%t/default-%m.profraw

后期处理配置文件

配置文件采用 .profraw 格式。必须首先使用adb pull从设备中获取它们。获取后,使用NDK中的llvm-profdata实用程序将.profraw转换为.profdata,然后将其传递给编译器。

$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-profdata \
    merge --output=pgo_profile.profdata \
    <list-of-profraw-files>

使用相同NDK版本的llvm-profdataclang,以避免配置文件格式的版本不匹配。

步骤3:使用配置文件构建应用程序

在应用程序的发布版本构建过程中,通过向编译器和链接器传递-fprofile-use=<>.profdata来使用上一步中的配置文件。即使代码发生变化,也可以使用这些配置文件——Clang编译器可以容忍源代码和配置文件之间存在细微的差异。

注意:通常,对于大多数库,配置文件在不同架构之间是通用的。例如,从库的arm64构建生成的配置文件可用于所有架构。但需要注意的是,如果库中存在特定于架构的代码路径(arm与x86或32位与64位),则应为每种配置使用单独的配置文件。

综合示例

https://github.com/DanAlbert/ndk-samples/tree/pgo/pgo展示了从应用程序使用PGO的端到端演示。它提供了本文档中略过的其他详细信息。

  • CMake构建规则展示了如何设置一个CMake变量,该变量使用检测工具构建原生代码。当未设置构建变量时,原生代码将使用先前生成的PGO配置文件进行优化。
  • 在检测构建中,pgodemo.cpp在工作负载执行时写入配置文件。
  • MainActivity.kt中,使用applicationContext.cacheDir.toString()在运行时获取配置文件的可写位置。
  • 要从设备中提取配置文件而无需adb root,请使用此处的adb方法。