← 文章 / 芯片硬件
Lobsters 2026/8/8 · 2026-08-08 13:42:36 · 40 阅读

汇编耻辱堂:CPU 单指令性能下限大比拼

指令延迟分析通常关注的是性能优化——让代码跑得尽可能快。Assembly Hall of Shame(汇编耻辱堂)则反其道而行:寻找单条指令性能的绝对下限。

🏆 当前冠军 🏆

x86fxrstor64

策略:使用 fxrstor64 从 PCIe 总线中一个高延迟的 MMIO 区域加载 512 字节的 FPU/MMX/XMM 状态,然后在加载期间让总线"挨饿"——一队"打桩核"用紧凑的 4 字节读操作猛敲另一个高延迟 MMIO 寄存器,用未发布(non-posted)事务把 PCIe 根复合体和终端设备彻底打满,这样 CPU 0 的 512 字节 fxrstor64 就只能排在这些争用流量的后面慢慢等。

挑战者:AMD Ryzen 7 5800H

; CPU 0 — 被计时的指令
movl $0xfcc68830, %rsi
fxrstor64 %rsi

; CPU 1..N — 针对另一个高延迟地址的打桩循环
movl 0xfcc68858, %eax

🏆 得分:198,002,498,236 周期

🏆 耗时:62 秒

荣誉提名

smiiiiiiiiiiiiiiii 中,有人利用一条违反规范的 未对齐 ymm0 加载,迫使 GPU 寄存器产生未发布的 dword 事务,从而突破了系统管理模式(SMM)的根本设计。

vmovdqu 0xfcc003b1, %ymm0

规则

  • 指令可以使用任何必要的准备,但只有单条指令本身计入得分。
  • 对于陷入/模拟/虚拟化的指令,只能计入陷入本身的时间,不算处理器部分。
  • 指令必须不可中断。rep movspause 等一律取消资格。
  • 时间按 CPU 基础时钟频率归一化。
  • 所有平台必须保持出厂默认配置,不得改装硬件。

x86 排行榜

27. nop

策略nop 啥也不干。它也就只能在排行榜上垫底了。

挑战者:Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz

nop

得分:1 周期

耗时:0 纳秒

26. nop16

策略:普通的 nop 太短了,但怎么让"什么都不做"花更长时间呢?试试超长 nop

参赛选手:Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz

data16 data16 data16 data16 data16 data16 data16 nopl 0x00000000(%%eax,%%eax,1)

得分:20 个周期

耗时:7 纳秒

25. rdtsc

策略:只是一条参考指令,用来校准基准。

参赛选手:Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz

rdtsc

得分:49 个周期

耗时:18 纳秒

24. idiv

策略:用 128 位被除数(rdx:rax=2:0)配以小除数,使商超过符号扩展所设定的上限,从而走完除法器微码的最长路径。

参赛选手:Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz

xorq %rax, %rax   ; rax = 0  (低 64 位被除数)
movq $2, %rdx     ; rdx = 2  (高 64 位:完整被除数 = 2^65)
movq $5, %rbx     ; 除数 → 商 = 2^65/5 ≈ 7.4×10^18
idivq %rbx

得分:77 个周期

耗时:28 纳秒

23. enter

策略:使用最大嵌套深度(31),强制走 30 次显示指针加载与压入的微码遍历路径。

参赛选手:Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz

enter $0, $31       ; 分配 0 字节,嵌套深度 31(最大值)

得分:112 个周期

耗时:41 纳秒

22. fldl

策略:试一个小非规格化数,触发浮点微码辅助例程。

参赛选手:Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz

    movabsq $0x0000000000000001, %rax
    movq    %rax, -8(%rsp)
    fldl    -8(%rsp)

得分:133 个周期

耗时:49 纳秒

21. clflush

策略:只要确保缓存行是脏的就行。

参赛选手:Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz

clflush (%rax)          ; rax -> 驻留在 L3 中的脏缓存行

得分:165 个周期

耗时:60 纳秒

20. fsin

策略:用指数 0x7ff 触发微码中的"特殊值"处理路径;正负号、NaN/inf 似乎没区别,直接用 QNaN。

参赛选手:Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz

    movabsq $0x7fffffffffffffff, %rax
    movq    %rax, -8(%rsp)
    fldl    -8(%rsp)
    fsin

得分:257 个周期

耗时:94 纳秒

19. mfence

策略:用 movnti 向不同的缓存行写入,把所有写合并行填充缓冲区(LFB)打满,迫使 mfence 在执行完毕前必须把整个 LFB 写路径排空到 uncore。

参赛选手:Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz

movnti %r9,  0*64(%rdi)   ; ×16 条不同的缓存行 —— 打满写合并 LFB
; …
movnti %r9, 15*64(%rdi)
mfence                     ; 执行完毕前必须排空所有挂起的 LFB 写入

得分:326 个周期

耗时:120 纳秒

18. mov cr3

策略:暂时没什么花招,就是看看让 TLB 失效要花多久。

参赛选手:AMD Ryzen 7 5800H with Radeon Graphics(Trigkey S5)

mov %rax, %cr3

得分:352 个周期

耗时:110 纳秒

17. fadd

策略:用非规格化源操作数触发 x87 浮点的微码辅助路径。

参赛选手:Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz

fldl   subnorm    ; 1e-310:小于 DBL_MIN 的值,偏移指数为 0
faddl  subnorm    ; 源操作数是次正规数 → 触发 FP 微码辅助

得分:677 个周期

耗时:249 纳秒

16. split lock

策略:让带 lock 前缀的操作数跨越缓存行边界,迫使 CPU 走外部总线锁定而非 MESI 缓存一致性快速路径。

选手:Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz

; split_ptr % 64 == 63 — dword 跨越第 N 行的字节 63 与第 N+1 行的字节 64–66
lock xaddl %r9d, (%rdi)

得分:865 个周期

耗时:319 纳秒

15. fdiv -

策略:使用次正规数作为除数,硬件将控制权交给微码辅助例程,辅助例程先对操作数进行规范化,再执行除法,最后恢复架构状态。

选手:Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz

    movabsq $0x3ff0000000000000, %rax   ; 1.0(正规被除数)
    movq    %rax, -8(%rsp)
    fldl    -8(%rsp)                     ; ST(0) = 1.0

    movabsq $0x0000002000000000, %rax   ; 6.79e-313(次正规除数)
    movq    %rax, -8(%rsp)
    fdivl   -8(%rsp)                     ; ST(0) = 1.0 / 次正规数 → FP 辅助

得分:883 个周期

耗时:325 纳秒

14. cpuid

策略:用 rakefield 找出延迟最高的 CPUID leaf。

选手:Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz

movl $6, %eax
cpuid

得分:1248 个周期

耗时:460 纳秒

13. rdrand

策略:在紧凑循环中持续调用 rdrand,以比硬件熵池补充更快的速度耗尽熵源,迫使后续调用阻塞等待熵源恢复。

选手:Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz

rdrand %rax

得分:5,579 个周期

耗时:2.057 微秒

12. wrmsr

策略:用 project:nightshyft 找出高延迟的 MSR。Zen 上的 MCG_CTL 看起来很有戏:可能是微码静默并跨硬件单元(其中一些可能跨晶粒)同步 MCA 错误组,需要走 fabric 级通信,而不是一次简单的本地寄存器写。

参赛选手:AMD Ryzen 7 5800H with Radeon Graphics(Trigkey S5)

movl $0x17b, %ecx       ; MCG_CTL
wrmsr

得分:34,304 周期

耗时:10.742 微秒

11. out

策略:瞄准一个跨越网卡设备寄存器边界的 I/O 端口,让设备每次写入时都静默其 TX DMA 引擎。

参赛选手:AMD Ryzen 7 5800H with Radeon Graphics(Trigkey S5)

mov $0xf019, %dx
outl %eax, %dx

得分:49,857 周期

耗时:15.580 微秒

10. rdmsr

策略:用 project:nightshyft 找出高延迟的 MSR:VIA 有一个未公开的寄存器 0x133,响应时间长得离谱,也不知道它是干什么的。

参赛选手:VIA Eden Processor 800MHz

movl $0x133, %ecx ; undocumented MSR
rdmsr

得分:161,602 周期

耗时:202.004 微秒

9. wbinvd

策略:把 L1/L2/L3 缓存全部填满脏行,强制把整条缓存层级写回 DRAM。

参赛选手:AMD Ryzen 7 5800H with Radeon Graphics(Trigkey S5)

wbinvd

得分:1,616,480 周期

耗时:506.165 微秒

8. in

策略:瞄准映射到 ACPI PM 块的 I/O 端口,那里的一次未对齐 4 字节读会被解码成多次 non-posted 读,源地址取决于这个端口最终走到哪里。

参赛选手:AMD Ryzen 7 5800H 集成 Radeon 显卡(Trigkey S5)

mov $0x0413, %dx
inl %dx, %eax

得分:12,524,415 个时钟周期

耗时:3.921769 毫秒

7. mov

策略:用 mmiotic 找出 PCIe 互联中延迟最高的空闲区域,访问一个未知的 GPU 寄存器。

参赛选手:AMD Ryzen 7 5800H 集成 Radeon 显卡(Trigkey S5)

movl 0xfcc003b0, %esi

得分:443,937,696 个时钟周期

耗时:139.010268 毫秒

6. mov rax -

策略:在 MMIO 地址空间里搜索 PCIe 互联中最慢的寄存器,访问一个未知的 GPU 寄存器,并用 8 字节 MMIO 读取来触发两次 dword 寄存器访问——严格来说这是不允许的,但确实能用。

参赛选手:AMD Ryzen 7 5800H 集成 Radeon 显卡(Trigkey S5)

movq 0xfcc003b0, %rax

得分:887,716,864 个时钟周期

耗时:277.971228 毫秒

5. vmovdqu xmm -

策略:在 MMIO 地址空间里搜索 PCIe 互联中最慢的寄存器,访问一个未知的 GPU 寄存器,并用 16 字节 MMIO 读取来触发四次 dword 寄存器访问——严格来说这是不允许的,但确实能用。

参赛选手:AMD Ryzen 7 5800H 集成 Radeon 显卡(Trigkey S5)

vmovdqu 0xfcc003b0, %xmm0

得分:1,774,555,776 个时钟周期

耗时:555.664133 毫秒

4. vmovdqu ymm -

策略:在 MMIO 地址空间里搜索 PCIe 互联中最慢的寄存器,访问一个未知的 GPU 寄存器,并用 32 字节 MMIO 读取来触发八次 dword 寄存器访问——依然严格来说不允许,但确实能用。

参赛选手:AMD Ryzen 7 5800H 集成 Radeon 显卡(Trigkey S5)

vmovdqu 0xfcc003b0, %ymm0

得分:3,549,079,296 个时钟周期

耗时:1.111345034 秒

3. vmovdqu ymm(未对齐) -

策略:在 PCIe 互联中搜索最慢的 MMIO 寄存器空间,命中某个未知的 GPU 寄存器,用 32 字节未对齐的 MMIO 读操作触发九次双字寄存器访问——比起对齐版本更加"不合规",但照常能跑。

参赛选手:AMD Ryzen 7 5800H with Radeon Graphics(Trigkey S5)

vmovdqu 0xfcc003b1, %ymm0

得分:4,453,212,256 个时钟周期

耗时:1.394428818 秒

2. fxrstor64(基线版本) -

策略:用 mmiotic 定位 PCIe 互联中的高延迟死区,隔离出 0 附近的某段区域并偏移状态以避免 MXCSR 被破坏(可能是 VGA 缓冲区?),然后用 fxrstor64 从 MMIO 加载 512 字节的 FPU/MMX/XMM 状态,强制 CPU 透过最慢的内存窗口处理 512 字节的 I/O 事务。

参赛选手:AMD Ryzen 7 5800H

movl $0xfcc68830, %rsi
fxrstor64 %rsi

得分:74,584,168,512 个时钟周期

耗时:23.354502677 秒

1. 🏆 fxrstor64 🏆

策略:在 fxrstor64(基线版本) 的基础上,在 fxrstor64 加载过程中让互联陷入"饥饿"——一组"打手"核心对着另一个高延迟 MMIO 寄存器疯狂发起紧凑的 4 字节读,把 PCIe 根联合体和终端设备用未发布事务(non-posted transaction)淹没,让 CPU 0 的 512 字节 fxrstor64 不得不排在这堆竞争流量后面。

参赛选手:AMD Ryzen 7 5800H with Radeon Graphics(Trigkey S5)

; CPU 0 — 计时指令
movl $0xfcc68830, %rsi
fxrstor64 %rsi

; CPUs 1..N — 对另一个高延迟位置的打手循环
movl 0xfcc68858, %eax

🏆 得分:198,002,498,236 个时钟周期

🏆 耗时:62 秒

??. xrstor64(AMX,MMIO)

策略:在 Sapphire Rapids 上结合 fxrstor64 的 MMIO 思路,启用扩展 AVX 状态:xsave 的状态区域为 8KB,对比 512 字节大了 16 倍 → 1,000,000,000,000 个时钟周期

参赛选手:TODO

; XCR0 必须启用 AMX 组件(位 17-18);状态区域约 8KB
xrstor64 (%rsi)         ; rsi -> MMIO 区域,与 fxrstor64 手法相同

ARM 排行榜

  • 待定。

RISC-V 排行榜

  • 待定。

作者

汇编耻辱堂由 Christopher Domas(@xoreaxeaxeax)发起研究。

原始来源: Lobsters

评论 (0)