最近的英特尔微架构中的简单解码器能否处理所有 1-µop 指令?
Can the simple decoders in recent Intel microarchitectures handle all 1-µop instructions?
最近的 Intel CPU 的前端包含一个复杂的解码器和一些简单的解码器。复杂解码器可以处理解码为多个微操作的指令,而简单解码器仅支持解码为单个(融合域)微操作的指令。
简单解码器是否可以解码所有 1-µop 指令,或者是否存在只能由复杂解码器处理的 1-µop 指令?
不行,有些指令只能解码1/clock
Andreas 的评论表明 xor eax,eax
/ setnle al
似乎有 1/clock 的解码瓶颈。我在 cdq
上发现了同样的事情:读取 EAX,写入 EDX,从 DSB(uop 缓存)也明显 运行s 更快,并且不涉及 partial-registers 或任何奇怪的东西,并且不需要 dep-breaking 指令。
更好的是,作为一条 single-byte 指令,它只需一小段指令就可以击败 DSB。 (导致在某些 CPU 上的测试产生误导性的结果,例如在 Agner Fog 的表和 https://uops.info/, e.g. SKX shown as 1c throughput.) https://www.uops.info/html-tp/SKX/CDQ-Measurements.html vs. https://www.uops.info/html-tp/CFL/CDQ-Measurements.html 上,由于不同的测试方法,吞吐量不一致:只有 Coffee Lake 测试曾经测试过足够小的展开计数(10)破坏 DSB,找到 0.6 的吞吐量。(一旦考虑循环开销,实际吞吐量为 0.5,完全由 back-end 与 cqo
相同的端口压力解释。IDK 为什么你会找到 0.6 而不是0.55,循环中 p6 只有一个额外的 uop。)
(Zen 可以 运行 此指令具有 0.25c 的吞吐量;没有奇怪的解码问题并由每个 integer-ALU 端口处理。)
dec/jnz 循环中的 times 10 cdq
可以 运行 来自 uop 缓存,并且 运行s 在 Skylake 上以 0.5c 的吞吐量(p06)加上循环开销也竞争p6.
times 20 cdq
对于一个 32 字节的机器代码块来说超过 3 个 uop 缓存行,这意味着循环只能 运行 来自传统解码(循环顶部对齐)。在 Skylake 上,这个 运行s 每 cdq
1 个周期。性能计数器确认 MITE 每个周期提供 1 uop,而不是 3 或 4 组,空闲周期介于两者之间。
default rel
%ifdef __YASM_VER__
CPU Skylake AMD
%else
%use smartalign
alignmode p6, 64
%endif
global _start
_start:
mov ebp, 1000000000
align 64
.loop:
;times 10 cdq ; 0.5c throughput
;times 20 cdq ; 1c throughput, 1 MITE uop per cycle front-end
; times 10 cqo ; 0.5c throughput 2-byte insn fits uop cache
; times 10 cdqe ; 1c throughput data dependency
;times 10 cld ; ~4c throughput, 3 uops
dec ebp
jnz .loop
.end:
xor edi,edi
mov eax,231 ; __NR_exit_group from /usr/include/asm/unistd_64.h
syscall ; sys_exit_group(0)
在我的 Arch Linux 桌面上,我在 perf:
下将其构建到 运行 的静态可执行文件中
- i7-6700k with epp=balance_performance(最大“turbo”= 3.9GHz)
- 微码修订版 0xd6(所以 LSD 被禁用,这并不重要:如果所有 uops 都在 DSB uop 缓存 IIRC 中,则循环只能从 LSD 循环缓冲区 运行。)
in a bash shell:
t=cdq-latency; nasm -f elf64 "$t".asm && ld -o "$t" "$t.o" && objdump -drwC -Mintel "$t" && taskset -c 3 perf stat --all-user -etask-clock,context-switches,cpu-migrations,page-faults,cycles,instructions,uops_issued.any,frontend_retired.dsb_miss,idq.dsb_uops,idq.mite_uops,idq.mite_cycles,idq_uops_not_delivered.core,idq_uops_not_delivered.cycles_fe_was_ok,idq.all_mite_cycles_4_uops ./"$t"
拆卸
0000000000401000 <_start>:
401000: bd 00 ca 9a 3b mov ebp,0x3b9aca00
401005: 0f 1f 84 00 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0]
...
40103d: 0f 1f 00 nop DWORD PTR [rax]
0000000000401040 <_start.loop>:
401040: 99 cdq
401041: 99 cdq
401042: 99 cdq
401043: 99 cdq
...
401052: 99 cdq
401053: 99 cdq # 20 total CDQ
401054: ff cd dec ebp
401056: 75 e8 jne 401040 <_start.loop>
0000000000401058 <_start.end>:
401058: 31 ff xor edi,edi
40105a: b8 e7 00 00 00 mov eax,0xe7
40105f: 0f 05 syscall
性能结果:
Performance counter stats for './cdq-latency':
5,205.44 msec task-clock # 1.000 CPUs utilized
0 context-switches # 0.000 K/sec
0 cpu-migrations # 0.000 K/sec
1 page-faults # 0.000 K/sec
20,124,711,776 cycles # 3.866 GHz (49.88%)
22,015,118,295 instructions # 1.09 insn per cycle (59.91%)
21,004,212,389 uops_issued.any # 4035.049 M/sec (59.97%)
1,005,872,141 frontend_retired.dsb_miss # 193.235 M/sec (60.03%)
0 idq.dsb_uops # 0.000 K/sec (60.08%)
20,997,157,414 idq.mite_uops # 4033.694 M/sec (60.12%)
19,996,447,738 idq.mite_cycles # 3841.451 M/sec (40.03%)
59,048,559,790 idq_uops_not_delivered.core # 11343.621 M/sec (39.97%)
112,956,733 idq_uops_not_delivered.cycles_fe_was_ok # 21.700 M/sec (39.92%)
209,490 idq.all_mite_cycles_4_uops # 0.040 M/sec (39.88%)
5.206491348 seconds time elapsed
所以循环开销 (dec/jnz) 基本上是免费发生的,在与最后一个 cdq
相同的循环中解码。计数不准确,因为我在一个 运行(启用 HT)中使用了太多事件,因此 perf 进行了软件多路复用。来自另一个 运行,计数器较少:
# same source, only these HW counters enabled to avoid multiplexing
5,161.14 msec task-clock # 1.000 CPUs utilized
20,107,065,550 cycles # 3.896 GHz
20,000,134,955 idq.mite_cycles # 3875.142 M/sec
59,050,860,720 idq_uops_not_delivered.core # 11441.447 M/sec
95,968,317 idq_uops_not_delivered.cycles_fe_was_ok # 18.594 M/sec
所以我们可以看到 MITE(传统解码)基本上在每个周期都处于活动状态,并且 front-end 基本上从不“正常”。 (即从未停滞在 back-end)。
只用10条CDQ指令,让DSB工作:
...
0000000000401040 <_start.loop>:
401040: 99 cdq
401041: 99 cdq
...
401049: 99 cdq # 10 total CDQ insns
40104a: ff cd dec ebp
40104c: 75 f2 jne 401040 <_start.loop>
Performance counter stats for './cdq-latency' (4 runs):
1,417.38 msec task-clock # 1.000 CPUs utilized ( +- 0.03% )
0 context-switches # 0.000 K/sec
0 cpu-migrations # 0.000 K/sec
1 page-faults # 0.001 K/sec
5,511,283,047 cycles # 3.888 GHz ( +- 0.03% ) (49.83%)
11,997,247,694 instructions # 2.18 insn per cycle ( +- 0.00% ) (59.99%)
10,999,182,841 uops_issued.any # 7760.224 M/sec ( +- 0.00% ) (60.17%)
197,753 frontend_retired.dsb_miss # 0.140 M/sec ( +- 13.62% ) (60.21%)
10,988,958,908 idq.dsb_uops # 7753.010 M/sec ( +- 0.03% ) (60.21%)
10,234,859 idq.mite_uops # 7.221 M/sec ( +- 27.43% ) (60.21%)
8,114,909 idq.mite_cycles # 5.725 M/sec ( +- 26.11% ) (39.83%)
40,588,332 idq_uops_not_delivered.core # 28.636 M/sec ( +- 21.83% ) (39.79%)
5,502,581,002 idq_uops_not_delivered.cycles_fe_was_ok # 3882.221 M/sec ( +- 0.01% ) (39.79%)
56,223 idq.all_mite_cycles_4_uops # 0.040 M/sec ( +- 3.32% ) (39.79%)
1.417599 +- 0.000489 seconds time elapsed ( +- 0.03% )
如 idq_uops_not_delivered.cycles_fe_was_ok
所报告,基本上所有未使用的 front-end uop 插槽都是 back-end(p0 / p6 上的端口压力)的故障,而不是 front-end.
最近的 Intel CPU 的前端包含一个复杂的解码器和一些简单的解码器。复杂解码器可以处理解码为多个微操作的指令,而简单解码器仅支持解码为单个(融合域)微操作的指令。
简单解码器是否可以解码所有 1-µop 指令,或者是否存在只能由复杂解码器处理的 1-µop 指令?
不行,有些指令只能解码1/clock
Andreas 的评论表明 xor eax,eax
/ setnle al
似乎有 1/clock 的解码瓶颈。我在 cdq
上发现了同样的事情:读取 EAX,写入 EDX,从 DSB(uop 缓存)也明显 运行s 更快,并且不涉及 partial-registers 或任何奇怪的东西,并且不需要 dep-breaking 指令。
更好的是,作为一条 single-byte 指令,它只需一小段指令就可以击败 DSB。 (导致在某些 CPU 上的测试产生误导性的结果,例如在 Agner Fog 的表和 https://uops.info/, e.g. SKX shown as 1c throughput.) https://www.uops.info/html-tp/SKX/CDQ-Measurements.html vs. https://www.uops.info/html-tp/CFL/CDQ-Measurements.html 上,由于不同的测试方法,吞吐量不一致:只有 Coffee Lake 测试曾经测试过足够小的展开计数(10)破坏 DSB,找到 0.6 的吞吐量。(一旦考虑循环开销,实际吞吐量为 0.5,完全由 back-end 与 cqo
相同的端口压力解释。IDK 为什么你会找到 0.6 而不是0.55,循环中 p6 只有一个额外的 uop。)
(Zen 可以 运行 此指令具有 0.25c 的吞吐量;没有奇怪的解码问题并由每个 integer-ALU 端口处理。)
dec/jnz 循环中的
times 10 cdq
可以 运行 来自 uop 缓存,并且 运行s 在 Skylake 上以 0.5c 的吞吐量(p06)加上循环开销也竞争p6.
times 20 cdq
对于一个 32 字节的机器代码块来说超过 3 个 uop 缓存行,这意味着循环只能 运行 来自传统解码(循环顶部对齐)。在 Skylake 上,这个 运行s 每 cdq
1 个周期。性能计数器确认 MITE 每个周期提供 1 uop,而不是 3 或 4 组,空闲周期介于两者之间。
default rel
%ifdef __YASM_VER__
CPU Skylake AMD
%else
%use smartalign
alignmode p6, 64
%endif
global _start
_start:
mov ebp, 1000000000
align 64
.loop:
;times 10 cdq ; 0.5c throughput
;times 20 cdq ; 1c throughput, 1 MITE uop per cycle front-end
; times 10 cqo ; 0.5c throughput 2-byte insn fits uop cache
; times 10 cdqe ; 1c throughput data dependency
;times 10 cld ; ~4c throughput, 3 uops
dec ebp
jnz .loop
.end:
xor edi,edi
mov eax,231 ; __NR_exit_group from /usr/include/asm/unistd_64.h
syscall ; sys_exit_group(0)
在我的 Arch Linux 桌面上,我在 perf:
下将其构建到 运行 的静态可执行文件中- i7-6700k with epp=balance_performance(最大“turbo”= 3.9GHz)
- 微码修订版 0xd6(所以 LSD 被禁用,这并不重要:如果所有 uops 都在 DSB uop 缓存 IIRC 中,则循环只能从 LSD 循环缓冲区 运行。)
in a bash shell:
t=cdq-latency; nasm -f elf64 "$t".asm && ld -o "$t" "$t.o" && objdump -drwC -Mintel "$t" && taskset -c 3 perf stat --all-user -etask-clock,context-switches,cpu-migrations,page-faults,cycles,instructions,uops_issued.any,frontend_retired.dsb_miss,idq.dsb_uops,idq.mite_uops,idq.mite_cycles,idq_uops_not_delivered.core,idq_uops_not_delivered.cycles_fe_was_ok,idq.all_mite_cycles_4_uops ./"$t"
拆卸
0000000000401000 <_start>:
401000: bd 00 ca 9a 3b mov ebp,0x3b9aca00
401005: 0f 1f 84 00 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0]
...
40103d: 0f 1f 00 nop DWORD PTR [rax]
0000000000401040 <_start.loop>:
401040: 99 cdq
401041: 99 cdq
401042: 99 cdq
401043: 99 cdq
...
401052: 99 cdq
401053: 99 cdq # 20 total CDQ
401054: ff cd dec ebp
401056: 75 e8 jne 401040 <_start.loop>
0000000000401058 <_start.end>:
401058: 31 ff xor edi,edi
40105a: b8 e7 00 00 00 mov eax,0xe7
40105f: 0f 05 syscall
性能结果:
Performance counter stats for './cdq-latency':
5,205.44 msec task-clock # 1.000 CPUs utilized
0 context-switches # 0.000 K/sec
0 cpu-migrations # 0.000 K/sec
1 page-faults # 0.000 K/sec
20,124,711,776 cycles # 3.866 GHz (49.88%)
22,015,118,295 instructions # 1.09 insn per cycle (59.91%)
21,004,212,389 uops_issued.any # 4035.049 M/sec (59.97%)
1,005,872,141 frontend_retired.dsb_miss # 193.235 M/sec (60.03%)
0 idq.dsb_uops # 0.000 K/sec (60.08%)
20,997,157,414 idq.mite_uops # 4033.694 M/sec (60.12%)
19,996,447,738 idq.mite_cycles # 3841.451 M/sec (40.03%)
59,048,559,790 idq_uops_not_delivered.core # 11343.621 M/sec (39.97%)
112,956,733 idq_uops_not_delivered.cycles_fe_was_ok # 21.700 M/sec (39.92%)
209,490 idq.all_mite_cycles_4_uops # 0.040 M/sec (39.88%)
5.206491348 seconds time elapsed
所以循环开销 (dec/jnz) 基本上是免费发生的,在与最后一个 cdq
相同的循环中解码。计数不准确,因为我在一个 运行(启用 HT)中使用了太多事件,因此 perf 进行了软件多路复用。来自另一个 运行,计数器较少:
# same source, only these HW counters enabled to avoid multiplexing
5,161.14 msec task-clock # 1.000 CPUs utilized
20,107,065,550 cycles # 3.896 GHz
20,000,134,955 idq.mite_cycles # 3875.142 M/sec
59,050,860,720 idq_uops_not_delivered.core # 11441.447 M/sec
95,968,317 idq_uops_not_delivered.cycles_fe_was_ok # 18.594 M/sec
所以我们可以看到 MITE(传统解码)基本上在每个周期都处于活动状态,并且 front-end 基本上从不“正常”。 (即从未停滞在 back-end)。
只用10条CDQ指令,让DSB工作:
...
0000000000401040 <_start.loop>:
401040: 99 cdq
401041: 99 cdq
...
401049: 99 cdq # 10 total CDQ insns
40104a: ff cd dec ebp
40104c: 75 f2 jne 401040 <_start.loop>
Performance counter stats for './cdq-latency' (4 runs):
1,417.38 msec task-clock # 1.000 CPUs utilized ( +- 0.03% )
0 context-switches # 0.000 K/sec
0 cpu-migrations # 0.000 K/sec
1 page-faults # 0.001 K/sec
5,511,283,047 cycles # 3.888 GHz ( +- 0.03% ) (49.83%)
11,997,247,694 instructions # 2.18 insn per cycle ( +- 0.00% ) (59.99%)
10,999,182,841 uops_issued.any # 7760.224 M/sec ( +- 0.00% ) (60.17%)
197,753 frontend_retired.dsb_miss # 0.140 M/sec ( +- 13.62% ) (60.21%)
10,988,958,908 idq.dsb_uops # 7753.010 M/sec ( +- 0.03% ) (60.21%)
10,234,859 idq.mite_uops # 7.221 M/sec ( +- 27.43% ) (60.21%)
8,114,909 idq.mite_cycles # 5.725 M/sec ( +- 26.11% ) (39.83%)
40,588,332 idq_uops_not_delivered.core # 28.636 M/sec ( +- 21.83% ) (39.79%)
5,502,581,002 idq_uops_not_delivered.cycles_fe_was_ok # 3882.221 M/sec ( +- 0.01% ) (39.79%)
56,223 idq.all_mite_cycles_4_uops # 0.040 M/sec ( +- 3.32% ) (39.79%)
1.417599 +- 0.000489 seconds time elapsed ( +- 0.03% )
如 idq_uops_not_delivered.cycles_fe_was_ok
所报告,基本上所有未使用的 front-end uop 插槽都是 back-end(p0 / p6 上的端口压力)的故障,而不是 front-end.