有序文件

有序文件是一种新近的链接器优化技术。有序文件是文本文件,其中包含代表函数的符号。诸如 lld 等链接器使用有序文件以特定顺序排列函数。由于在程序冷启动期间符号的高效加载,因此这些带有序符号的二进制文件或库会减少页面故障数,并缩短程序的启动时间。

您可以按照以下三个步骤将有序文件功能添加到应用中:

  1. 生成配置文件和映射文件
  2. 根据配置文件和映射文件创建有序文件
  3. 在发布 build 期间使用有序文件来排列符号

生成有序文件

生成有序文件需要执行以下三个步骤:

  1. 构建会写入有序文件的应用插桩版本
  2. 运行该应用以生成配置文件
  3. 对配置文件和映射文件进行后处理

创建插桩 build

这些配置文件是通过运行应用的插桩 build 生成的。创建插桩 build 需要将 -forder-file-instrumentation 添加到编译器标记和链接器标记,并将 -mllvm -orderfile-write-mapping=<filename>-mapping.txt 严格添加到编译器标记。该插桩标记支持有序文件插桩以进行性能分析,并会加载性能分析所需的特定库。另一方面,映射标记只会输出显示二进制文件或库中每个函数的 MD5 哈希的映射文件。

此外,请务必传递除 -O0 以外的任意优化标记,因为插桩标记和映射标记都需要一个优化标记。如果未传递优化标记,则不会生成映射文件,并且插桩 build 可能会向配置文件输出错误的哈希。

ndk-build

请务必使用 APP_OPTIM=release 进行构建,以便 ndk-build 使用 -O0 以外的优化模式。使用 AGP 进行构建时,系统会自动针对发布 build 执行此操作。

LOCAL_CFLAGS += \
    -forder-file-instrumentation \
    -mllvm -orderfile-write-mapping=mapping.txt \

LOCAL_LDFLAGS += -forder-file-instrumentation

CMake

请务必使用 Debug 以外的 CMAKE_BUILD_TYPE,以便 CMake 使用 -O0 以外的优化模式。使用 AGP 进行构建时,系统会自动针对发布 build 执行此操作。

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

发布 build 不需要任何这些标记,因此它应该由 build 变量控制。为简单起见,您可以在 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() 以显式写入到配置文件

系统会在内存中收集配置文件,而转储函数会将它们写入到文件中。您需要确保在启动结束时调用转储函数,以便您的配置文件包含直到启动结束为止的所有符号。

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;
}

针对配置文件运行 build

在实体设备或虚拟设备上运行插桩应用来生成配置文件。您可以使用 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 商店的模拟器,因为无权访问很多文件夹。

对配置文件和映射文件进行后处理

获取配置文件后,您需要找到映射文件,并将每个配置文件转换为十六进制格式。通常,您可以在应用的 build 文件夹中找到映射文件。如果您同时拥有这两者,则可以使用我们的脚本获取配置文件和正确的映射文件,以便生成有序文件。

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

若要详细了解该脚本,您可以参阅此自述文件

使用有序文件构建应用

在生成有序文件后,您应移除早前的标记和有序文件函数,因为它们仅用于生成步骤。您只需将 -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.profexample-mapping.txt