排序文件是一种最近的链接器优化技术。这些排序文件是包含表示函数的符号的文本文件。像 lld 这样的链接器使用排序文件以特定顺序布局函数。由于程序冷启动期间高效加载符号,这些具有已排序符号的二进制文件或库减少了页面错误并提高了程序的启动时间。
可以通过以下三个步骤将排序文件功能添加到您的应用程序
- 生成配置文件和映射文件
- 根据配置文件和映射文件创建排序文件
- 在发行版构建期间使用排序文件来布局符号
生成排序文件
生成排序文件需要三个步骤
- 构建写入排序文件的应用的检测版本
- 运行应用以生成配置文件
- 后处理配置文件和映射文件
创建检测版构建
通过运行应用程序的检测版构建来生成配置文件。检测版构建需要向编译器和链接器标志添加-forder-file-instrumentation
,并严格地向编译器标志添加-mllvm -orderfile-write-mapping=<filename>-mapping.txt
。检测标志启用用于分析的排序文件检测,并加载进行分析所需的特定库。另一方面,映射标志仅输出显示二进制文件或库中每个函数的 MD5 哈希值的映射文件。
此外,请确保传递任何优化标志,但不要传递-O0
,因为检测标志和映射标志都需要一个优化标志。如果没有传递优化标志,则不会生成映射文件,并且检测版构建可能会向配置文件输出错误的哈希值。
ndk-build
请确保使用 APP_OPTIM=release
进行构建,以便 ndk-build 使用除 -O0
之外的优化模式。使用 AGP 构建时,发行版构建会自动执行此操作。
LOCAL_CFLAGS += \
-forder-file-instrumentation \
-mllvm -orderfile-write-mapping=mapping.txt \
LOCAL_LDFLAGS += -forder-file-instrumentation
CMake
请确保使用除 Debug
之外的 CMAKE_BUILD_TYPE
,以便 CMake 使用除 -O0
之外的优化模式。使用 AGP 构建时,发行版构建会自动执行此操作。
target_compile_options(orderfiledemo PRIVATE
-forder-file-instrumentation
-mllvm -orderfile-write-mapping=mapping.txt
)
target_link_options(orderfiledemo PRIVATE -forder-file-instrumentation)
其他构建系统
使用 -forder-file-instrumentation -O1 -mllvm -orderfile-write-mapping=mapping.txt
编译代码。
特别地,-O1
不是必需的,但不要使用 -O0
。
链接时省略 -mllvm -orderfile-write-mapping=mapping.txt
。
所有这些标志对于发行版构建都不是必需的,因此应该由构建变量控制。为简便起见,您可以像我们的 示例 中那样在 CMakeLists.txt 中设置所有这些内容。
创建排序文件库
除了标志外,还需要设置配置文件,并且检测到的二进制文件需要在其执行过程中显式触发配置文件写入。
- 调用
__llvm_profile_set_filename(PROFILE_DIR "/<filename>-%m.profraw")
来设置配置文件路径。虽然传递的参数是<filename>-%m.profraw
,但配置文件保存为<filename>-%m.profraw.order
。确保应用程序可写入PROFILE_DIR
,并且您可以访问该目录。- 由于许多共享库都被分析,因此
%m
很有用,因为它会扩展到库的唯一模块签名,从而为每个库生成单独的配置文件。有关更多模式说明符,您可以查看此 链接。
- 由于许多共享库都被分析,因此
- 调用
__llvm_profile_initialize_file()
来设置配置文件 - 调用
__llvm_orderfile_dump()
以显式写入配置文件
配置文件存储在内存中,dump 函数将其写入文件。您需要确保在启动结束时调用 dump 函数,以便您的配置文件包含启动结束前的所有符号。
extern "C" {
extern int __llvm_profile_set_filename(const char*);
extern int __llvm_profile_initialize_file(void);
extern int __llvm_orderfile_dump(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_initialize_file();
__llvm_orderfile_dump();
return;
}
运行构建以生成配置文件
在物理设备或虚拟设备上运行检测到的应用程序以生成配置文件。您可以使用 adb pull
提取配置文件。
adb shell "run-as <package-name> sh -c 'cat /data/user/0/<package-name>/cache/default-%m.profraw.order' | cat > /data/local/tmp/default-%m.profraw.order"
adb pull /data/local/tmp/default-%m.profraw.order .
如前所述,请确保您可以访问包含已写入配置文件的文件夹。如果是虚拟设备,您可能需要避免使用带有 Play 商店的模拟器,因为无法访问许多文件夹。
后处理配置文件和映射文件
获取配置文件后,您需要找到映射文件并将每个配置文件转换为十六进制格式。通常,您可以在应用程序的构建文件夹中找到映射文件。获得两者后,您可以使用我们的 脚本 来获取配置文件和正确的映射文件以生成排序文件。
Linux/Mac/ChromeOS
hexdump -C default-%m.profraw.order > default-%m.prof
python3 create_orderfile.py --profile-file default-%m.prof --mapping-file <filename>-mapping.txt
Windows
certutil -f -encodeHex default-%m.profraw.order default-%m.prof
python3 create_orderfile.py --profile-file default-%m.prof --mapping-file <filename>-mapping.txt
如果您想了解更多关于脚本的信息,您可以查看此 README。
使用排序文件构建应用程序
生成排序文件后,您应该删除之前的标志和排序文件函数,因为这些函数仅用于生成步骤。您只需要将 -Wl,--symbol-ordering-file=<filename>.orderfile
传递给编译和链接器标志。有时,找不到符号或无法移动符号并发出警告,您可以传递 -Wl,--no-warn-symbol-ordering
来抑制这些警告。
ndk-build
LOCAL_CFLAGS += \
-Wl,--symbol-ordering-file=<filename>.orderfile \
-Wl,--no-warn-symbol-ordering \
LOCAL_LDFLAGS += \
-Wl,--symbol-ordering-file=<filename>.orderfile \
-Wl,--no-warn-symbol-ordering \
CMake
target_compile_options(orderfiledemo PRIVATE
-Wl,--symbol-ordering-file=<filename>.orderfile
-Wl,--no-warn-symbol-ordering
)
target_link_options(orderfiledemo PRIVATE
-Wl,--symbol-ordering-file=<filename>.orderfile
-Wl,--no-warn-symbol-ordering
)
其他构建系统
使用 -Wl,--symbol-ordering-file=<filename>.orderfile -Wl,--no-warn-symbol-ordering
编译代码。
更多信息,请查看 排序文件示例。
排序文件实现细节
生成排序文件和使用它们进行构建的方法有很多种。NDK 使用 LLVM 的方法,因此对于您的 C 或 C++ 共享库(而不是实际的 Java 或 Kotlin 应用程序)来说,它最有用。Clang 获取每个函数名(符号)并创建其 MD5 哈希值,并将此关系输出到映射文件。当函数第一次执行时,函数的 MD5 哈希值会被写入配置文件(profraw 格式)。函数的后续执行不会将其 MD5 哈希值写入配置文件,因为它希望避免重复。因此,只有函数的第一次执行才会记录在排序中。通过遍历配置文件和映射文件,您可以获取每个 MD5 哈希值并将其替换为相应的函数,从而获得排序文件。
十六进制格式的配置文件和映射文件的示例分别可在 example.prof 和 example-mapping.txt 中找到。