一条mov如何让CPU干等139毫秒?asm-hall-of-shame PCIe/MMIO慢速GPU寄存器寻宝指南
【免费下载链接】asm-hall-of-shameRacing to the bottom of CPU performance项目地址: https://gitcode.com/gh_mirrors/as/asm-hall-of-shame
asm-hall-of-shame 是一个开源的"指令延迟倒数榜"项目:别的性能分析都在想办法让代码跑得更快,它反其道而行,专门在 x86 上寻找单条指令能有多慢。其中最经典的案例是:一条平平无奇的 4 字节movl内存读取,竟让 CPU 干等了整整139 毫秒(443,937,696 个周期)——罪魁祸首是 PCIe 总线上一个连文档都没写的 GPU 慢速寄存器。🕵️
图中从最慢的fxrstor64(1980亿周期)到最快的nop(1 周期),完整呈现了 asm-hall-of-shame 的 x86 单指令延迟排行
一、什么是 asm-hall-of-shame:CPU 性能的"耻辱榜"
传统指令延迟研究关注性能优化,而这个项目的目标正好相反:找到单条指令性能的绝对地板。它的规则很严格(见 README.md):
- 指令可以任意搭建前置环境,但只有一条指令参与计分
rep movs、pause这类可中断指令直接淘汰- 成绩按 CPU 基础频率归一化,且所有平台必须是出厂原装配置
榜单的起点是nop(1 个周期),终点则是当前冠军fxrstor64:198,002,498,236 个周期,约合 62 秒。中间跨越了十几个数量级——这正是"寻宝"的乐趣所在。
二、139毫秒的 mov:一次普通的 4 字节读取
主角代码在mov/mov.c,核心操作其实非常朴素:
#define PHYS_ADDR 0xfcc003b0UL // 位于 Radeon GPU 的 MMIO 空间 void *page = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, fd, (off_t)PAGE_BASE); uint64_t t = MEASURE_PTR("movl (%%rdi), %%esi\n\t", reg); // 计时一次 movl程序通过/dev/mem以O_SYNC + MAP_SHARED映射物理页,确保这是一次无缓存、强序的读取。于是这条movl的真实旅程是:
CPU 发起读请求 → PCIe 根复合体 → 交换链路 → GPU 端点 → 等 GPU 应答 → 数据回传 → 指令才能退休
也就是说,CPU 必须老老实实等完整个 PCIe 往返。而这个地址0xfcc003b0恰好落在 Radeon GPU 的 MMIO 空间里,对应一个** undocumented(无文档)的 GPU 寄存器**——那近 5 亿个周期的卡顿,很可能反映了 GPU 深处某个缓慢的内部状态机或跨时钟域逻辑。
📌 有个容易踩的坑:据mov/README.md记录,这片区域的高延迟可能要先访问某个邻近地址"预热"后才会显现,需要用 mmiotic 工具做一次 prime 探测,否则可能测不出满血延迟。
三、为什么 MMIO 读取这么慢?看宽度倍增的"寻宝"梯度
PCIe 上 MMIO 的天然粒度是32 位(一个 dword),而且 CPU 读取 MMIO 无法"发了就忘"——它必须等 completion 回来。于是,读得越多,排队的时间就越长:
| 指令 | 读取宽度 | 拆分成的 dword 事务 | 耗时 |
|---|---|---|---|
movl 0xfcc003b0, %esi | 4 字节 | 1 | 139.01 ms |
movq 0xfcc003b0, %rax | 8 字节 | 2 | 277.97 ms |
vmovdqu ..., %xmm0 | 16 字节 | 4 | 555.66 ms |
vmovdqu ..., %ymm0 | 32 字节 | 8 | 1.11 s |
vmovdqu 0xfcc003b1, %ymm0(未对齐) | 32 字节 | 9 | 1.39 s |
几个有趣的细节:
- 64 位的
movq和 128/256 位的vmovdqu根本不是合法的 MMIO 访问宽度,但硬件"有求必应",老老实实拆成连续多个 dword 事务 - 未对齐版本最骚:地址偏移 1 字节后,32 字节窗口横跨 9 个 dword 槽位,多一个事务就多排一次队(详见
vmovdqu_ymm_unaligned/README.md) - 各案例源码分别在
mov/mov.c、mov_rax/mov_rax.c、vmovdqu_xmm/vmovdqu_xmm.c等目录下
四、从 512 字节到 62 秒:冠军 fxrstor64 与"锤芯"策略
沿着同一条路继续放大,fxrstor64指令一次从 MMIO 拉取512 字节的 FPU/MMX/XMM 状态镜像——512 个字节每个都要穿越 PCIe 才能退休,单条指令就耗掉23.35 秒(fxrstor64/README.md)。
当前榜单冠军lock_hammer_fxrstor64更进一步(lock_hammer_fxrstor64/README.md):CPU 0 执行 512 字节的fxrstor64,其余所有核心组成"锤芯舰队",用紧凑的 4 字节读循环疯狂敲打另一个高延迟 MMIO 寄存器。每条读都是 non-posted 事务,把 PCIe 根复合体和 GPU 端点塞满在途事务——CPU 0 的加载只能排在所有"插队"流量后面。结果:62 秒,1980 亿周期,断层式第一。🏆
五、动手复现:本地跑一遍 PCIe 慢速寄存器寻宝
如果你有一台 AMD 平台机器(参考平台为 Ryzen 7 5800H + Radeon),可以完整复现这套测试:
- 克隆项目:
git clone https://gitcode.com/gh_mirrors/as/asm-hall-of-shame - 构建:在仓库根目录执行
make,根 Makefile 会递归编译所有子目录 - 运行 mov 测试:
sudo ./bin/mov/mov(输出最小周期数),加-v可看地址、基线与均值详情 - 换算时间:周期数转秒可借助
tools/cycles2time.c,榜单图表则由tools/make_graph.py生成
⚠️ 几点提醒:
- 所有测试都需
sudo(要读/dev/mem),且只读访问,请勿修改寄存器写入类目标 - 高延迟区域可能需要先用 mmiotic 做一次 prime,参考
mov/README.md中的探测命令 - 项目作者为 Christopher Domas,整个榜单的探索过程在根目录 README.md 的 x86 Leaderboard 一节中有完整记录
六、写在最后
一条mov的 139 毫秒背后,是 CPU、PCIe 协议与 GPU 内部实现三方"合谋"的延迟奇观:CPU 等 completion、MMIO 逐 dword 拆事务、GPU 内部慢状态机兜底。asm-hall-of-shame 用最硬核的方式告诉我们——性能优化的尽头不是更快,而是先知道最慢能慢到什么程度。从 1 周期的nop到 62 秒的fxrstor64,这份"耻辱榜"就是 x86 世界最生动的一份 PCIe/MMIO 寻宝地图。🗺️
【免费下载链接】asm-hall-of-shameRacing to the bottom of CPU performance项目地址: https://gitcode.com/gh_mirrors/as/asm-hall-of-shame
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考