文档目录

一、PGO 解决什么问题

编译器默认的优化是基于通用假设——它不知道你的程序实际运行时哪些分支是热的、哪些是冷的。

// 编译器不知道这两条分支的实际运行频率:
void process(bool is_error) {
    if (is_error) {
        // 编译器假设这两条分支概率是 50/50
        // 但实际上这条分支只占 0.01%
        log_error();
        return;
    }
    // 99.99% 的执行走这里
    do_normal_work();
}

PGO 的做法:

  1. 先编译一个带桩(profile-generate)的版本。
  2. 用真实负载跑一遍,收集实际的执行行为。
  3. 用收集的数据重新编译(profile-use),编译器知道"那条错误分支 99.99% 不执行"→ 把它放到 .text.unlikely 段 + 不再内联 + 不再预取。

二、三步流程

# 第一步:插桩编译
g++ -O2 -fprofile-generate -o my_program main.cpp
# 生成一个插桩后的可执行文件

# 第二步:跑真实负载
./my_program < test_input.txt
# 运行结束后生成 *.gcda 文件(profile 数据)
# 位置:当前目录的 ./ 或环境变量 GCOV_PREFIX 指定

# 第三步:利用 profile 优化编译
g++ -O2 -fprofile-use -o my_program_opt main.cpp
# 编译器读取 *.gcda 中的信息做针对性优化

三、PGO 的实际效果

没有 PGO:
  main → process (50%) → normal (生成两份差不多的代码)
              (50%) → error

有 PGO(profile 显示只走了 0.01% error 路径):
  编译器做的优化:
    1. normal 路径内联到 process 中(减少一次函数调用)
    2. error 路径移到 .text.unlikely(热代码更紧凑,I-Cache 命中率更高)
    3. normal 路径的基本块排在一起(减少分支跳转)
    4. 寄存器分配策略偏向 normal 路径

实测结果:
    性能提升通常在 10-30%,取决于分支的可预测性。
    浏览器(Chrome)用 PGO 编译后,启动速度提升约 15%。