Chromium 定制版 PGO 实战:Chrome 与 V8 Builtins 两套体系以及打包踩坑
本文基于一次真实升核(M148 / V8 14.8.x)打包与 PGO 训练整理
前言:为什么 PGO 会「编着编着就缺文件」
切到 Official Release + PGO Phase 2 后,常见报错:
../../v8/tools/builtins-pgo/profiles/x64-rl.profile
needed by snapshot_blob.bin -> gen/v8/embedded.S
missing and no known rule to make it
这往往不是 snapshot 坏了,而是 Chrome 主程序 PGO 与 V8 Builtins PGO 是两套机制:只提交了 Chrome 的*.profdata,却默认打开了 V8 builtins 优化,构建在 schedule 阶段就失败。
一、两套 PGO:别混成一个「PGO 包」
| Chrome PGO | V8 Builtins PGO | |
|---|---|---|
优化对象 |
| V8 builtins( |
训练产物 |
|
|
训练工具 |
| 官方下载或 |
Phase 2 开关 |
|
|
is_official_build=true
→ chrome_pgo_phase=2
→ v8_enable_builtins_optimization 默认为 true(若未显式关闭)
→ Windows x64 需要 x64-rl.profile
Chrome profdata 有了 ≠ V8 profile 有了。
二、Chrome PGO:Phase 1 与 Phase 2
Phase 1:插桩采集
- GN:
chrome_pgo_phase=1 - 编译带
-fprofile-generate - 插桩版浏览器跑 benchmark →
.profraw→llvm-profdata merge→profile.profdata
python3 tools/pgo/generate_profile.py -C out/PGOPhase1
需要 完整源码树 + Phase 1 输出目录,不能只有二进制。
Phase 2:用 profile 优化
-fprofile-use=...profdata+ ThinLTO- 链接
chrome.dll通常最慢 - CI 若每次 clean 全量,每次都像第一次全编
三、Phase 1 链接坑:升核后 fork 与上游 API 类型不一致
Phase 1 除了「能编过、能跑 benchmark」,还可能遇到 链接阶段 才暴露的问题。下面是一次 M148 升核 +chrome_pgo_phase=1时的案例(扩展工具栏 ViewModel),与 V8 profile 无关,但 不做修复就过不了 Phase 1,也就训不出 Chrome profdata。
3.1 现象
在args.gn中设置:
chrome_pgo_phase = 1
# fork 定制宏已开启(如扩展工具栏仍走 vector 顺序语义)
执行:
autoninja -C out/Release chrome
chrome.dll链接失败,lld-link 报 undefined symbol,符号栈与base::flat_tree/flat_set析构相关——表面看像 STL 或 libc++ 链接问题,实际是 接口返回类型与底层实现不一致 在 PGO 插桩 下被放大。
3.2 根因(148 上游 vs fork 历史行为)
148 上游把ExtensionsToolbarViewModel::GetAllActionIds()改成了返回const base::flat_set<ActionId>&,与ToolbarActionsModel::action_ids()对齐。
定制 fork 在 产品宏分支 下仍保留ToolbarActionsModel::action_ids()返回std::vector<ActionId>&(扩展工具栏顺序语义依赖 vector,而非 sorted flat_set)。
若 ViewModel 仍声明返回flat_set&,实现里却return actions_model_->action_ids():
- 编译器对
vector→flat_set做隐式转换,构造 临时flat_set - 函数返回的是 临时对象的引用(本身已是危险写法)
- PGO Phase 1 插桩 下,
flat_tree析构路径 无法如 Release 那样内联 - 链接器找不到对应符号 →
chrome.dll链接失败
非 PGO 或 Phase 0 构建有时能「侥幸过」或表现不同;Phase 1 是更严格的照妖镜。
3.3 修复思路(与上游分叉的最小对齐)
在 fork 宏分支 让声明、实现与底层 同一类型——返回const std::vector<ActionId>&,直接转发action_ids(),避免任何 vector→flat_set 临时对象:
#ifdef FORK_EXTENSIONS_TOOLBAR /* 示意:你们实际的 fork 宏名 */
const std::vector<ToolbarActionsModel::ActionId>&
ExtensionsToolbarViewModel::GetAllActionIds() const {
return actions_model_->action_ids();
}
#else
const base::flat_set<ToolbarActionsModel::ActionId>&
ExtensionsToolbarViewModel::GetAllActionIds() const {
return actions_model_->action_ids();
}
#endif
HasAnyExtensions()在 fork 分支改为 直接读action_ids().empty(),不要再经可能触发转换的GetAllActionIds():
bool ExtensionsToolbarViewModel::HasAnyExtensions() const {
#ifdef FORK_EXTENSIONS_TOOLBAR
return !actions_model_->action_ids().empty();
#else
return !GetAllActionIds().empty();
#endif
}
头文件同步改返回类型,并加注释说明:禁止在 fork 路径返回flat_set&,否则 PGO 链接可能再炸。
3.4 测试建议
| 项 | 做法 |
|---|---|
链接 |
|
功能 | 启动浏览器:扩展工具栏图标显示、拖拽、溢出菜单 |
回归 | Phase 0 / Phase 2 各编一次,确认无新增链接错误 |
3.5 对 PGO 流程的启示
升核 → 先过 Phase 1 链接 + 能启动
→ 再 generate_profile.py 训 profdata
→ 再 Phase 2 + V8 profile
Phase 1 链接问题 和 Phase 2 缺 x64-rl.profile 是 不同阶段、不同层 的故障:
| 阶段 | 典型错误 | 性质 |
|---|---|---|
Phase 1 | lld-link undefined symbol(flat_set/flat_tree) | fork 与 148 API 类型不一致 + 插桩 |
Phase 2 | missing x64-rl.profile | V8 builtins PGO 文件缺失 |
升核合并时,建议对flat_set/span/ 容器类型变更 做专项 grep:凡 fork 仍用 vector 的模型,上层 ViewModel 不要声明成 upstream 的 flat_set&。
四、V8 Builtins PGO:官方包 vs 自训
4.1 官方 profile
https://storage.googleapis.com/chromium-v8-builtins-pgo/by-version/{V8四段版本}/meta.json
https://storage.googleapis.com/chromium-v8-builtins-pgo/by-version/{V8四段版本}/x64-rl.profile
版本来自v8/include/v8-version.h(如14.8.178.4),不是 Chrome 产品号。meta.json里revision需与DEPS的v8_revision一致 再用。
4.2x64.profilevsx64-rl.profile
- Windows 正式构建:
x64-rl.profile - Linux/macOS + Clang:
x64.profile
4.3 内网镜像 404、Google 有
多为 镜像未同步chromium-v8-builtins-pgo桶。
4.4 自训难点(摘要)
Windows 要-rl、GN 须与正式 Release 一致、benchmark 环境、每次 bump V8 重做、profile 默认 gitignore。revision 对齐时 优先官方。
五、推荐策略(决策树)
升核 / 出包
├─ Phase 1
│ ├─ chrome.dll 链接过? → 修 fork/上游 API 不一致(如 GetAllActionIds)
│ └─ generate_profile.py → profdata
├─ Phase 2
│ ├─ Chrome:pgo_data_path / 已提交 profdata
│ └─ V8:x64-rl.profile(官方 revision 对齐)或 v8_enable_builtins_optimization=false
└─ CI:确认 gn gen 真正传入 extra_args
务实组合:Chrome profdata 自训;V8x64-rl.profile用官方(revision 一致时)。
六、换机器训练 Chrome PGO 要带什么
- 同 commit 完整
src - Phase 1 的
out/(去掉obj/、*.pdb可瘦身) llvm-profdata- 定制主程序若不叫
chrome.exe,需与generate_profile.py期望名对齐(复制/改名)
七、Phase 2 编译慢
| 场景 | 体验 |
|---|---|
首次全量 | 很慢 |
同目录无改动 | 增量几乎为 0 |
CI 每次 clean | 每次都慢 |
八、官方 V8 下载(示例 14.8.178.4)
https://storage.googleapis.com/chromium-v8-builtins-pgo/by-version/14.8.178.4/meta.json
https://storage.googleapis.com/chromium-v8-builtins-pgo/by-version/14.8.178.4/x64-rl.profile
放置:
v8/tools/builtins-pgo/profiles/meta.json
v8/tools/builtins-pgo/profiles/x64-rl.profile
九、总结
- Chrome PGO 与 V8 Builtins PGO 两套文件、两套流程。
- Phase 1 要先过 链接 + benchmark;fork 与 148 返回类型 不一致会在 PGO 插桩下触发 lld-link。
- Phase 2 常见缺
x64-rl.profile;Windows 勿用x64.profile顶替。 - 官方 V8 profile 在 revision 对齐时可直接用。
- CI 检查
gn gen是否传入v8_enable_builtins_optimization等参数。
代码示例中的FORK_EXTENSIONS_TOOLBAR为脱敏占位符,请替换为你们仓库实际的 fork 宏名。
