当代 x86 处理器中的指令融合是什么?
What is instruction fusion in contemporary x86 processors?
我的理解是,有两种指令融合:
- 微操作融合
- 宏观操作融合
微操作是那些可以在1个时钟周期内执行的操作。如果融合几个微操作,我们得到一个"instruction".
如果融合了几个指令,我们得到一个宏操作。
如果几个宏操作融合,我们得到宏操作融合。
我说的对吗?
不,融合与一个复杂指令(如 cpuid
或 lock add [mem], eax
)如何解码为多个 uops 完全不同。
退役阶段确定一条指令的所有 uops 都已退役,因此该指令已退役的方式与融合无关。
Macro-fusion 将 cmp/jcc 或 test/jcc 解码为单个 compare-and-branch uop。(Intel 和 AMD CPU)。管道的其余部分纯粹将其视为单个 uop1(除了性能计数器仍将其视为 2 条指令)。这节省了 uop 缓存 space,以及包括解码在内的所有带宽。在某些代码中,compare-and-branch 占整个指令组合的很大一部分,可能占 25%,因此选择寻找这种融合而不是其他可能的融合,如 mov dst,src1
/ or dst,src2
感觉。
Sandybridge-family 也可以 macro-fuse 一些其他带有条件分支的 ALU 指令,例如 add
/sub
或 inc
/dec
+ JCC 有一些条件。 ()
Micro-fusion 将同一条指令的 2 个微指令存储在一起,因此它们只占用管道 fused-domain 部分中的 1 "slot" .但他们仍然要分别派遣到不同的执行单位。在 Intel Sandybridge-family 中,RS(Reservation Station aka scheduler)处于未融合域中,因此它们甚至单独存储在调度程序中。 (请参阅我对 的回答中的脚注 2。)
P6 家族有一个 fused-domain RS 以及 ROB,因此 micro-fusion 有助于增加 out-of-order window 的有效尺寸。但据报道,SnB-family 简化了 uop 格式,使其更紧凑,允许更大的 RS 大小,这在任何时候都是有用的,而不仅仅是 micro-fused 指令。
并且 Sandybridge 系列将 "un-laminate" 在某些条件下索引寻址模式,在 issue/rename 进入 out-of-order 后端的 ROB 之前,将它们拆分回各自插槽中的 2 个单独的 uops ,因此您失去了 front-end issue/rename 吞吐量优势 micro-fusion。参见 Micro fusion and addressing modes
两者可以同时发生
cmp [rdi], eax
jnz .target
cmp/jcc 可以 macro-fuse 到单个 cmp-and-branch ALU uop,来自 [rdi]
的负载可以 micro-fuse 使用那个 uop。
未能 micro-fuse cmp
不会阻止 macro-fusion。
这里的限制是:RIP-relative + immediate 永远不能 micro-fuse,所以 cmp dword [static_data], 1
/ jnz
可以 macro-fuse 但不能 micro-fuse .
A cmp
/jcc
on SnB-family(如cmp [rdi+rax], edx
/ jnz
)将在解码器中宏和 micro-fuse,但是micro-fusion 将在发行阶段之前 un-laminate。 (因此在 fused-domain 和 unfused-domain 中总共有 2 个微指令:使用索引寻址模式加载,以及 ALU cmp/jnz
)。您可以通过在 CMP 和 JCC 之间放置一个 mov ecx, 1
来验证这一点macro-fusion。 micro-fusion 表现相同。
在 Skylake 上,cmp dword [rdi], 0
/jnz
不能 macro-fuse。 (仅 micro-fuse)。我用一个包含一些虚拟 mov ecx,1
指令的循环进行了测试。重新排序,因此其中一个 mov
指令拆分了 cmp/jcc
并没有更改 fused-domain 或 unfused-domain uops 的性能计数器。
但是 cmp [rdi],eax
/jnz
做 宏和 micro-fuse。重新排序 mov ecx,1
指令将 CMP 与 JNZ 分开 确实 更改性能计数器(证明 macro-fusion),并且 uops_executed 高于 uops_issued每次迭代 1(证明 micro-fusion)。
cmp [rdi+rax], eax
/jne
仅macro-fuses;不微。 (实际上 micro-fuses 在解码中,但 un-laminates 在发布之前,因为索引寻址模式,它不是像 sub eax, [rdi+rax]
这样可以保持索引寻址模式的 RMW-register 目的地 micro-fused。具有索引寻址模式的 sub
在 SKL 上执行 宏和 micro-fuse,并且大概是 Haswell)。
(cmp dword [rdi],0
确实 micro-fuse,但是:uops_issued.any:u
比 uops_executed.thread
低 1,并且循环不包含 nop
或其他 "eliminated" 指令,或任何其他可能 micro-fuse).
的内存指令
一些编译器(包括 GCC IIRC)更喜欢使用单独的加载指令,然后在寄存器上进行比较+分支。 TODO:检查 gcc 和 clang 的选择是否是立即与寄存器的最佳选择。
Micro-operations are those operations that can be executed in 1 clock cycle.
不完全是。他们在管道中使用 1 "slot",或者在 out-of-order back-end.
中跟踪它们的 ROB 和 RS 中
是的,将 uop 分派到执行端口发生在 1 个时钟周期内,简单的 uops(例如,整数加法)可以在同一周期内完成执行。自 Haswell 以来,这种情况可能会同时发生多达 8 个微指令,但在 Sunny Cove 上增加到 10 个微指令。实际执行可能需要1个时钟周期以上(占用执行单元时间更长,如FP分频)。
我认为除法器是现代主流 Intel 上唯一没有完全流水线化的执行单元,但 Knight's Landing 有一些 not-fully-pipelined SIMD 洗牌,它们是单 uop 但(倒数)吞吐量为 2 个周期。)
脚注 1:
如果 cmp [rdi], eax
/ jne
内存操作数出错,即 #PF
异常,则采用指向 [=20 之前的异常 return 地址=].所以我觉得即使是异常处理也还是可以把它当成一个单一的东西。
或者如果分支目标地址是伪造的,#PF 异常将在分支已经执行后发生,从具有更新的 RIP 的代码获取。再说一次,我认为没有办法让 cmp
成功执行,而 jcc
出错,需要使用指向 JCC 的 RIP 进行异常处理。
但是即使这种情况是可能的,CPU 也需要设计来处理,可以推迟到实际检测到异常时才对其进行排序。也许有微码辅助,或一些 special-case 硬件。
至于 cmp/jcc uop 在正常情况下如何通过管道,它的工作方式与一条长 single-uop 指令完全一样,都设置标志 和 有条件的分支。
令人惊讶的是,loop
指令(类似于 dec rcx/jnz
但没有设置标志)不是 Intel CPUs 上的单个 uop。 Why is the loop instruction slow? Couldn't Intel have implemented it efficiently?.
我的理解是,有两种指令融合:
- 微操作融合
- 宏观操作融合
微操作是那些可以在1个时钟周期内执行的操作。如果融合几个微操作,我们得到一个"instruction".
如果融合了几个指令,我们得到一个宏操作。
如果几个宏操作融合,我们得到宏操作融合。
我说的对吗?
不,融合与一个复杂指令(如 cpuid
或 lock add [mem], eax
)如何解码为多个 uops 完全不同。
退役阶段确定一条指令的所有 uops 都已退役,因此该指令已退役的方式与融合无关。
Macro-fusion 将 cmp/jcc 或 test/jcc 解码为单个 compare-and-branch uop。(Intel 和 AMD CPU)。管道的其余部分纯粹将其视为单个 uop1(除了性能计数器仍将其视为 2 条指令)。这节省了 uop 缓存 space,以及包括解码在内的所有带宽。在某些代码中,compare-and-branch 占整个指令组合的很大一部分,可能占 25%,因此选择寻找这种融合而不是其他可能的融合,如 mov dst,src1
/ or dst,src2
感觉。
Sandybridge-family 也可以 macro-fuse 一些其他带有条件分支的 ALU 指令,例如 add
/sub
或 inc
/dec
+ JCC 有一些条件。 (
Micro-fusion 将同一条指令的 2 个微指令存储在一起,因此它们只占用管道 fused-domain 部分中的 1 "slot" .但他们仍然要分别派遣到不同的执行单位。在 Intel Sandybridge-family 中,RS(Reservation Station aka scheduler)处于未融合域中,因此它们甚至单独存储在调度程序中。 (请参阅我对
P6 家族有一个 fused-domain RS 以及 ROB,因此 micro-fusion 有助于增加 out-of-order window 的有效尺寸。但据报道,SnB-family 简化了 uop 格式,使其更紧凑,允许更大的 RS 大小,这在任何时候都是有用的,而不仅仅是 micro-fused 指令。
并且 Sandybridge 系列将 "un-laminate" 在某些条件下索引寻址模式,在 issue/rename 进入 out-of-order 后端的 ROB 之前,将它们拆分回各自插槽中的 2 个单独的 uops ,因此您失去了 front-end issue/rename 吞吐量优势 micro-fusion。参见 Micro fusion and addressing modes
两者可以同时发生
cmp [rdi], eax
jnz .target
cmp/jcc 可以 macro-fuse 到单个 cmp-and-branch ALU uop,来自 [rdi]
的负载可以 micro-fuse 使用那个 uop。
未能 micro-fuse cmp
不会阻止 macro-fusion。
这里的限制是:RIP-relative + immediate 永远不能 micro-fuse,所以 cmp dword [static_data], 1
/ jnz
可以 macro-fuse 但不能 micro-fuse .
A cmp
/jcc
on SnB-family(如cmp [rdi+rax], edx
/ jnz
)将在解码器中宏和 micro-fuse,但是micro-fusion 将在发行阶段之前 un-laminate。 (因此在 fused-domain 和 unfused-domain 中总共有 2 个微指令:使用索引寻址模式加载,以及 ALU cmp/jnz
)。您可以通过在 CMP 和 JCC 之间放置一个 mov ecx, 1
来验证这一点macro-fusion。 micro-fusion 表现相同。
在 Skylake 上,cmp dword [rdi], 0
/jnz
不能 macro-fuse。 (仅 micro-fuse)。我用一个包含一些虚拟 mov ecx,1
指令的循环进行了测试。重新排序,因此其中一个 mov
指令拆分了 cmp/jcc
并没有更改 fused-domain 或 unfused-domain uops 的性能计数器。
但是 cmp [rdi],eax
/jnz
做 宏和 micro-fuse。重新排序 mov ecx,1
指令将 CMP 与 JNZ 分开 确实 更改性能计数器(证明 macro-fusion),并且 uops_executed 高于 uops_issued每次迭代 1(证明 micro-fusion)。
cmp [rdi+rax], eax
/jne
仅macro-fuses;不微。 (实际上 micro-fuses 在解码中,但 un-laminates 在发布之前,因为索引寻址模式,它不是像 sub eax, [rdi+rax]
这样可以保持索引寻址模式的 RMW-register 目的地 micro-fused。具有索引寻址模式的 sub
在 SKL 上执行 宏和 micro-fuse,并且大概是 Haswell)。
(cmp dword [rdi],0
确实 micro-fuse,但是:uops_issued.any:u
比 uops_executed.thread
低 1,并且循环不包含 nop
或其他 "eliminated" 指令,或任何其他可能 micro-fuse).
一些编译器(包括 GCC IIRC)更喜欢使用单独的加载指令,然后在寄存器上进行比较+分支。 TODO:检查 gcc 和 clang 的选择是否是立即与寄存器的最佳选择。
Micro-operations are those operations that can be executed in 1 clock cycle.
不完全是。他们在管道中使用 1 "slot",或者在 out-of-order back-end.
中跟踪它们的 ROB 和 RS 中是的,将 uop 分派到执行端口发生在 1 个时钟周期内,简单的 uops(例如,整数加法)可以在同一周期内完成执行。自 Haswell 以来,这种情况可能会同时发生多达 8 个微指令,但在 Sunny Cove 上增加到 10 个微指令。实际执行可能需要1个时钟周期以上(占用执行单元时间更长,如FP分频)。
我认为除法器是现代主流 Intel 上唯一没有完全流水线化的执行单元,但 Knight's Landing 有一些 not-fully-pipelined SIMD 洗牌,它们是单 uop 但(倒数)吞吐量为 2 个周期。)
脚注 1:
如果 cmp [rdi], eax
/ jne
内存操作数出错,即 #PF
异常,则采用指向 [=20 之前的异常 return 地址=].所以我觉得即使是异常处理也还是可以把它当成一个单一的东西。
或者如果分支目标地址是伪造的,#PF 异常将在分支已经执行后发生,从具有更新的 RIP 的代码获取。再说一次,我认为没有办法让 cmp
成功执行,而 jcc
出错,需要使用指向 JCC 的 RIP 进行异常处理。
但是即使这种情况是可能的,CPU 也需要设计来处理,可以推迟到实际检测到异常时才对其进行排序。也许有微码辅助,或一些 special-case 硬件。
至于 cmp/jcc uop 在正常情况下如何通过管道,它的工作方式与一条长 single-uop 指令完全一样,都设置标志 和 有条件的分支。
令人惊讶的是,loop
指令(类似于 dec rcx/jnz
但没有设置标志)不是 Intel CPUs 上的单个 uop。 Why is the loop instruction slow? Couldn't Intel have implemented it efficiently?.