一、PGO 解决什么问题
编译器默认的优化是基于通用假设——它不知道你的程序实际运行时哪些分支是热的、哪些是冷的。
// 编译器不知道这两条分支的实际运行频率:
void process(bool is_error) {
if (is_error) {
// 编译器假设这两条分支概率是 50/50
// 但实际上这条分支只占 0.01%
log_error();
return;
}
// 99.99% 的执行走这里
do_normal_work();
}
PGO 的做法:
- 先编译一个带桩(profile-generate)的版本。
- 用真实负载跑一遍,收集实际的执行行为。
- 用收集的数据重新编译(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%。