微融合和寻址模式

使用英特尔®架构代码分析器 (IACA),我发现了一些意想不到的情况(对我来说)。

以下指令使用[base+index]寻址

 addps xmm1, xmmword ptr [rsi+rax*1] 

根据IACA没有微熔丝。 但是,如果我这样使用[base+offset]

 addps xmm1, xmmword ptr [rsi] 

IACA报告说它确实融合了。

“ 英特尔优化参考手册”第2-11部分提供了以下内容作为“可由所有解码器处理的微型熔合微操作”

 FADD DOUBLE PTR [RDI + RSI*8] 

Agner Fog的优化组装手册也给出了使用[base+index]寻址的微操作融合的例子。 例如,请参见第12.2节“Core2上的相同示例”。 那么正确的答案是什么?

在解码器和uop-cache中,寻址模式不影响微融合(除了具有立即操作数的指令不能使RIP相对寻址模式微熔化)。

但是,uop和寻址模式的某些组合不能保持在ROB(在无序内核)中的微熔合,所以在需要的时候,Intel SnB系列CPU“解叠”重命名阶段。 对于问题吞吐量和乱序窗口大小(ROB大小),解包后的融合域计数是重要的。

英特尔的优化手册在第2.3.2.4节“微操作队列”和“循环stream检测器”(LSD)中描述了Sandybridge的拆分,但没有描述任何后来的微架构的变化。


从SnB,HSW和SKL的实验中我可以看出最好的规则

  • SnB(我也假设为IvB):索引寻址模式总是不叠加,其他的保持微熔。 IACA(主要?)是正确的。
  • HSW及更高版本:索引寻址模式只能从FMA,ADC和CMOV等3input微控制器进行分层。 (即使在HSW上,ADC和CMOV也可以解压到2个ALU微处理器,所以这很奇怪)。 IACA是错误的,应用SnB规则。

相关:简单(非索引)寻址模式是port7(Haswell和更高版本)上的专用存储地址单元可以处理的唯一寻址模式,所以它仍然可能用于避免商店的索引寻址模式。 (一个好的方法是用单个寄存器来寻址你的dst,但用dst+(initial_src-initial_dst) src,那么你只需要在一个循环内增加dst寄存器。)

请注意,一些指令从来没有微熔丝(甚至在解码器/ uop-cache中)。 即使寄存器源版本只有1个vinsertf128 ymm, ymm, [mem], imm8 shufps xmm, [mem], imm8或者vinsertf128 ymm, ymm, [mem], imm8 ,在SnB上通过Skylake总是2个uops。 对于具有imm8控制操作数加上通常的dest / src1,src2寄存器/内存操作数的指令,这是典型情况,但还有其他一些情况。 例如PSRLW/D/Q xmm,[mem] (来自存储器操作数的向量移位计数)不会微熔丝,PMULLD也不会。

另请参阅Agner Fog博客上的这篇文章,以便在阅读大量寄存器时讨论有关HSW / SKL的问题吞吐量限制: 与索引寻址模式相比,大量微融合可能导致相对于具有较less寄存器操作数的相同指令变慢:寄存器寻址模式和立即数。 我们还不知道原因,但是我怀疑是某种寄存器读取限制,可能与读取PRF中的大量冷寄存器有关。


testing案例,来自实际测量的数字 :这些解码器中的所有微型熔丝AFAIK,即使它们之后未被层压。

 # store mov [rax], edi SnB/HSW/SKL: 1 fused-domain, 2 unfused. The store-address uop can run on port7. mov [rax+rsi], edi SnB: unlaminated. HSW/SKL: stays micro-fused. (The store-address can't use port7, though). mov [buf +rax*4], edi SnB: unlaminated. HSW/SKL: stays micro-fused. # normal ALU stuff add edx, [rsp+rsi] SnB: unlaminated. HSW/SKL: stays micro-fused. # I assume the majority of traditional/normal ALU insns are like add 

HSW / SKL可能需要解压的三个input指令

 vfmadd213ps xmm0,xmm0,[rel buf] HSW/SKL: stays micro-fused: 1 fused, 2 unfused. vfmadd213ps xmm0,xmm0,[rdi] HSW/SKL: stays micro-fused vfmadd213ps xmm0,xmm0,[0+rdi*4] HSW/SKL: un-laminated: 2 uops in fused & unfused-domains. (So indexed addressing mode is still the condition for HSW/SKL, same as documented by Intel for SnB) # no idea why this one-source BMI2 instruction is unlaminated # It's different from ADD in that its destination is write-only (and it uses a VEX encoding) blsi edi, [rdi] HSW/SKL: 1 fused-domain, 2 unfused. blsi edi, [rdi+rsi] HSW/SKL: 2 fused & unfused-domain. adc eax, [rdi] same as cmov r, [rdi] cmove ebx, [rdi] Stays micro-fused. (SnB?)/HSW: 2 fused-domain, 3 unfused domain. SKL: 1 fused-domain, 2 unfused. # I haven't confirmed that this micro-fuses in the decoders, but I'm assuming it does since a one-register addressing mode does. adc eax, [rdi+rsi] same as cmov r, [rdi+rsi] cmove ebx, [rdi+rax] SnB: untested, probably 3 fused&unfused-domain. HSW: un-laminated to 3 fused&unfused-domain. SKL: un-laminated to 2 fused&unfused-domain. 

我假设Broadwell的行为就像adlac / cmov的Skylake。

奇怪的是,HSW取消了内存源ADC和CMOV的层叠。 也许英特尔并没有想办法改变这个来自SnB的事情,而是在他们达到Haswell发货的最后期限之前。

Agner的insn表格说cmovcc r,madc r,m在HSW / SKL上完全不是微型熔丝,但是这与我的实验不符。 我测量的周期数与融合域uop问题计数相匹配,4个uops /时钟问题瓶颈。 希望他会仔细检查并更正表格。

内存 – 目标整数ALU

 add [rdi], eax SnB: untested (Agner says 2 fused-domain, 4 unfused-domain (load + ALU + store-address + store-data) HSW/SKL: 2 fused-domain, 4 unfused. add [rdi+rsi], eax SnB: untested, probably 4 fused & unfused-domain HSW/SKL: 3 fused-domain, 4 unfused. (I don't know which uop stays fused). HSW: About 0.95 cycles extra store-forwarding latency vs. [rdi] for the same address used repeatedly. (6.98c per iter, up from 6.04c for [rdi]) SKL: 0.02c extra latency (5.45c per iter, up from 5.43c for [rdi]), again in a tiny loop with dec ecx/jnz adc [rdi], eax SnB: untested HSW: 4 fused-domain, 6 unfused-domain. (same-address throughput 7.23c with dec, 7.19c with sub ecx,1) SKL: 4 fused-domain, 6 unfused-domain. (same-address throughput ~5.25c with dec, 5.28c with sub) adc [rdi+rsi], eax SnB: untested HSW: 5 fused-domain, 6 unfused-domain. (same-address throughput = 7.03c) SKL: 5 fused-domain, 6 unfused-domain. (same-address throughput = ~5.4c with sub ecx,1 for the loop branch, or 5.23c with dec ecx for the loop branch.) 

是的,没错, adc [rdi],eax / dec ecx / jnz运行速度比在SKL上add代替adc循环 。 我没有尝试使用不同的地址,因为显然SKL不喜欢重复重写相同的地址(存储转发延迟高于预期。另请参见这篇文章有关重复存储/重新加载到相同的地址比预期的要慢 。

内存目标adc是如此之多的微软,因为英特尔P6家族(显然是SnB家族)不能保持多UOP指令所有微操作的相同的TLB条目,所以它需要一个额外的UOP来解决这个问题在这种情况下,加载和添加完成,然后存储故障,但insn不能只是重新启动,因为CF已经更新 。 Andy Glew(@krazyglew)有趣的一系列评论。

据推测在解码器中的融合和后续的解叠可以使我们免于需要微码ROM ,从adc [base+idx], reg 。的单条指令中产生4个以上的融合域uops。


为什么SnB系列不层压

Sandybridge简化了内部uop格式,以节省功耗和晶体pipe(同时使用物理寄存器文件进行重大改变,而不是将input/输出数据保存在ROB中)。 SnB系列CPU只允许有限数量的input寄存器用于无序内核中的融合域uop。 对于SnB / IvB,该限制是2个input(包括标志)。 对于HSW和以后,这个限制是一个uop的3个input。 我不确定内存目标addadc是否充分利用了这一点,或者如果英特尔不得不通过一些指令让Haswell走出门外

Nehalem和早些时候对于一个未保真域的uop有2个input的限制,但是ROB可以明显地跟踪具有3个input寄存器(非存储器寄存器操作数,基址和索引)的微混合微操作。


因此索引存储和ALU +加载指令仍然可以高效地进行解码(不必成为组中的第一个uop),并且不会在uopcaching中占用额外的空间,否则微融合的优势基本上不适用于调优紧圈。 “未分层”发生在4个融合领域每周期发行/淘汰宽度无序核心之前 。 融合域性能计数器(uops_issued / uops_retired.retire_slots)在拆分后计算融合域uops。

英特尔对重命名者的描述( 第2.3.3.1节:更名)意味着它是实际上进行非分层的问题/重命名阶段,所以用于未分层的高层仍然可以在28/56/64熔合-domain uop问题队列/循环缓冲区(又名IDQ)。

TODO:testing这个。 做一个循环,应该刚好在循环缓冲区中。 改变一些东西,所以在发布之前,一个uop将被解压缩,看它是否仍然从循环缓冲区(LSD)运行,或者如果所有的uops现在都从uopcaching(DSB)重新获取。 有perf计数器跟踪uop从哪里来,所以这应该很容易。

较难的TODO:如果在从uopcaching读取和添加到IDQ之间发生拆分,testing它是否能减lessuop-cache带宽。 或者如果在问题阶段发生分层问题,是否会影响问题吞吐量? (也就是说,在发布第一个4后如何处理剩余的uops)


(请参阅此答案的前一个版本,以根据调整某些LUT代码进行一些猜测,关于vpgatherdd一些说明比pinsrw循环多出pinsrw周期。)

SnB实验testing

HSW / SKL号是在i5-4210U和i7-6700k上测量的。 两者都启用了HT(但系统空闲,所以线程本身就是整个核心)。 我使用ocperf.py在两个系统上运行相同的静态二进制文件,即SKL上的Linux 4.10和HSW上的Linux 4.8。 (HSW笔记本电脑NFS挂载我的SKL桌面/家。)

如下所述,在不再工作的i5-2500k上测量SnB数量。

通过使用性能计数器testinguops和周期来确认。

我find了英特尔Sandybridge的PMU事件表 ,用于Linux的perf命令。 (不幸的是,标准的perf没有大多数特定于硬件的PMU事件的象征性名称,比如uops。)我最近的回答就是利用它。

ocperf.py为这些uarch特定的PMU事件提供了符号名称 ,所以你不必查找表。 而且,同样的象征性的名字可以在多个原始作品中使用。 当我第一次写这个答案时,我并没有意识到这一点。

为了testinguop微融合,我构build了一个testing程序,这个testing程序在英特尔CPU的每循环4次高频融合域限制上受到了限制。 为了避免任何执行端口争用,这些uop中的许多都是nop ,它们仍然位于uopcaching中,并且像任何其他uop一样通过pipe道,除非它们不被分派到执行端口。 (一个xor x, same ,或是一个被淘汰的举动,都是一样的。)

testing程序: yasm -f elf64 uop-test.s && ld uop-test.o -o uop-test

 GLOBAL _start _start: xor eax, eax xor ebx, ebx xor edx, edx xor edi, edi lea rsi, [rel mydata] ; load pointer mov ecx, 10000000 cmp dword [rsp], 2 ; argc >= 2 jge .loop_2reg ALIGN 32 .loop_1reg: or eax, [rsi + 0] or ebx, [rsi + 4] dec ecx nop nop nop nop jg .loop_1reg ; xchg r8, r9 ; no effect on flags; decided to use NOPs instead jmp .out ALIGN 32 .loop_2reg: or eax, [rsi + 0 + rdi] or ebx, [rsi + 4 + rdi] dec ecx nop nop nop nop jg .loop_2reg .out: xor edi, edi mov eax, 231 ; exit(0) syscall SECTION .rodata mydata: db 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff 

我还发现,如果循环不是4个uops的倍数,那么循环缓冲区中的uop带宽不是每个周期4个常量。 (即它是abcabc ,…;不是abcabcab ,…)。 Agner Fog的微文档不幸的是在循环缓冲区的限制上并不清楚。 请参阅在执行uop计数不是处理器宽度倍数的循环时性能是否降低? 对HSW / SKL进行更多的调查。 在这种情况下,SnB可能比HSW差,但是我不确定,也没有工作的SnB硬件。

我想保持macros观融合(比较和分支),所以我在dec和branch之间使用了nop 。 我使用了4个nop ,所以在微融合的情况下,这个循环将是8个uops,并且每1次迭代填充2个周期的stream水线。

在另一个版本的循环中,使用不是微熔丝的2操作数寻址模式,循环将是10个熔融域微操作,并且在3个循环中运行。

3.3GHz Intel Sandybridge(i5 2500k)的testing结果 在testing之前,我没有做任何事情来让cpufreq调速器提高时钟速度,因为循环是没有与内存交互的循环。 我已经添加了注释,我必须inputhex的性能计数器事件。

testing1-reg寻址模式:no cmdline arg

 $ perf stat -e task-clock,cycles,instructions,r1b1,r10e,r2c2,r1c2,stalled-cycles-frontend,stalled-cycles-backend ./uop-test Performance counter stats for './uop-test': 11.489620 task-clock (msec) # 0.961 CPUs utilized 20,288,530 cycles # 1.766 GHz 80,082,993 instructions # 3.95 insns per cycle # 0.00 stalled cycles per insn 60,190,182 r1b1 ; UOPS_DISPATCHED: (unfused-domain. 1->umask 02 -> uops sent to execution ports from this thread) 80,203,853 r10e ; UOPS_ISSUED: fused-domain 80,118,315 r2c2 ; UOPS_RETIRED: retirement slots used (fused-domain) 100,136,097 r1c2 ; UOPS_RETIRED: ALL (unfused-domain) 220,440 stalled-cycles-frontend # 1.09% frontend cycles idle 193,887 stalled-cycles-backend # 0.96% backend cycles idle 0.011949917 seconds time elapsed 

testing2-reg寻址模式:使用cmdline arg

 $ perf stat -e task-clock,cycles,instructions,r1b1,r10e,r2c2,r1c2,stalled-cycles-frontend,stalled-cycles-backend ./uop-test x Performance counter stats for './uop-test x': 18.756134 task-clock (msec) # 0.981 CPUs utilized 30,377,306 cycles # 1.620 GHz 80,105,553 instructions # 2.64 insns per cycle # 0.01 stalled cycles per insn 60,218,693 r1b1 ; UOPS_DISPATCHED: (unfused-domain. 1->umask 02 -> uops sent to execution ports from this thread) 100,224,654 r10e ; UOPS_ISSUED: fused-domain 100,148,591 r2c2 ; UOPS_RETIRED: retirement slots used (fused-domain) 100,172,151 r1c2 ; UOPS_RETIRED: ALL (unfused-domain) 307,712 stalled-cycles-frontend # 1.01% frontend cycles idle 1,100,168 stalled-cycles-backend # 3.62% backend cycles idle 0.019114911 seconds time elapsed 

因此,两个版本都运行了80M指令,并将60M微处理器分派到执行端口。 ( or用一个内存源为or分派一个ALU,并为负载分派一个加载端口,而不pipe它是否是微融合的,或者不在stream水线的其余部分, nop根本不派遣到一个执行端口)同样,两个版本都退役了100M未融合域的微软,因为这里有40M个数字。

区别在于融合域的计数器。

  1. 1寄存器地址版本仅发行并退役80M融合域uops。 这与指令的数量相同。 每个insn变成一个融合域uop。
  2. 2寄存器地址版本发行100M融合域uops。 这与未融合域的uops数量相同,表明没有发生微融合。

我怀疑你只会看到UOPS_ISSUED和UOPS_RETIRED(使用的退休槽)之间的差异,如果分支预测失误导致uops在发布后被取消,但在退休之前。

最后,性能影响是真实的。 非融合版本花费了1.5倍的时钟周期。 与大多数真实案例相比,这夸大了性能差异。 循环必须运行多个周期,而2个额外的uop将它从2推到3.通常,额外的2个融合域uop将会减less差异。 如果代码被4-fused-domain-uops-per-cycle以外的其他东西所掩盖,那么可能没有区别。

但是,如果通过递增指针来实现,而不是使用[base + offset]寻址模式,那么在循环中创build大量内存引用的代码可能会更快。

进一步的东西

RIP相对于立即不能微型保险丝 。 Agner Fog的testing表明,甚至在解码器/ uop-cache中也是如此,所以它们从来没有融合过(而不是未被叠层)。

IACA得到这个错误,并声称这两个微型保险丝:

 cmp dword [abs mydata], 0x1b ; fused counters != unfused counters (micro-fusion happened, and wasn't un-laminated). Uses 2 entries in the uop-cache, according to Agner Fog's testing cmp dword [rel mydata], 0x1b ; fused counters ~= unfused counters (micro-fusion didn't happen) 

RIP-rel在没有即时的情况下会执行微型保险丝(并保持融合),例如:

 or eax, dword [rel mydata] ; fused counters != unfused counters, ie micro-fusion happens 

微融合不会增加指令的延迟 。 在另一个input准备就绪之前,负载可以发出。

 ALIGN 32 .dep_fuse: or eax, [rsi + 0] or eax, [rsi + 0] or eax, [rsi + 0] or eax, [rsi + 0] or eax, [rsi + 0] dec ecx jg .dep_fuse 

由于eax dep链,这个循环每次迭代运行5个周期。 没有比or eax, [rsi + 0 + rdi]mov ebx, [rsi + 0 + rdi] / or eax, ebx序列更快的序列。 (未融合版本和mov版本都运行相同数量的uops。)调度/ dep检查发生在未融合域中。 新发行的微博进入调度程序(又名Reservation Station(RS))以及ROB。 他们在派遣之后离开调度器(又被送到执行单位),但留在ROB直到退休。 因此,隐藏负载延迟的无序窗口至less是调度程序的大小( Sandybridge有54个未融合域uops,Haswell有60个,Skylake有97个)。

微融合没有一个快捷方式的基地和偏移是相同的寄存器。 一个带有or eax, [mydata + rdi+4*rdi]的循环or eax, [mydata + rdi+4*rdi] (其中rdi被清零)与循环or eax, [rsi+rdi]一样多的uops和循环。 这种寻址模式可以用来从一个固定的地址开始遍历奇数大小的结构数组。 这在大多数程序中可能从未使用,所以英特尔没有花费晶体pipe来允许这种双寄存器模式的特殊情况进行微型熔丝就不足为奇了。 (而且英特尔把它称为“索引寻址模式”,无论哪里都需要寄存器和比例因子。)


cmp / jccdec / jcc macros融合创build了一个uop,即使在未融合域中也保持单个uop。 dec / nop / jge仍然可以在一个周期内运行,但是三个uops而不是一个。

现在我已经回顾了Intel Sandy Bridge,Ivy Bridge,Haswell和Broadwell的testing结果。 我还没有进入Skylake的testing。 结果是:

  • 具有双寄存器寻址和三个input相关性的指令完好无损。 只要它们包含不超过32位数据(或2 * 16位),它们在微操作caching中只占用一个条目。
  • 在Haswell和Broadwell上使用融合的乘加指令可以使用四个input依赖关系进行指令。 这些指令仍然融合到单个微操作中,并且在微操作高速caching中仅占用一个条目。
  • 具有多于32位数据的指令(例如32位地址和8位立即数据)仍然可以熔合,但是在微操作高速caching中使用两个条目(除非32位可以被压缩成16位有符号整数)
  • 即使偏移和直接常量都非常小,但是相对于rip的寻址和立即常量的指令并不会融合。
  • 所有的testing结果都是相同的。
  • 这些testing是使用我自己的testing程序进行的,使用循环中的性能监视计数器,这些计数器足够小以适合微操作caching。

您的结果可能是由于其他因素。 我没有试过使用IACA。

我的testing表明,至less在Skylake上,处理器完全融合了复杂的寻址模式,这与Sandybridge不同。

也就是说,Peter在上面贴出的代码的1-arg2-arg版本运行的周期数是相同的,并且有相同数量的uop被派遣和退役。

我的结果:

性能计数器统计信息./uop-test

  23.718772 task-clock (msec) # 0.973 CPUs utilized 20,642,233 cycles # 0.870 GHz 80,111,957 instructions # 3.88 insns per cycle 60,253,831 uops_executed_thread # 2540.344 M/sec 80,295,685 uops_issued_any # 3385.322 M/sec 80,176,940 uops_retired_retire_slots # 3380.316 M/sec 0.024376698 seconds time elapsed 

性能计数器统计信息./uop-test x

  13.532440 task-clock (msec) # 0.967 CPUs utilized 21,592,044 cycles # 1.596 GHz 80,073,676 instructions # 3.71 insns per cycle 60,144,749 uops_executed_thread # 4444.487 M/sec 80,162,360 uops_issued_any # 5923.718 M/sec 80,104,978 uops_retired_retire_slots # 5919.478 M/sec 0.013997088 seconds time elapsed 

性能计数器统计信息./uop-test xx

  16.672198 task-clock (msec) # 0.981 CPUs utilized 27,056,453 cycles # 1.623 GHz 80,083,140 instructions # 2.96 insns per cycle 60,164,049 uops_executed_thread # 3608.645 M/sec 100,187,390 uops_issued_any # 6009.249 M/sec 100,118,409 uops_retired_retire_slots # 6005.112 M/sec 0.016997874 seconds time elapsed 

我没有在Skylake上find任何UOPS_RETIRED_ANY指令,只有显然是融合域的“退役老虎机”。

最后的testing( uop-test xx )是Peterbuild议的一个变体,它使用一个RIP相对cmp和immediate,这个variables已经不是微型的:

 .loop_riprel cmp dword [rel mydata], 1 cmp dword [rel mydata], 2 dec ecx nop nop nop nop jg .loop_riprel 

结果表明,每个周期额外的2个微处理器由uops发出和退出的计数器拾取(因此testing可以区分融合发生与否)。

欢迎在其他体系结构上进行更多的testing! 你可以在github中find代码(从Peter上面复制)。


[1] …也许在Skylake和Sandybridge之间还有一些架构,因为Peter只testing了SB,而我只testing了SKL。

旧的英特尔处理器没有uopcaching可以做融合,所以也许这是uopcaching的缺点。 我现在没有时间来testing这个,但是下次我更新我的testing脚本时,我会为uop融合添加一个testing。 您是否尝试过使用FMA指令? 它们是允许在未融合的uop中input3个依赖项的唯一指令。