跳到主要内容
版本:4.x

配置文件引导的优化(PGO)

配置文件引导的优化(Profile-Guided Optimization,简称PGO)通过收集程序在实际运行中的性能数据,指导编译器进行更有针对性的优化,从而提升生成代码的执行效率。ZCC 支持在无操作系统环境下使用 PGO 技术。

PGO 快速上手

以下是以 hello.c 为例的快速上手流程:

#include <stdio.h>

int main() {
printf("Hello\n");
return 0;
}

第一步:插入性能数据采集代码

在编译源文件时,添加 -fprofile-instr-generate 编译选项。此选项会指示编译器在生成的代码中插入用于收集运行时性能数据的指令。

zcc hello.c -fprofile-instr-generate -o a.out

第二步:收集性能数据

运行上一步生成的可执行程序。在无操作系统环境下,性能数据会以特定格式的文本形式输出。请确保将程序的标准输出重定向至文件以便保存数据。

./a.out > a.txt
提示
  • 输出的性能数据被包裹在特定的起始和结束标记之间:

    ====LLVMProfilingData:begin====
    ...(性能数据)...
    ====LLVMProfilingData:end====
  • 可多次运行程序(可使用相同或不同的参数),并将每次的输出分别保存,例如a1.txta2.txt等,以获取更全面的性能样本。

第三步:转换性能数据格式

使用 llvm-profdata 工具将文本格式的性能数据转换为二进制格式,便于后续处理。

llvm-profdata translate a.txt --output=a.bin

如有多个数据文件,需分别转换:

llvm-profdata translate a1.txt --output=a1.bin
llvm-profdata translate a2.txt --output=a2.bin

第四步:合并性能数据

将多个二进制格式的性能数据文件合并为一个统一的配置文件,该文件将用于指导编译器优化。

  • 合并指定文件

    llvm-profdata merge -output=a.profdata a1.bin a2.bin a3.bin
  • 使用通配符合并

    llvm-profdata merge -output=a.profdata a*.bin

第五步:使用配置文件进行优化编译

在编译时,使用 -fprofile-instr-use 选项指定上一步生成的配置文件(a.profdata)。编译器将依据此文件中的性能分析结果,进行针对性优化,生成高效的可执行文件。

zcc hello.c -fprofile-instr-use=a.profdata -o a.pgo.out

第六步:运行优化后的程序

运行最终生成的、经过 PGO 优化的可执行程序,体验性能提升。

./a.pgo.out

PGO 的打印函数

ZCC 提供了打印 PGO 数据的接口 void __zcc_baremetal_profile_dump(void),默认情况下编译器会在 main 函数结束后调用 __zcc_baremetal_profile_dump 来进行 PGO 数据的打印,若用户的 main 函数不会结束,则可以在 main 中手动调用 __zcc_baremetal_profile_dump 函数来进行 PGO 数据的打印。

链接器脚本

在开启 PGO 之后,编译器会产生一些以 __llvm_prf_ 开头的段,比如__llvm_prf_names__llvm_prf_cnts__llvm_prf_data等,同时为每一个段定义了两个符号,一个符号指向段的开头,一个符号指向段的结尾。比如__start___llvm_prf_names__stop___llvm_prf_names分别指向__llvm_prf_names的开头和结尾。

如果用户在链接器脚本中将以 __llvm_prf_ 开头的段放入了其他段中,则编译器不会为这个以 __llvm_prf_ 开头的段定义 __start___stop_ 符号,并且需要将以 __llvm_prf_ 开头的段 ALIGN 到 8 个字节处。例如:

  .data           :
{
*(.data .data.*)
. = ALIGN(8);
__start___llvm_prf_cnts = .;
*(__llvm_prf_cnts)
. = ALIGN(8);
__stop___llvm_prf_cnts = .;

. = ALIGN(8);
__start___llvm_prf_names = .;
*(__llvm_prf_names)
. = ALIGN(8);
__stop___llvm_prf_names = .;

. = ALIGN(8);
__start___llvm_prf_data = .;
*(__llvm_prf_data)
. = ALIGN(8);
__stop___llvm_prf_data = .;
}