不,融合与一条复杂指令(例如cpuid
or lock add [mem], eax
) 可以解码为多个微指令。大多数指令解码为单个 uop,因此这是现代 x86 CPU 中的正常情况。
后端必须跟踪与指令相关的所有微指令,无论是否存在任何微融合或宏融合。当单个指令的所有微指令已从 ROB 中退出时,该指令已退出。 (中断只能在指令边界处进行,因此如果有一个待处理,则退出必须为此找到一个指令边界,而不是在多微指令指令的中间。否则,可以在不考虑指令边界的情况下填充退出槽,例如发出插槽。)
指令之间的宏融合
Macro-fusion decodes cmp/jcc or test/jcc into a single compare-and-branch uop. (Intel and AMD CPUs). The rest of the pipeline sees it purely as a single uop1 (except performance counters still count it as 2 instructions). This saves uop cache space, and bandwidth everywhere including decode. In some code, compare-and-branch makes up a significant fraction of the total instruction mix, like maybe 25%, so choosing to look for this fusion rather than other possible fusions like mov dst,src1
/ or dst,src2
makes sense.
Sandybridge-family 还可以将其他一些 ALU 指令与条件分支进行宏融合,例如add
/sub
or inc
/dec
+ JCC 有一些条件。 (x86_64 - 汇编 - 循环条件和乱序)
Ice Lake2 changed to doing macro-fusion right after legacy decode, so pre-decode only has to steer 1 x86 instruction to each of the four decoders.
微融合 - 1 条指令内
微融合将来自同一指令的 2 个微指令存储在一起,因此它们仅占用管道的融合域部分中的 1 个“槽”。但他们仍然需要分别调度到不同的执行单元。在Intel Sandybridge系列中,RS(保留站又名调度程序)位于未融合域中,因此它们甚至单独存储在调度程序中。 (参见我的回答中的脚注2了解 lfence 对具有两个长依赖链的循环的影响,以增加长度)
P6家族有一个融合域RS以及ROB,因此微融合有助于增加那里乱序窗口的有效大小。但据报道,SnB 系列简化了 uop 格式,使其更加紧凑,允许更大的 RS 尺寸,这始终很有用,而不仅仅是针对微融合指令。
在某些情况下,Sandybridge 系列将“取消层压”索引寻址模式,在乱序后端将其发出/重命名为 ROB 之前,将它们拆分回各自插槽中的 2 个单独的微指令,这样您就失去了前端-结束问题/重命名微融合的吞吐量优势。看微融合和寻址模式
See also
- 法纳尔的文章“x86 CPU内部将指令解码为RISC形式”的传说融合域微指令的详细分解
sub reg, 1
/ jnz
包含内存目标的循环add [edx], eax
和指针增量,适用于 P6、Pentium-M、Core 2、Sandybridge,以及 Netburst (P4) 和 Bulldozer/Zen。 (忽略调度程序和执行单元中的未融合域微指令计数)。还对微观和宏观融合进行了描述,以防我的措辞不容易理解。
-
https://agner.org/optimize/尤其是他的微架构指南具有逆向工程细节。
- Intel's 优化手册其中有一些章节涉及微架构细节。
-
https://en.wikichip.org/wiki/macro-operation_fusion(包括 RISC-V 和 ARM 等非 x86。)
-
https://www.corsix.org/content/x86-macro-op-fusion-notes哪些指令+JCC的组合可以熔断。显然,Zen3+ 可以将任何 JCC 与 OR、XOR 和 AND 以及自 Broadwell 以来的 CMP 或 TESTS 进行宏融合。冰湖已移除
dec/jcc
fusion!
- https://easyperf.net/blog/2018/02/23/MacroFusion-in-Intel-CPU
两者可以同时发生
cmp [rdi], eax
jnz .target
在 i7-6700k Skylake 上进行测试,可能适用于大多数早期和后期的 Sandybridge 系列 CPU,尤其是在 Ice Lake 之前。
cmp/jcc 可以宏融合到单个 cmp-and-branch ALU uop 中,并且来自的负载[rdi]
可以与该 uop 进行微融合。
微熔断失败cmp
不阻止宏融合。
这里的限制是:RIP-relative+immediate永远不能微熔丝,所以cmp dword [static_data], 1
/ jnz
可以宏熔断但不能微熔断。
A cmp
/jcc
在 SnB 系列上(例如cmp [rdi+rax], edx
/ jnz
)将在解码器中进行宏观和微观融合,但微观融合将在发布阶段之前取消层压。 (因此,融合域和非融合域中总共有 2 个 uop:使用索引寻址模式加载,并且 ALUcmp/jnz
)。您可以通过放置一个性能计数器来验证这一点mov ecx, 1
在 CMP 和 JCC 之间对比之后,并注意uops_issued.any:u
and uops_executed.thread
每次循环迭代两者都会增加 1,因为我们击败了宏融合。微融合的表现也一样。
在天湖上,cmp dword [rdi], 0
/jnz
无法宏熔断。 (仅限微型保险丝)。我用包含一些虚拟的循环进行了测试mov ecx,1
指示。重新排序其中之一mov
指令拆分了cmp/jcc
没有更改融合域或非融合域微指令的性能计数器。
But cmp [rdi],eax
/jnz
does宏保险丝和微保险丝。重新排序,这样mov ecx,1
指令将 CMP 与 JNZ 分开does更改性能计数器(证明宏融合),并且每次迭代 uops_executed 比 uops_issued 高 1(证明微融合)。
cmp [rdi+rax], eax
/jne
仅宏熔断器;不是微的。 (事实上,解码中的微熔丝,但由于索引寻址模式,在发布之前未层压,并且它不是像 RMW 寄存器目标那样sub eax, [rdi+rax]
可以保持索引寻址模式微融合。那sub
采用索引寻址模式doesSKL 上的宏和微熔丝,大概还有 Haswell)。
(The cmp dword [rdi],0
does micro-保险丝,不过:uops_issued.any:u
低于 1uops_executed.thread
,并且循环不包含nop
或其他“消除”指令,或任何其他可以微熔丝的内存指令)。
一些编译器(包括 GCC IIRC)更喜欢使用单独的加载指令,然后在寄存器上进行比较+分支。 TODO:检查 gcc 和 clang 的选择对于立即数与寄存器是否是最佳的。
微操作是那些可以在1个时钟周期内执行的操作。
不完全是。它们在管道中占用 1 个“槽”,或者在乱序后端跟踪它们的 ROB 和 RS 中占用 1 个“槽”。
是的,将微指令分派到执行端口会在 1 个时钟周期内发生,并且简单的微指令(例如整数加法)可以在同一周期内完成执行。自 Haswell 以来,这种情况最多可同时发生 8 个 uops,但在 Sunny Cove 上增加到 10 个。实际执行可能需要超过1个时钟周期(占用执行单元较长时间,例如FP除法)。
我认为除法器是现代主流英特尔上唯一没有完全流水线的执行单元,但 Knight's Landing 有一些非完全流水线的 SIMD 洗牌,它们是单微操作,但(倒数)吞吐量为 2 个周期。)。
脚注 1 - 宏融合的 uop 是否需要拆分?
If cmp [rdi], eax
/ jne
内存操作数上的错误,即#PF
页面错误异常,异常返回地址指向页面的开头cmp
,这样它就可以在页面中的OS页面之后重新运行。无论我们是否有核聚变,这都有效,这并不奇怪。
或者如果分支目标地址是未映射的页面,则#PF
会发生异常after该分支已通过更新的 RIP 获取代码执行。
但是如果分支目标地址是非规范的,从架构上来说jcc
本身应该#GP
过错。例如如果 RIP 接近规范范围的顶部,并且 rel32=+almost2GiB。 (x86-64 的设计使得 RIP 值在内部实际上可以是 48 位或 57 位,永远不需要保存非规范地址,因为尝试设置它时会发生错误,而不是等到从非规范地址获取代码为止- 规范地址。)
如果 CPU 处理该问题时出现异常jcc
,不是cmp
,然后可以推迟对其进行排序,直到实际检测到异常为止。也许有微代码辅助,或者一些特殊情况的硬件。
另外,TF=1 的单步执行应该在cmp
.
至于 cmp/jcc uop 在正常情况下如何通过管道,它的工作方式与一条长单 uop 指令完全相同,两者都设置标志and有条件分支。
令人惊讶的是,loop
指令(如dec rcx/jnz
但没有设置标志)是notIntel CPU 上的单个 uop。为什么循环指令很慢?英特尔就不能有效地实施它吗?.
脚注2:冰湖变化
阿格纳·福格发现宏观融合发生after传统解码器。 (微融合当然仍然在解码器中,所以指令如下add eax, [rdi]
仍然可以在“简单”解码器中解码。)
希望这里的好处是,如果最后一条指令可能是宏熔断指令,则不会提前结束解码组,这是早期 CPU 所做的 IIRC 操作。 (对于大的展开块,传统解码吞吐量较低sub
指令对比or
不涉及 JCC 时的说明。早期的 CPU 无法进行宏熔断or
与任何东西。
这仅影响旧解码,而不影响 uop 缓存。)
Wikichip错误地报告 ICL 每个时钟周期只能进行一次宏融合,但测试两个可熔断对可以在同一时钟周期内解码吗?确认 Rocket Lake(相同的 uarch 向后移植到 14nm)仍然可以像 Haswell 和 Skylake 一样执行 2/clock。
一个来源报道称 Ice Lake 无法进行宏融合inc
or dec
/jcc
(或任何带有内存操作数的指令),但 Agner Fog 的表不同意。uiCA shows dec
/jnz
在循环宏融合的底部,以及他们的论文显示其预测与真实 CPU(包括 ICL)上的测试非常吻合。但如果他们使用最新的 GCC 进行编译,他们可能不会测试任何dec/jcc
loops, sub/jcc
。 Agner 的 ICL 融合表不是早期 SnB 的复制/粘贴;表明inc
/dec
可以在相同情况下熔断add
/sub
现在(令人惊讶的是包括jc
/ja
现在,但是dec
不修改CF。)如果有人可以测试这个来验证,那就太好了。
update: 诺亚的考验在老虎湖表演中dec/jnz
在循环的底部可以进行宏熔断。然后dec/jc
似乎没有宏熔断。
微码版本:0x42
. decl; jnz
仍然循环宏熔丝(niters = nissued_uops = nexecuted_uops = cycles = {expected_ports}
).
无法获取decl; jc
到宏保险丝。为了decl; jc
我设置了两个循环:subl $1, %ecx
; decl %eax
; jc loop
(其中 ecx 是循环计数器)。niters * 3
发出/执行的 uops。
还尝试了仅取消设置进位标志和decl %eax; jc done; jnz loop
,也是 3 * nitters uops。
Ice Lake 的行为很可能与 Tiger Lake 相同,因为它没有进行重大的微架构更改。