Intel Gracemont 微架构评测¶
背景¶
之前 测试了 Intel Alder Lake 的 P 核微架构,这次就来测一下 Alder Lake 的 E 核微架构 Gracemont。
官方信息¶
Intel 关于 Gracemont 微架构有这些官方的信息:
- Intel Alder Lake CPU Architectures
- Alder Lake Architecture on Hot Chips 33
- Intel 64 and IA-32 Architectures Optimization Reference Manual Volume 1
现有评测¶
网上已经有较多针对 Gracemont 微架构的评测和分析,建议阅读:
- Gracemont: Revenge of the Atom Cores
- Intel’s Gracemont Small Core Eclipses Last-Gen Big Core Performance
下面分各个模块分别记录官方提供的信息,以及实测的结果。读者可以对照已有的第三方评测理解。官方信息与实测结果一致的数据会加粗。
Benchmark¶
Intel Gracemont 的性能测试结果见 SPEC。
前端¶
Fetch¶
官方信息:
- 2x 32B/cycle
Gracemont 的 Clustered Decode 架构比较特别,目前没有找到方法去证实它的 Fetch 带宽,后续如果找到了更好的方法,再测这个特性。
Decode¶
官方信息:
- 2x 3-wide
Gracemont 的 Clustered Decode 架构比较特别,目前没有找到方法去确认它 2x 3-wide 的 Decode 带宽,后续如果找到了更好的方法,再测这个特性。
L1 ICache¶
官方信息:
- 64KB
为了测试 L1 ICache 容量,构造一个具有巨大指令 footprint 的循环,由大量的 4 字节 nop 和最后的分支指令组成。观察在不同 footprint 大小下的 IPC:

可以看到 footprint 在 64 KB 之前时可以达到 5 IPC,之后则降到 3.25 IPC,这里的 64 KB 就对应了 L1 ICache 的容量。
L1 ITLB¶
官方信息:
- 64 entries, fully associative
构造一系列的 jmp 指令,使得 jmp 指令分布在不同的 page 上,使得 ITLB 成为瓶颈:

可以看到 64 个 Page 出现了明显的拐点,对应的就是 64 的 L1 ITLB 容量。过了拐点后,每次 jmp 的时间延长到了 16 个周期左右,包括了 L2 TLB 到 L1 ITLB 的 refill 时间。
Return Stack¶
用之前设计的 Return Stack 测试代码来测试 Gracemont,它的 call/ret 是成对的,也就是 ret 的返回地址不变,称这个版本为 A。此时发现不同调用深度下,都能做到 2 cycle 每 call/ret 对,没有观察到性能的下降,说明此时 Return Stack 并没有介入,应该是由 BTB 提供了预测。下面是 A 版本代码在 Gracemont 上的测试结果:

为了解决这个问题,修改代码,在函数里构造两个 call 去调用同一个函数,这样 ret 的返回地址就会变化了,称这个版本为 B。这时候跑出来的结果比较奇怪,周期数快速上升:

同样的 B 版本代码在 AMD Zen3 和 Apple Firestorm 的处理器上,可以观察到在符合预期的 Return Stack 大小处出现性能拐点,和 A 版本代码得到的结论一致。而 B 版本代码在 Golden Cove 上,会观察到在 6 的附近有一个性能下降如下图,但之前用 A 版本代码测得的拐点为 20:

这个区别背后的原因还需要进一步的分析。下面是两个版本的汇编代码的对比:
# version A
func_n:
call func_{n-1}
ret
# version B, generate two alternating call sites
func_n:
mov %rdi, %rsi
and $1, %rsi
je 2f
call func_{n-1}
jmp 3f
2:
call func_{n-1}
jmp 3f
3:
ret
后端¶
Rename¶
官方信息:
- 5-wide
在先前测试 L1 ICache 容量的时候,观察到的最大的 IPC 就是 5,此时测得的瓶颈在于 Rename 宽度,对应 5-wide Rename。
Execution Units¶
官方信息:
- 6 alu ports: 0/1/2/3/30/31
- P0: ALU/SHIFT
- P1: ALU/SHIFT/MUL/DIV
- P2: ALU/SHIFT/MUL/DIV
- P3: ALU/SHIFT
- P30: JMP
- P31: JMP
- 3 simd ports: 20/21/22
- P20: SALU/SIMUL/FMUL/FADD/FDIV/AES/SHA
- P21: SALU/FMUL/FADD/AES
- P22: SALU
- 2 load ports: 10/11
- 2 store address ports: 12/13
- 2 store data ports: 8/9
- 2 simd store data ports: 28/29
- ports 10/11/12/13 shares a reservation station
- ports 8/9/30/31 shares a reservation station
- ports 28/29 shares a reservation station
- ports 20/21/22 shares a reservation station
实测各类指令的吞吐:
- NOP: 5 IPC
- ALU: 4 IPC
LSU¶
官方信息:
- 2x 16B Load/cycle, 2x 16B Store/cycle
- Load latency 3-4 cycle
Load Store 带宽¶
针对 Load Store 带宽,实测每个周期可以完成:
- 2x 128b Load
- 2x 128b Load + 2x 128b Store
- 2x 128b Store
- 1x 256b Load
- 1x 256b Load + 1x 256b Store
- 1x 256b Store
最大的读带宽是 32B/cyc,最大的写带宽是 32B/cyc,二者可以同时达到。
Store to Load Forwarding¶
官方信息:
- Loads that forward from stores can do so in the same load to use latency as cache hits for cases where the store's address is known, and the store data is available.
经过实际测试,Gracemont 上如下的情况可以成功转发,对地址 x 的 Store 转发到对地址 y 的 Load 成功时 y-x 的取值范围:
| Store\Load | 8b Load | 16b Load | 32b Load | 64b Load |
|---|---|---|---|---|
| 8b Store | {0} | {} | {} | {} |
| 16b Store | {0} | {0} | {} | {} |
| 32b Store | {0} | {0} | {0} | {} |
| 64b Store | {0} | {0} | {0,4} | {0} |
可以看到,Gracemont 在 Store 包含 Load 且地址相同时可以转发。特别地,针对 64b Store 到 32b Load 转发还允许 y-x=4。各种情况下的 CPI:
- 转发成功时,CPI 比较复杂,有的情况是介于 0.5 到 2 之间,有时候又是介于 2 到 4 之间,有时候是 6
- 重合但不能转发时,CPI 等于 11,特殊情况下还出现了 28.5
不支持多个 Store 对同一个 Load 的转发。跨缓存行时不能转发。
即使 Load 和 Store 不重合,但在一定情况下,也会出现 CPI 等于 11 的情况,例如:
- 对地址 3 的 16b Store 转发到地址 0 的 8b Load
- 对地址 1 的 64b Store 转发到地址 9/10/11 的 64b Load
- 对地址 0 的 8b Store 转发到地址 1/2/3 的 64b Load
- 对地址 0 的 8b Store 转发到地址 3 的 16b Load
在以上几种情况下,Load 和 Store 访问范围并不重合,但性能和访问范围重合且转发失败时相同(CPI 等于 11),由此猜测 Gracemont 判断是否重合是以对齐的 4B 为粒度,如果 Load 和 Store 访问了相同的对齐的 4B 块,即使不重合,一定情况下也可能会被当成重合的情况来处理,但由于实际上并没有重合,就没法转发,性能就比较差。
小结:Gracemont 的 Store to Load Forwarding:
- 1 ld + 1 st: 要求 st 完全包含 ld 且地址相同且不能跨缓存行;特别地,64b Store 到 32b Load 转发允许 y-x=4
- 1 ld + 2+ st: 不支持
Load to Use Latency¶
测试不同场景下的 Load to Use Latency:
mov 0(%rsi), %rsi: 3 cycle,但在跨越 64B 缓存行边界时退化到 11 cyclemov 8(%rsi), %rsi: 3 cyclemov 0(%rsp, %rsi, 8), %rsi: 4 cyclemov 0(%rsi, %rdx, 8), %rsi: 4 cycle- Load to ALU Latency: 4 cycle
Memory Dependency Predictor¶
为了让 Load 预测执行,需要保证 Load 和之前的 Store 访问的内存没有 Overlap,那么就需要有一个预测器来预测 Load 和 Store 之间在内存上的依赖。这个预测器就是 Memory Dependency Predictor,负责预测是否有依赖。如果没有依赖,Load 就可以提前执行,但如果实际上有依赖,就需要回滚。
参考 Rage Against the Machine Clear: A Systematic Analysis of Machine Clears and Their Implications for Transient Execution Attacks 和 Memory Disambiguation on Skylake 的方法,构造一对 Store-Load,通过延迟 Store 地址的计算,从周期数可以区分出硬件是否进行了预测,以及预测正确与否:
; Listing 4 of Rage Against the Machine Clear: A Systematic Analysis of Machine Clears and Their Implications for Transient Execution Attacks
st_ld: ;rdi: store addr, rsi: load addr
%rep 10 ;Trick to delay the store address
imul rdi, 1
%endrep
mov DWORD [rdi], 0x42 ;Store
mov eax, DWORD [rsi] ;Load
%rep 10 ;Pronounce load timing
imul eax, 1
%endrep
ret
对于实际上没有依赖的 Load,如果正确预测了,就可以提前执行,那么周期数就会比较少;对于实际上有依赖的 Load,如果错误预测了,因为提前执行后又回滚,周期数会更多。
测试时,让这对 Store-Load 采用相同/不同的地址进行访存,具体地,首先是 100 次相同地址(有依赖),然后 20 次不同地址(无依赖),最后 10 次相同地址(有依赖),每次执行的周期数如下:

可见当预测器被训练为 Store-Load 有依赖之后,经过 3 次 Store-Load 无依赖(横坐标 100 到 102)的训练以后,从第 4 次(横坐标 103)开始成功预测了无依赖的情况,使得 Load 可以提前执行,表现为周期数的明显减少。而当 Store-Load 再次出现依赖(横坐标 120)时,因为错误预测,出现了周期数的明显增加,并且下一次执行 Store-Load(横坐标 121)就能正确预测出有依赖。这与论文中针对 Skylake 的逆向结果类似:从初始状态开始(通过大量的有依赖来重置状态),连续无依赖 3 次以后,才会被预测为无依赖,且只要有一次有依赖,就会被预测为有依赖。对应的内部实现是,硬件对这个 Load 维护一个 2-bit 的饱和计数器,有依赖时清零,无依赖时加一,当累加到最大值 3 时,预测为无依赖,否则就是有依赖。Gracemont 和 Skylake 以及 Golden Cove 的逻辑类似,不过计数器的位数更少,更容易预测出无依赖。
上面的测试只证明了有 2-bit 的计数器,且无依赖时加一,累加到 3 时才预测为无依赖,但并没有证明它在有依赖时清零,也可能是通过减一,或其他不会减到零的情况。下面修改一下访存模式来证明,即累加到 3 后,先来一次有依赖,再来多次无依赖,就可以观察到下面的结果:

可见,一次有依赖过后,又需要 3 次无依赖,才能预测为无依赖。这证明了前面的表述,即 2-bit 饱和计数器,无依赖时加一,有依赖时置零,当计数器等于 3 时,预测为无依赖。
接下来测试这些计数器是怎么维护的。方法是,设置两个 Store-Load 对,其中第一对总是有依赖,第二对总是没有依赖,调整两个 Load 指令的地址,看看什么时候会出现性能下降。出现性能下降就意味着这两个 Load 指令被映射到了同一个 2-bit 饱和计数器上,那么根据上面的规律,它们总是会被预测为有依赖。测试结果如下:

可见当两个 Load 地址在低 16 位相同时,会被映射到同一个计数器上。不过这并不代表它就有 65536 个计数器,下面来测试一下实际有多少。思路是,构造多对 Store-Load,让它们的 Load 地址的低 16 位不同,然后都让它们有依赖,观察到多少对 Store-Load 时出现性能下降。通过 Store-to-Load Forwarding and Memory Disambiguation in x86 Processors 的 Fast Data 测试方法,可得它的容量是 26:

猜测它是一个 26 路全相联的设计,每个表项有 16-bit 的 tag(取自 Load PC 低 16 位)和 2-bit 饱和计数器。未命中时预测无依赖,如果实际有依赖则插入表项,计数器置零。命中时根据计数器值预测:无依赖加一,有依赖置零,到 3 时预测无依赖。另一种可能是计数器到 3 时表项被删除,效果相同。究竟是哪种还有待后续研究,不过从利用率的角度来说,删除的可能性更大。这种设计和 ARM Neoverse N2 比较类似,和 Golden Cove 又不太一样,三者对比如下:
| Gracemont | Neoverse N2 | Golden Cove | |
|---|---|---|---|
| 容量 | 26 | 32 | 512 |
| tag 位数 | 16 | 15 | N/A,用 PC[8:0] 直接映射 |
| ctr 位数 | 2 | 4 | 4 |
| 新表项 ctr 取值 | 0 | 1 | N/A,表项总是存在 |
| 不命中时预测为 | 无依赖 | 无依赖 | N/A,总是命中 |
| 命中时预测为 | 有依赖 | 有依赖 | ctr==15 时预测无依赖,否则有依赖 |
| 命中时有依赖 | ctr=0 | ctr-=1 | ctr=0 |
| 命中时无依赖 | ctr+=1 | ctr+=1 | ctr+=1 |
| ctr 等于多少时 evict | 3 | 15 | N/A,不会 evict |
| 是否有全局预测器 | 无 | 无 | 有,可以覆盖局部预测器的结果,强制预测为有依赖 |
L1 DCache¶
官方信息:
- 32KB
- dual ported
L1 DTLB¶
官方信息:
- 32 entries, fully associative
用类似测 L1 DCache 的方法测试 L1 DTLB 容量,只不过把 pointer chasing 链的指针分布在不同的 page 上,使得 DTLB 成为瓶颈。奇怪的是,虽然官方信息写的是 32-entry 的 L1 DTLB,但是实测它有 48-entry:

这个观察和 Meteor Lake’s E-Cores: Crestmont Makes Incremental Progress 是一致的,怀疑是 Intel 写错了数据。
L2 TLB¶
官方信息:
- 4-way 2048 entries for 4K/2M pages
- fully associative 8 entries for 1GB pages
- 4 page walkers
使用类似 L1 DTLB 的方式去测试 L2 TLB,在 2048 附近观察到了拐点:

这个结果和官方数据是吻合的。
L2 Cache¶
官方信息:
- 2MB/4MB Shared among 4 cores
- 64 B/cycle shared among 4 cores
- 17 cycle latency
ReOrder Buffer¶
官方信息:
- 256 entries
- 8 wide retirement
为了测试 ROB 的大小,设计了一个循环,循环开始和结束是长延迟的 long latency load。中间是若干条 NOP 指令,当 NOP 指令比较少时,循环的时候取决于 load 指令的时间;当 NOP 指令数量过多,填满了 ROB 以后,就会导致 ROB 无法保存循环末尾的 load 指令,性能出现下降。测试结果如下:

当 NOP 数量达到 256 时,性能开始急剧下滑,说明 Golden Cove 的 ROB 大小是 256。