如果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不能宏熔丝inc或dec/jcc(或任何带有内存操作数的指令),但Agner Fog的表不同意。uiCA在循环宏融合的底部显示dec/jnz,their paper显示其预测与真实的CPU(包括ICL)上的测试一致。但是如果他们使用最新的GCC编译,他们可能不会测试任何dec/jcc循环,sub/jcc。Agner的ICL融合表不是早期SnB的复制/粘贴;它显示inc/dec可以在与add/sub相同的情况下进行融合(令人惊讶的是,现在包括jc/ja,但dec不会修改CF。)如果有人可以测试这一点来验证,那就太好了。
1条答案
按热度按时间k7fdbhmy1#
不,融合与一条复杂指令(如
cpuid
或lock 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
/sub
或inc
/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 *
另见
add [edx], eax
和指针增量的sub reg, 1
/jnz
循环的融合域uops,适用于P6、Pentium-M、Core 2、Sandybridge,并介绍了Netburst(P4)和Bulldozer/Zen。(忽略调度器和执行单元中的未融合域uop计数)。也有一个描述微观和宏观融合的情况下,我的措辞是不容易理解.dec/jcc
融合!两者都可能同时发生
在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:使用索引寻址模式加载,以及ALUcmp/jnz
)。您可以通过在CMP和JCC与之后,注意uops_issued.any:u
和uops_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:u
比uops_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不能宏熔丝
inc
或dec
/jcc
(或任何带有内存操作数的指令),但Agner Fog的表不同意。uiCA在循环宏融合的底部显示dec
/jnz
,their 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相同,因为它没有进行重大的微架构更改。