Commit 48de5d2
committed
feat(bench): 构建引擎基准套件 —— 把一次性脚本变成跨平台、可扩展的测量设施 (2026.8.12.1)
起因是一次实测:mcpp 的自举构建**不是吞吐瓶颈,是延迟瓶颈**。关键路径 = 100% 墙钟,
后 55% 的时间里 32 个硬件线程上只有 1 个编译进程在跑;而这条关键路径上 77% 的时间
在生产**没有任何下游需要的 `.o`** —— 下游真正需要的 BMI 在编译进度 22.8% 处就已经
原子 rename 就位(strace 证实:之后 982 个系统调用无一再碰它)。
同样的病理在 xlings(110 模块、独立作者、独立代码库)上完整复现:并行度 3.16×、
关键路径 100%。所以这不是某一家构建系统的实现问题,而是「C++23 命名模块 + GCC
单阶段 + 边完成即释放」这一组合的结构性结果。
完整分析见 .agents/docs/2026-08-12-modular-build-performance-deep-analysis.md,
架构与实施计划见 .agents/docs/2026-08-12-bench-suite-architecture-and-plan.md。
## 为什么要重写而不是扩展
上一轮用的是 bash + hyperfine 的一次性脚本,四个缺陷都是结构性的:只支持两个引擎
(加第三个要动主体)、**Windows 上根本跑不了**(而 mcpp 是三平台产品)、被测对象只
有 mcpp 自己(答不了「模块化 vs 头文件」这个真问题)、结果是随手加字段的 TSV
(跨机器无法合并)。
## bench/ 的设计
**协议先行。** `bench.protocol` 带 `protocol_version`,并把三条不变量写进类型而不是
留给约定 —— 每一条都被旧脚本违反过:
1. 失败不得伪装成数据。`status` 与 timing 是分开的字段,非 ok 的格**没有 median 键**
(而不是 0)。旧脚本把失败格式化成 "0.000 s",三个这样的格子进了结果文件,
看起来像是有史以来最快的构建。
2. 跳过必须带原因。"bazel 没装" 与 "bazel 跑挂了" 是相反的结论。
3. 结果与宿主同生共死,含**异构 CPU 标记** —— 13900K 的 32 线程不是 32 个同构核,
所有并行度数字都要照着它读。
**加一个引擎 = 加一个文件。** `bench.engines.Engine` + `registry.cppm` 一行,runner /
协议 / 场景 / CI 全不动。已接入 mcpp、mcpp-opt(优化前后成为矩阵的一个正交维度)、
cmake、xmake、meson、bazel。
**同一工程三种形态,生成而非手写**:`headers` / `modules` / `modules-impl`。手写两份
「等价」代码几乎必然在某处不等价,而那正是被测量的东西。第三种变体直接对应实测结论:
GCC 与 Clang 的模块接口单元 BMI **都**携带函数体,所以改任何一行函数体都会级联到全部
导入者,且没有编译器开关能解决(`-fmodules-reduced-bmi` 实测无效)。
**平台差异只在叶子。** 按 xlings `src/platform/*.cppm` 的既定约定:模块分区 + 整文件
宏控,非目标平台**不导出任何符号**。于是任一构建中每个名字只有一份定义、编译期自动
选中 —— 不需要 stub,也不需要 `if constexpr` 派发。`#if defined(_WIN32)` 只出现在
那两个分区里,runner / engines / protocol / fixture 全部零平台条件。
**`--analyze`**:同一个二进制还能剖析任意 ninja 构建目录(工作量 / makespan / 关键
路径 / 并发曲线),并固化了五个会**反转结论**的解析陷阱 —— 其中最狠的一个是:最长路径
必须按拓扑序松弛,栈式 DFS 的防环写法会把未算完的依赖记 0,把 76.5s/26 节点读成
33.9s/10 节点,把「100% 延迟瓶颈」读成「44%」。它是靠与独立 Python 实现交叉验证抓到的
—— 其余所有指标都吻合,唯独这一个差 2.3 倍。
## 实现过程中被实测推翻的三件事
- `-fmodule-only` 文档说「只产 CMI」,实测**照样跑完整个 codegen 再把结果丢弃**
(15.93s vs 完整 15.95s)。GCC 16.1 没有廉价产出 BMI 的开关;Clang 有。
- 「BMI 太大所以导入慢」不成立:`import std`(31.5MB BMI)只多 4.8ms —— GCC 的模块
导入本来就是惰性的。真正的驱动因素是代码量(corr(LOC, t_total) = 0.825)。
- 降优化档不是出路:`-O0` 相对 `-O2` 只快 1.75×,而产物运行时性能全丢。
## CI
`.github/workflows/bench.yml`,**仅手动触发**、覆盖 linux/macOS/windows、
**不设性能阈值**。基准是重活且噪声大,挂进每个 PR 只会淹没它要产出的信号;而在共享
runner 上设阈值,等于把正常方差变成人人学会忽略的红叉。
## 验证
- `mcpp build` 通过,`mcpp test` **80 passed / 0 failed**
- 新增 e2e `230_bench_harness.sh`:构建 harness、真实测量、并从**两侧**断言协议不变量
(只断言 ok 格有 median 会放过一个「给所有格都发 median」的实现)
- 六个引擎在本机全部实测跑通(mcpp / mcpp-opt / cmake / xmake / meson / bazel)
- `check_version_pins.sh` OK:xlings pin 已是最新发布 2026.8.11.2,无需变更
顺带保留仓库根的 `xmake.lua`(用 xmake 构建 mcpp 本身的对照臂)。它从 mcpp.toml 读取
`[toolchain] default` 来钉编译器 —— registry 里有多个 GCC,而「取目录序最后一个」只是
碰巧对。1 parent 8219584 commit 48de5d2
57 files changed
Lines changed: 4984 additions & 2 deletions
File tree
- .agents/docs
- .github/workflows
- bench
- proto-bmi-release
- results
- src
- analysis
- engines
- fixture
- platform
- src
- tests/e2e
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 203 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
0 commit comments