assembly 什么是当代x86处理器中的指令融合?

6mzjoqzu  于 2023-06-30  发布在  其他
关注(0)|答案(1)|浏览(159)

我所理解的是,有两种类型的指令融合:
1.微操作融合
1.宏操作融合
微操作是可以在1个时钟周期内执行的那些操作。如果几个微操作被融合,我们得到一个“指令”。
如果几个指令被融合,我们得到一个宏操作。
如果将多个宏操作进行融合,则得到宏操作融合。
我说的对吗

k7fdbhmy

k7fdbhmy1#

不,融合与一条复杂指令(如cpuidlock add [mem], eax)如何解码为多个uop完全不同。大多数指令解码为单个uop,因此这是现代x86 CPU中的正常情况。
后端必须跟踪与指令相关联的所有uop,无论是否存在任何微融合或宏融合。当单个指令的所有uop都已从ROB引退时,该指令已引退。(中断只能在指令边界处进行,因此如果一个中断是待决的,则引退必须找到用于该中断的指令边界,而不是在多uop指令的中间。否则,可以不考虑指令边界来填充引退槽,如发布槽。)

宏融合-指令间

宏融合将cmp/jcc或test/jcc解码为单个比较和分支uop。(Intel和AMD CPU)。流水线的其余部分将其纯粹视为单个uop 1(除了性能计数器仍将其计数为2条指令)。这节省了uop高速缓存空间,以及包括解码在内的各处的带宽。在一些代码中,比较和分支构成了总指令组合的很大一部分,比如25%,所以选择寻找这种融合而不是其他可能的融合,比如mov dst,src1/or dst,src2是有意义的。

Sandybridge家族也可以宏融合一些其他ALU指令与条件分支,如add/subinc/dec + JCC与某些条件。(x86_64 - Assembly - loop conditions and out of order
Ice Lake 2更改为在传统解码之后立即进行宏融合,因此预解码仅需将1个x86指令引导到四个解码器中的每一个。

微融合-1条指令内

微融合将来自同一指令的2个uop存储在一起,因此它们仅占用流水线的融合域部分中的1个“槽”。但他们还是要分别被派到不同的执行单位。在英特尔Sandybridge系列中,RS(保留站,又名调度器)位于未融合域中,因此它们甚至单独存储在调度器中。(见我对 * Understanding the impact of lfence on a loop with two long dependency chains, for increasing lengths * 的答复中的脚注2)

P6家族具有融合域RS以及ROB,因此微融合有助于增加无序窗口的有效大小。但据报道,SnB家族简化了uop格式,使其更加紧凑,允许更大的RS尺寸,这一直都很有帮助,而不仅仅是微融合指令。
并且Sandybridge家族在某些条件下将“非层压”索引寻址模式,在无序后端中的ROB中发出/重命名之前将它们拆分回它们自己的插槽中的2个单独的uop,因此您失去了微融合的前端发出/重命名吞吐量优势。参见 * Micro fusion and addressing modes *
另见

两者都可能同时发生

cmp   [rdi], eax
    jnz   .target

在i7- 6700 k Skylake上测试,可能适用于大多数早期和后期的Sandybridge系列CPU,特别是在Ice Lake之前。
cmp/jcc可以宏融合到单个cmp和分支ALU uop中,来自[rdi]的负载可以与该uop微融合。
未能对cmp进行微熔并不能阻止宏熔。
这里的限制是:RIP-relative + immediate永远不能微融合,所以cmp dword [static_data], 1/jnz可以宏融合,但不能微融合。
SnB系列上的cmp/jcc(如cmp [rdi+rax], edx/jnz)将在解码器中进行宏和微融合,但微融合将在发布阶段之前解层压。(因此,在融合域和未融合域中总共有2个uop:使用索引寻址模式加载,以及ALU cmp/jnz)。您可以通过在CMP和JCC与之后,注意uops_issued.any:uuops_executed.thread在每个循环迭代中都增加1,因为我们击败了宏融合。微融合也是如此。

在Skylake上,cmp dword [rdi], 0/jnz不能宏融合。(仅微型保险丝)。我用一个包含一些虚拟mov ecx,1指令的循环进行了测试。重新排序,使其中一条mov指令分割cmp/jcc,不会更改融合域或非融合域uop的性能计数器。

cmp [rdi],eax/jnz * 不 * 宏和微熔丝。重新排序,使得mov ecx,1指令将CMP与JNZ * 分开,这样会改变性能计数器(证明宏融合),并且uops_executed比uops_issued每迭代高1(证明微融合)。

cmp [rdi+rax], eax/jne仅宏熔丝;(实际上,由于索引寻址模式,在解码中进行了微融合,但在发布之前未进行层压,并且它不是像sub eax, [rdi+rax]这样的RMW寄存器目的地,可以保持索引寻址模式微融合。具有索引寻址模式的sub在SKL上进行宏和微熔丝,可能还有Haswell)。
(The cmp dword [rdi],0做 * 微 *-熔丝,虽然:uops_issued.any:uuops_executed.thread低1,并且循环不包含nop或其他“消除”指令,或任何其他可能微熔丝的内存指令)。
一些编译器(包括GCC IIRC)更喜欢使用单独的加载指令,然后在寄存器上比较+分支。TODO:检查gcc和clang的选择是否是最佳的注册。
微操作是可以在1个时钟周期内执行的那些操作。
不完全是它们在流水线中或在无序后端中跟踪它们的ROB和RS中占用1个“槽”。
是的,将uop分派到执行端口发生在1个时钟周期中,并且简单的uop(例如,整数加法)可以在同一周期中完成执行。自Haswell以来,这可以同时发生多达8个uops,但在Sunny Cove上增加到10个。实际的执行可能需要多于1个时钟周期(占用执行单元更长时间,例如,在一个时钟周期内占用一个时钟周期)。FP部门)。
我认为除法器是现代主流Intel上唯一一个不是完全流水线的执行单元,但Knight's Landing有一些不是完全流水线的SIMD shuffle,它们是单个uop,但(倒数)吞吐量为2个周期。

脚注1 -宏融合uop是否需要拆分?

如果cmp [rdi], eax/jne在内存操作数上发生故障,即一个#PF页面错误异常,它取的异常返回地址指向cmp的开始,所以它可以在OS页面之后重新运行该页面。不管我们有没有核聚变,这都是有效的,没什么好奇怪的。
或者,如果分支目标地址是一个未Map的页面,则在分支已经执行 * 之后 ,将从使用更新的RIP的代码获取中发生#PF异常。
但是如果分支目标地址是非规范的,那么从体系结构上讲,jcc本身应该是#GP故障。例如,如果RIP接近规范范围的顶部,并且rel 32 =+几乎2GiB。(x86-64的设计使RIP值在内部可以是48位或57位,永远不需要保存非规范地址,因为在尝试设置它时会发生错误,而不是等到从非规范地址中提取代码。
如果CPU处理jcc上的异常,而不是cmp,那么可以推迟排序,直到实际检测到异常。也许有一个微代码辅助,或一些特殊情况的硬件。
此外,TF=1的单步执行应在cmp之后停止。
就cmp/jcc uop在正常情况下如何通过流水线而言,它的工作方式完全类似于一条长单uop指令,该指令同时设置标志 * 和 * 条件分支。
令人惊讶的是,loop指令(类似于dec rcx/jnz,但没有设置标志)
不是 * Intel CPU上的单个uop。Why is the loop instruction slow? Couldn't Intel have implemented it efficiently?

脚注2:冰湖变化

Agner Fog发现宏融合发生在传统解码器之后。(微融合当然仍然在解码器中,因此像add eax, [rdi]这样的指令仍然可以在“简单”解码器中解码。
希望这里的好处是,如果最后一条指令可能是宏熔丝,这是IIRC早期CPU所做的事情,那么不会提前结束解码组。(对于较大的sub指令展开块,传统解码吞吐量低于or指令时,不涉及JCC。早期的CPU不能将or与任何东西进行宏融合。这仅影响传统解码,而不影响uop缓存。)
Wikichip错误地报告ICL每个时钟周期只能进行一次宏融合,但在 * Can two fuseable pairs be decoded in the same clock cycle? * 上的测试证实了Rocket Lake(相同的uarch后移植到14 nm)仍然可以像Haswell和Skylake一样进行2/时钟融合。
One source报告Ice Lake不能宏熔丝incdec/jcc(或任何带有内存操作数的指令),但Agner Fog的表不同意。uiCA在循环宏融合的底部显示dec/jnztheir paper显示其预测与真实的CPU(包括ICL)上的测试一致。但是如果他们使用最新的GCC编译,他们可能不会测试任何dec/jcc循环,sub/jcc。Agner的ICL融合表不是早期SnB的复制/粘贴;它显示inc/dec可以在与add/sub相同的情况下进行融合(令人惊讶的是,现在包括jc/ja,但dec不会修改CF。)如果有人可以测试这一点来验证,那就太好了。

更新:Noah在Tiger Lake上的测试显示,循环底部的dec/jnz可以宏融合。而dec/jc似乎并没有宏熔丝。
微码版本:0x42 . decl; jnz循环仍然宏熔丝(niters = nissued_uops = nexecuted_uops = cycles = {expected_ports})。
无法将decl; jc连接到macrofuse。对于decl; jc,我设置了两个循环:subl $1, %ecx ; decl %eax ; jc loop(其中ecx是循环计数器)。niters * 3 uops已发布/已执行。
还尝试了carry-flag unset和decl %eax; jc done; jnz loop,也是3 * niters uops。
Ice Lake的行为很可能与Tiger Lake相同,因为它没有进行重大的微架构更改。

相关问题