moe*_*ep0 5 cpu x86 assembly intel cpu-architecture
我正在尝试使用我的 Intel i7-10700 和 ubuntu 20.04 来验证两个可熔断对可以在同一时钟周期内解码的结论。
测试代码排列如下,复制了8000次左右,以避免LSD和DSB的影响(主要使用MITE)。
ALIGN 32
.loop_1:
dec ecx
jge .loop_2
.loop_2:
dec ecx
jge .loop_3
.loop_3:
dec ecx
jge .loop_4
.loop_4:
.loop_5:
dec ecx
jge .loop_6
Run Code Online (Sandbox Code Playgroud)
测试结果表明,单个循环中仅融合一对。( r479 div r1002479 )
Performance counter stats for process id '22597':
120,459,876,711 cycles
35,514,146,968 instructions # 0.29 insn per cycle
17,792,584,278 r479 # r479: Number of uops delivered
# to Instruction Decode Queue (IDQ) from MITE path
50,968,497 r4002479
17,756,894,879 r1002479 # r1002479: Cycles MITE is delivering any Uop
26.444208448 seconds time elapsed
Run Code Online (Sandbox Code Playgroud)
我不认为阿格纳的结论是错误的。因此,我的性能使用是否有问题,或者我未能在代码中找到见解?
在 Haswell 及更高版本上,是的。在常春藤桥和更早的地方,没有。
Agner Fog 表示,在 Ice Lake 及之后的版本中,宏融合是在解码后立即完成的,而不是在解码器中完成,后者需要预解码器将正确的 x86 机器代码块相应地发送到解码器。(Ice Lake 的限制略有不同:与以前的 CPU 型号不同,带有内存操作数的指令无法融合。带有立即数操作数的指令可以融合。)因此在 Ice Lake 上,宏融合不允许解码器处理超过 5 条指令每个时钟。
Wikichip声称在 Ice Lake 上每个时钟只能进行 1 次宏融合,但这可能是不正确的。Harold在 Rocket Lake 上使用我的微基准测试进行测试,发现与 Skylake 相同的结果。(Rocket Lake使用 Cypress Cove 核心,这是 Sunny Cove 的变体,向后移植到 14nm 工艺,因此在这方面它很可能与 Ice Lake 相同。)
您的结果表明uops_issued.any大约是一半instructions,因此您看到大多数对的宏观融合。(您还可以查看uops_retired.macro_fusedperf 事件。顺便说一句,现代perf对大多数 uarch 特定事件都有符号名称:使用perf list来查看它们。)
不过,解码器在 Skylake 衍生的微架构上每个时钟仍会产生多达 4 个甚至 5 个 uops,即使它们只进行两次宏融合。您没有查看 MITE 有多少个周期处于活动状态,因此在大多数情况下您看不到执行停止,直到 ROB / RS 中有空间容纳 4 个 uops 的问题组。这会在 IDQ 中为 MITE 的解码组开辟空间。
循环传递依赖dec ecx:只有 1 个/时钟,因为每个时钟dec都必须等待前一个时钟的结果准备好。
每个周期只能执行一个采取的dec分支(在端口 6 上),几乎每次都会采取/ jge,除了 2^32 中的 1(当 dec 之前 ECX 为 0 时)。
端口 0 上的另一个分支执行单元仅处理预测未采用的分支。 https://www.realworldtech.com/haswell-cpu/4/显示了布局,但没有提及该限制;Agner Fog的微架构指南就是如此。
分支预测:即使跳转到下一条指令(在架构上是 NOP),CPU 也不是特殊情况。慢速 jmp 指令(因为真正的代码没有理由这样做,除了call +0/ pop,至少对于返回地址预测器堆栈来说是特殊情况。)
这就是为什么每个时钟执行的指令远少于一条指令,更不用说每个时钟执行一个微指令了。
令我惊讶的是,MITE并没有继续解码单独的信号test,并且jcc在进行两次融合的同一周期中。我猜解码器已针对填充 uop 缓存进行了优化。(对 Sandybridge / IvyBridge 的类似影响是,如果解码组的最后一个 uop 可能是可熔断的,例如dec,则解码器将在该周期中仅产生 3 个 uop,预计可能会熔断下一个dec周期。至少在 SnB/ 上是这样。 IvB,其中解码器每个周期只能进行 1 次融合,并且如果同一解码组中存在另一对微指令,则将解码单独的 ALU + jcc uop。这里,SKL 选择在生成两个微指令后不解码单独的微指令(test和jcc另一个test)融合。)
global _start
_start:
mov ecx, 100000000
ALIGN 32
.loop:
%rep 399 ; the loop branch makes 400 total
test ecx, ecx
jz .exit_loop ; many of these will be 6-byte jcc rel32
%endrep
dec ecx
jnz .loop
.exit_loop:
mov eax, 231
syscall ; exit_group(EDI)
Run Code Online (Sandbox Code Playgroud)
在 i7-6700k Skylake 上,仅针对用户空间的性能计数器:
$ nasm -felf64 fusion.asm && ld fusion.o -o fusion # static executable
$ taskset -c 3 perf stat --all-user -etask-clock,context-switches,cpu-migrations,page-faults,cycles,instructions,uops_issued.any,uops_executed.thread,idq.all_mite_cycles_any_uops,idq.mite_uops -r2 ./fusion
Performance counter stats for './fusion' (2 runs):
5,165.34 msec task-clock # 1.000 CPUs utilized ( +- 0.01% )
0 context-switches # 0.000 /sec
0 cpu-migrations # 0.000 /sec
1 page-faults # 0.194 /sec
20,130,230,894 cycles # 3.897 GHz ( +- 0.04% )
80,000,001,586 instructions # 3.97 insn per cycle ( +- 0.00% )
40,000,677,865 uops_issued.any # 7.744 G/sec ( +- 0.00% )
40,000,602,728 uops_executed.thread # 7.744 G/sec ( +- 0.00% )
20,100,486,534 idq.all_mite_cycles_any_uops # 3.891 G/sec ( +- 0.00% )
40,000,261,852 idq.mite_uops # 7.744 G/sec ( +- 0.00% )
5.165605 +- 0.000716 seconds time elapsed ( +- 0.01% )
Run Code Online (Sandbox Code Playgroud)
未采用的分支不是瓶颈,也许是因为我的循环足够大,足以击败 DSB(uop 缓存),但又不会太大而无法击败分支预测。(实际上, Skylake 上的JCC 勘误缓解肯定会击败 DSB:如果一切都是宏融合分支,那么就会有一个触及每个 32 字节区域的末尾。只有当我们开始在分支之间引入 NOP 或其他指令时, uop 缓存能够运行。)
我们可以看到所有内容都已融合(40G 微指令中的 80G 指令),并以每个时钟 2 个测试和分支微指令(20G 周期)执行。此外,MITE 每个周期都提供 uops,即 20G MITE 周期。它所提供的显然是每个周期 2 uops,至少平均而言是这样。
使用交替的 NOP 组和未采用的分支进行测试可能会很好地了解当 IDQ 有空间从 MITE 接受更多微指令时会发生什么,看看它是否会将非融合测试和 JCC 微指令发送到 IDQ。
所有分支jcc rel8的向后没有区别,相同的性能结果:
%assign i 0
%rep 399 ; the loop branch makes 400 total
.dummy%+i:
test ecx, ecx
jz .dummy %+ i
%assign i i+1
%endrep
Run Code Online (Sandbox Code Playgroud)
NOP 仍然需要解码,但后端可以破解它们。这使得总 MITE 吞吐量成为唯一的瓶颈,而不是限制在 2 uops/时钟,无论可以产生多少 MITE。
global _start
_start:
mov ecx, 100000000
ALIGN 32
.loop:
%assign i 0
%rep 10
%rep 8
.dummy%+i:
test ecx, ecx
jz .dummy %+ i
%assign i i+1
%endrep
times 24 nop
%endrep
dec ecx
jnz .loop
.exit_loop:
mov eax, 231
syscall ; exit_group(EDI)
Run Code Online (Sandbox Code Playgroud)
Performance counter stats for './fusion':
2,594.14 msec task-clock # 1.000 CPUs utilized
0 context-switches # 0.000 /sec
0 cpu-migrations # 0.000 /sec
1 page-faults # 0.385 /sec
10,112,077,793 cycles # 3.898 GHz
40,200,000,813 instructions # 3.98 insn per cycle
32,100,317,400 uops_issued.any # 12.374 G/sec
8,100,250,120 uops_executed.thread # 3.123 G/sec
10,100,772,325 idq.all_mite_cycles_any_uops # 3.894 G/sec
32,100,146,351 idq.mite_uops # 12.374 G/sec
2.594423202 seconds time elapsed
2.593606000 seconds user
0.000000000 seconds sys
Run Code Online (Sandbox Code Playgroud)
所以看来 MITE 无法跟上 4-wide 的问题。 8 个分支的块使解码器每个时钟产生的微指令明显少于 5 个;可能只有 2 个像我们在较长时间运行中看到的那样test/jcc。
24 nops 可以解码
减少到 3 个测试/jcc 和 29 个测试组,nop可将 MITE 活动 8.600 个周期降至 8.607 Gcycle,并具有 32.100G MITE uops。(3.099 G uops_retired.macro_fused,其中 .1 来自循环分支。)仍然没有使前端达到每个时钟 4.0 uops 的饱和度,就像我希望在一个解码组末尾进行宏融合时那样。
它达到了 4.09 IPC,因此至少解码器和问题瓶颈领先于没有宏融合的情况。
(宏融合的最佳情况是 6.0 IPC,每个周期有 2 个融合,以及来自非融合指令的 2 个其他微指令。这与通过微融合的未融合域后端微指令吞吐量限制分开,请参阅此测试,每个周期约7 个微指令uops_executed.thread钟。)
即使%rep 2test/JCC 也会损害吞吐量,这似乎表明它在进行 2 次融合后只是停止解码,甚至在此之后不再解码 2 或 3 个 NOP。(对于一些较低的 NOP 计数,我们会得到一些 uop 缓存活动,因为外部代表计数不足以完全填满 uop 缓存。)
您可以在 shell 循环中测试它,就像for NOPS in {0..20}; do nasm ... -DNOPS=$NOPS ...使用源代码一样times NOPS nop。
总周期与 NOPS 数量之间存在一些平台/阶跃效应%rep 2,因此可能两个测试/JCC uops 在组末尾解码,在它们之前有 1、2 或 3 个 NOP。(但这并不是非常一致,特别是对于较低数量的 NOPS。但是 NOPS=16、17 和 18 都在 5.22 Gcycle 左右,14 和 15 都是 4.62 Gcycle。)
如果我们想真正了解正在发生的情况,有很多可能相关的性能计数器,例如idq_uops_not_delivered.cycles_fe_was_ok(问题阶段获得 4 uops 的周期,或者后端停滞的情况,因此这不是前端的错.)
| 归档时间: |
|
| 查看次数: |
242 次 |
| 最近记录: |