计算机体系结构 02:怎样比较两台机器
计算机体系结构 02:怎样比较两台机器
比较两台机器时,不能只问“主频多高”。一个程序的 CPU 时间可以粗略拆成:
1 | |
这条式子不是万能测量工具,但能防止三类误判:指令少不一定快,CPI 低不一定快,频率高也不一定快。真正要比较的是同一工作负载、同一正确性条件、同一计时区间下的完成时间;再用指令数、CPI、缓存、分支和带宽解释原因。
本篇证据等级为“手算”和“真机测量”。本机实验使用 macOS 27.0、Apple M4 Pro、14 个逻辑 CPU、24 GiB 内存、Apple clang 21.0.0 和 C 程序重复计时;环境查询命令为 sysctl -n machdep.cpu.brand_string hw.ncpu hw.memsize,脚本可采集的环境记录见 intro-environment.txt。没有硬件性能计数器,没有隔离背景负载,也没有锁定频率,因此不声称测得真实指令数、CPI、CPU 频率、缓存 miss 或能耗。
延迟和吞吐不是同一个指标
延迟是一次任务从开始到结束花多久。吞吐是单位时间完成多少任务。数组求和这种单次批处理,最直观指标是延迟:同一份输入多久算完。服务器处理请求时,平均延迟、尾延迟和吞吐可能同时重要;吞吐提高不一定让每个请求更快。
本篇把问题缩小为单进程、单工作负载的 elapsed time。计时区间包括循环执行本身,不包括编译时间;程序先生成输入数组,再重复运行求和内核,每次校验和必须一致。Apple 文档说明 mach_absolute_time 是单调 tick 计数,并建议现代代码可使用同类纳秒接口;本实验在 macOS 上用 clock_gettime_nsec_np(CLOCK_UPTIME_RAW) 取得纳秒级 uptime。这个选择只说明计时 API,不代表操作系统调度、睿频和温度影响已经消失。
指令数、CPI 和频率的手算
假设同一任务有三种实现:
| 实现 | 指令数 | CPI | 频率 | CPU 时间 |
|---|---|---|---|---|
| A | 1000 | 1.0 | 1 GHz | 1000 ns |
| B | 800 | 2.0 | 1 GHz | 1600 ns |
| C | 1200 | 0.8 | 2 GHz | 480 ns |
B 的指令数比 A 少,但 CPI 翻倍,反而更慢。C 的指令数更多,但 CPI 较低且频率更高,反而更快。这个例子说明“少执行几条指令”只是可能的优化方向,不是性能结论。
再构造一个“主频更高却更慢”的例子:
| 实现 | 指令数 | CPI | 频率 | CPU 时间 |
|---|---|---|---|---|
| D | 1000 | 1.0 | 1 GHz | 1000 ns |
| E | 1000 | 3.0 | 2 GHz | 1500 ns |
E 的频率是 D 的两倍,但平均每条指令要 3 个周期,最终更慢。现实中 CPI 可能来自缓存缺失、分支错误、资源冲突、数据依赖或指令类型差异。后续章节会把这些原因拆开。
Amdahl 定律给上限
如果程序中只有一部分能被优化,总体加速受未优化部分限制。设可优化部分占比为 p,这部分加速 s 倍,总体加速为:
1 | |
如果 80% 的时间能加速 4 倍,整体加速是:
1 | |
不是 4 倍。若只优化 20% 的时间,即便这部分无限快,整体也最多 1 / 0.8 = 1.25 倍。这条公式让性能讨论回到计时区间:被优化部分到底占多少,是测量问题,不是口号。
本机测量案例
附件 timing.c 创建 1 << 20 个 uint32_t,对同一数组重复 128 遍求和,每个二进制先做一次不计时预热校验,再重复计时 9 次。程序打印输入规模、期望校验和、每次耗时、每次校验结果,以及最小值、中位数和最大值;一旦校验失败就非零退出。本机环境记录在 intro-environment.txt。
编译命令来自 examples/computer-architecture/intro/scripts/run_all.sh:
1 | |
Clang 官方命令指南说明 -O0 表示不优化,-O3 在 -O2 基础上启用可能更耗时、可能生成更大代码的优化以尝试让程序更快。本实验用它们比较同一源码、同一输入、同一校验和下的宿主运行时间。
-O0 输出摘要:
1 | |
-O3 输出摘要:
1 | |
两个版本的校验和完全一致。以中位数看,-O0 约 84.03 ms,-O3 约 5.28 ms,本机这次运行中相差约 15.93 倍:
1 | |
这能支持的结论是:在这台 macOS arm64 机器、这个编译器和这个工作负载上,-O3 生成的程序显著更快,且输出校验一致。由于两组二进制按先后批次运行,并行会话和系统后台负载没有隔离,这个倍数只作为原生观测记录使用。它不能支持的结论是:-O3 一定让所有程序快 15.93 倍;这次快的原因一定是某条具体指令;或者缓存事件已经被测量。
为什么必须先校验结果
性能实验如果不校验结果,很容易把“算错但快”当成优化。timing.c 先用同一输入计算期望和,再做一次不计时预热校验,随后在每次计时后检查 got == expected。校验失败会打印错误并退出非零;成功输出每行都包含 ok=yes,脚本也用 grep 'ok=yes' 做最低限度检查。
这个检查仍然有限:它只能发现最终和不一致,不能证明中间没有未定义行为,不能证明没有数据竞争,也不能证明编译器没有做合法但出乎意料的变换。第 35 篇独立性能研究会把校验、预热、重复、异常值和反例写成更完整的报告格式。
验收结果
本篇实际运行:
1 | |
结果为 intro experiments: PASS。同名素材目录保存 timing.c、timing-O0-output.txt、timing-O3-output.txt 和 intro-environment.txt。
验收成立的部分:同一输入、同一校验和、同一计时程序,在 -O0 和 -O3 两个构建下得到重复计时记录;手算例子展示了主频更高或 CPI 更低仍可能更慢。保留缺口:没有硬件性能计数器;没有交替运行两种二进制;没有锁定 CPU 频率、核心亲和性或温度。
练习
练习 1:某程序执行 20 亿条指令,平均 CPI 为 1.5,频率 3 GHz。CPU 时间是多少?
答案:周期数为 2e9 × 1.5 = 3e9,时间为 3e9 / 3e9 = 1 秒。
练习 2:程序总时间 100 ms,其中 60 ms 是可向量化循环。若循环部分加速 4 倍,总时间是多少?整体加速多少?
答案:新时间为 40 ms + 60 ms / 4 = 55 ms,整体加速为 100 / 55 ≈ 1.82 倍。
参考资料
- Clang Command Guide,优化等级说明: https://clang.llvm.org/docs/CommandGuide/clang.html
- Apple Developer Documentation,
mach_absolute_time与单调计时说明: https://developer.apple.com/documentation/kernel/1462446-mach_absolute_time - WG14,C11 对应草案信息: https://www9.open-std.org/JTC1/SC22/WG14/www/projects.html
- 本篇实验附件:timing.c、timing-O0-output.txt、timing-O3-output.txt、intro-environment.txt




