以下所有指令均执行相同的操作:将%eax设置为零。哪种方法最合适(需要最少的机器周期)?

xorl   %eax, %eax
mov    q4312078q, %eax
andl   q4312078q, %eax


评论

您可能想阅读这篇文章

xor vs mov:stackoverflow.com/questions/1135679/…

#1 楼

TL; DR摘要:xor same, same是所有CPU的最佳选择。没有其他方法比它具有任何优势,并且它比任何其他方法都具有至少某些优势。英特尔和AMD官方推荐使用它以及编译器的作用。在64位模式下,仍应使用xor r32, r32,因为编写32位reg会将高32位置零。xor r64, r64浪费了一个字节,因为它需要REX前缀。

甚至更糟, Silvermont仅将xor r32,r32识别为dep-breaking,而不是64位操作数大小。因此,即使由于将r8..r15清零而仍需要REX前缀时,也请使用xor r10d,r10d而不是xor r10,r10

GP整数示例:

xor   eax, eax       ; RAX = 0.  Including AL=0 etc.
xor   r10d, r10d     ; R10 = 0
xor   edx, edx       ; RDX = 0

; small code-size alternative:    cdq    ; zero RDX if EAX is already zero

; SUB-OPTIMAL
xor   rax,rax       ; waste of a REX prefix, and extra slow on Silvermont
xor   r10,r10       ; bad on Silvermont (not dep breaking), same as r10d everywhere else because a REX prefix is still needed for r10d or r10.
mov   eax, 0        ; doesn't touch FLAGS, but not faster and takes more bytes
 and   eax, 0        ; false dependency.  (Microbenchmark experiments might want this)
 sub   eax, eax      ; same as xor on most but not all CPUs; bad on Silvermont for example.

xor   al, al        ; false dep on some CPUs, not a zeroing idiom.  Use xor eax,eax
mov   al, 0         ; only 2 bytes, and probably better than xor al,al *if* you need to leave the rest of EAX/RAX unmodified



通常最好用pxor xmm, xmm对向量寄存器进行零位处理。这就是gcc通常所做的(甚至在与FP指令一起使用之前)。

xorps xmm, xmm很有道理。它比pxor短一个字节,但是xorps需要Intel Nehalem上的执行端口5,而pxor可以在任何端口(0/1/5)上运行。 (Nehalem在整数和FP之间的2c旁路延迟延迟通常不相关,因为乱序执行通常会在新的依赖链开始时将其隐藏起来。)

在SnB系列微体系结构中,异或归零的方式都不需要执行端口。在AMD和Nehalem P6 / Core2之前的Intel上,xorpspxor的处理方式相同(与矢量整数指令相同)。

使用128x矢量指令的AVX版本会将零的上半部分清零。 reg也是如此,因此vpxor xmm, xmm, xmm是将YMM(AVX1 / AVX2)或ZMM(AVX512)或任何未来向量扩展置零的好选择。 vpxor ymm, ymm, ymm不需要额外的字节来编码,并且在Intel上运行相同,但是在Zen2之前(2微秒)在AMD上运行速度较慢。 AVX512 ZMM调零将需要额外的字节(用于EVEX前缀),因此应首选XMM或YMM调零。

XMM / YMM / ZMM示例

    # Good:
 xorps   xmm0, xmm0         ; smallest code size (for non-AVX)
 pxor    xmm0, xmm0         ; costs an extra byte, runs on any port on Nehalem.
 xorps   xmm15, xmm15       ; Needs a REX prefix but that's unavoidable if you need to use high registers without AVX.  Code-size is the only penalty.

   # Good with AVX:
 vpxor xmm0, xmm0, xmm0    ; zeros X/Y/ZMM0
 vpxor xmm15, xmm0, xmm0   ; zeros X/Y/ZMM15, still only 2-byte VEX prefix

#sub-optimal AVX
 vpxor xmm15, xmm15, xmm15  ; 3-byte VEX prefix because of high source reg
 vpxor ymm0, ymm0, ymm0     ; decodes to 2 uops on AMD before Zen2


    # Good with AVX512
 vpxor  xmm15,  xmm0, xmm0     ; zero ZMM15 using an AVX1-encoded instruction (2-byte VEX prefix).
 vpxord xmm30, xmm30, xmm30    ; EVEX is unavoidable when zeroing zmm16..31, but still prefer XMM or YMM for fewer uops on probable future AMD.  May be worth using only high regs to avoid needing vzeroupper in short functions.
    # Good with AVX512 *without* AVX512VL (e.g. KNL / Xeon Phi)
 vpxord zmm30, zmm30, zmm30    ; Without AVX512VL you have to use a 512-bit instruction.

# sub-optimal with AVX512 (even without AVX512VL)
 vpxord  zmm0, zmm0, zmm0      ; EVEX prefix (4 bytes), and a 512-bit uop.  Use AVX1 vpxor xmm0, xmm0, xmm0 even on KNL to save code size.


请参见使用xmm寄存器比ymm在AMD Jaguar / Bulldozer / Zen上进行vxorps调零是否更快?和清除Knights Landing上单个或几个ZMM寄存器的最有效方法是什么?半相关:将__m256值设置为所有ONE位并将CPU寄存器中的所有位有效设置为1的最快方法还涵盖了AVX512 k0..7屏蔽寄存器。 SSE / AVX vpcmpeqd在许多方面都处在中断状态(尽管仍然需要uop来写1),但是ZMM regs的AVX512 vpternlogd甚至都没有中断。在循环内部,请考虑从另一个寄存器复制,而不是使用ALU uop(特别是使用AVX512)重新创建一个寄存器。

但是清零很便宜:对一个循环内的xmm reg进行xor归零通常和复制,但在某些AMD向量(Bulldozer和Zen)上具有运动消除功能的矢量调节器除外,但仍需要ALU uop才能将零写入以进行异或归零。


归零有何特殊之处各种uarch上的xor等惯用语

某些CPU将sub same,same识别为归零惯用语,例如xor,但是识别任何归零惯用语的所有CPU都识别xor。只需使用xor,就不必担心哪个CPU可以识别哪个清零习惯。

xor(与mov reg, 0不同,它是一种公认​​的清零习惯),具有一些明显的和微妙的优点(摘要列表,然后我将在其上进行扩展):


代码尺寸比mov reg,0小。 (所有CPU)
避免了部分寄存器的罚款,以防以后的代码出现。 (英特尔P6系列和SnB系列)。
不使用执行单元,从而节省了功率并释放了执行资源。 (Intel SnB-family)
较小的uop(没有即时数据)在uop缓存行中留出空间,以便附近的指令在需要时借用。 (英特尔SnB系列)。

不会耗尽物理寄存器文件中的条目。 (至少Intel SnB系列(和P4),可能也是AMD,因为它们使用类似的PRF设计,而不是像Intel P6系列微体系结构那样在ROB中保持寄存器状态。)


更小的机器代码大小(2个字节而不是5个字节)始终是一个优点:更高的代码密度导致更少的指令高速缓存未命中,以及更好的指令提取和解码带宽。


在Intel SnB系列微体系结构上不对xor使用执行单元的好处很小,但可以节省功耗。在SnB或IvB上,这更重要,因为它们只有3个ALU执行端口。 Haswell及其更高版本具有4个执行端口,可以处理整数ALU指令,包括mov r32, imm32,因此,通过调度程序的完美决策(实际上并非总是如此),即使它们全都需要,HSW仍可以每个时钟维持4 oups ALU执行端口。

有关更多清零寄存器的其他问题,请参见我的回答。

Bruce Dawson在Michael Petch链接的博客文章中(对问题的评论)指出xor是在寄存器重命名阶段进行处理的,不需要执行单元(未融合域中的零微指令),但是错过了在融合领域仍然是一个事实的事实。现代的Intel CPU可以每个时钟发出和退出4个融合域uops。这就是每个时钟限制4个零的来源。寄存器重命名硬件的复杂性增加只是将设计宽度限制为4的原因之一。(Bruce撰写了一些非常出色的博客文章,例如他关于FP数学和x87 / SSE /舍入问题的系列文章,强烈推荐)。


在AMD Bulldozer系列CPU上,mov immediatexor在相同的EX0 / EX1整数执行端口上运行。 mov reg,reg也可以在AGU0 / 1上运行,但这仅用于寄存器复制,而不能用于立即数设置。因此,在AMD上AFAIK与xor相比,mov的唯一优势是编码更短。它也可能节省物理寄存器资源,但我还没有看到任何测试。


公认的归零习惯避免了Intel CPU上的部分寄存器惩罚,该惩罚将部分寄存器与完全寄存器分开重命名(P6和SnB家庭)。

xor会将寄存器标记为上部归零,因此xor eax, eax / inc al / inc eax避免了IvB之前的CPU通常存在的部分寄存器代价。即使没有xor,只要修改高8位(AH)然后读取整个寄存器,然后Haswell甚至将其删除,IvB也只需合并uop。

从Agner Fog的微体系结构指南,第98页( Pentium M部分,后面的部分包括SnB):


处理器将寄存器自身的XOR识别为
,将其设置为零。寄存器中的一个特殊标记记住该寄存器的高位部分
为零,因此EAX = AL。即使在循环中也可以记住此标记:

    ; Example    7.9. Partial register problem avoided in loop
    xor    eax, eax
    mov    ecx, 100
LL:
    mov    al, [esi]
    mov    [edi], eax    ; No extra uop
    inc    esi
    add    edi, 4
    dec    ecx
    jnz    LL


(来自pg82):处理器记住,只要AAX的高24位为零即可。
您不会遇到中断,错误预测或其他序列化事件。


该指南的pg82还确认mov reg, 0至少在以下情况下未被识别为归零成语。早期的P6设计,例如PIII或PM。如果他们在以后的CPU上花费晶体管来检测它,我会感到非常惊讶。


xor设置标志,这意味着测试条件时必须小心。遗憾的是,由于setcc仅在8位目标地址中可用,因此通常需要注意避免部分注册的处罚。

如果x86-64为16/32/64位setcc r/m重新使用已删除的操作码之一(如AAM),并且谓词编码在r / m字段的源寄存器3位字段中(其他一些单操作数指令将它们用作操作码位的方式)。但是他们并没有这样做,这对x86-32还是没有帮助。

理想情况下,您应该使用xor /设置标志/ setcc /读取完整的寄存器:

...
call  some_func
xor     ecx,ecx    ; zero *before* the test
test    eax,eax
setnz   cl         ; cl = (some_func() != 0)
add     ebx, ecx   ; no partial-register penalty here


这在所有CPU上都具有最佳性能(无停顿,合并uops或错误的依赖项)。在标志设置指令之前。例如您想在一个条件下分支,然后从同一标志在另一个条件下设置setcc。例如cmp/jlesete,或者您没有备用寄存器,或者您希望将xor完全排除在未采用的代码路径之外。

没有公认的归零惯用法影响标志,因此最佳选择取决于目标微体系结构。在Core2上,插入合并的uop可能会导致2或3个周期的停顿。它在SnB上似乎更便宜,但我并没有花费太多时间来进行测量。使用mov reg, 0 / setcc将对较旧的Intel CPU造成重大损失,而在较新的Intel CPU上仍会更糟。

如果您不能在标志设置指令之前进行异或为零,则使用setcc / movzx r32, r8可能是Intel P6&SnB系列的最佳选择。这应该比在异或归零后重复测试更好。 (甚至不考虑sahf / lahfpushf / popf)。 IvB可以消除movzx r32, r8(即,通过寄存器重新命名来处理它,而无需执行单位或等待时间,例如异或归零)。 Haswell及其以后只消除了常规的mov指令,因此movzx采用执行单元并且具有非零延迟,这使得test / setcc / movzxxor / test / setcc差,但仍然至少与test / mov r,0 / setcc一样好(并且在较旧的CPU上要好得多。)

在AMD / P4 / Silvermont上使用不先清零的setcc / movzx是不好的,因为它们不会分别跟踪子寄存器的deps。寄存器的旧值将有错误的查询。如果不选择mov reg, 0 / test / setcc,则使用xor / setcc进行调零/打破依赖关系可能是最佳选择。当然,如果不需要setcc的输出,宽度大于8位,则无需将任何内容归零。但是,如果选择的寄存器最近是长依赖链的一部分,请当心对除P6 / SnB以外的CPU的错误依赖。 (并且要小心,如果您调用的函数可能会保存/恢复您正在使用的部分寄存器,则可能会导致部分reg停顿或额外的uop。)


立即数为零的and不是在我所知道的任何CPU上,都没有特殊的情况,因为它独立于旧值,因此不会破坏依赖链。与xor相比,它没有优点,也有很多缺点。 />

有关微体系结构的详细信息,请参见http://agner.org/optimize/,包括哪些调零成语被识别为依赖项中断(例如,sub same,same在某些但不是所有CPU上,而xor same,same在所有CPU上都可以识别。)mov确实中断了对寄存器的旧值(与源值无关,是否为零,因为mov就是这样工作的)。 xor仅在src和dest是同一寄存器的特殊情况下才断开依赖关系链,这就是为什么mov不在特殊识别的依赖破坏者列表中的原因。 (此外,因为它不被认为是清零习惯,还有其他好处。)

有趣的是,最古老的P6设计(PPro至Pentium III)没有将xor -zeroing视为依赖项。 -breaker,仅用作清零习惯,以避免部分寄存器停顿,因此在某些情况下,同时使用movxor是值得的-为了使dep中断然后再次清零,将其清零+将内部标记位表示高位为零,因此EAX = AX = AL。

请参见Agner Fog的示例6.17。在他的microarch pdf中。他说,这也适用于P2,P3,甚至(早期?)PM。在链接的博客文章中的评论说,只有PPro受到了这种监督,但是我已经在Katmai PIII上进行了测试,而@Fanael在Pentium M上进行了测试,我们都发现它并没有打破延迟的依赖关系。 Q4312079q链。不幸的是,这证实了Agner Fog的结果。只要您不引入代码大小以外的性能问题,就避免触摸标志。避免破坏标志是不使用imul的唯一明智的原因,但是如果您有备用寄存器,有时您可以在设置标志的值之前进行异或为零。

mov-在xor之前的零延迟比在mov之后的延迟更好(在Intel上,您可以选择其他寄存器时除外),但代码大小更差。

评论


大多数算术指令OP R,S被乱序的CPU强制等待寄存器R的内容被先前的指令以寄存器R为目标填充;这是数据依赖性。关键是英特尔/ AMD芯片具有特殊的硬件,可以在遇到XOR R,R时打破对寄存器R的等待数据依赖,而对于其他寄存器清零指令则不一定如此。这意味着可以安排XOR指令立即执行,这就是Intel / AMD建议使用它的原因。

–伊拉克·巴克斯特
2015年11月12日10:41



@IraBaxter:是的,只是为了避免造成任何混淆(因为我对SO抱有这样的误解),mov reg,src也会中断面向OO CPU的dep链(无论​​src是imm32,[mem]还是其他寄存器)。在优化手册中没有提到这种打破依赖的情况,因为这不是特殊情况,只有在src和dest是同一寄存器时才会发生。不依赖于目标的指令总是会发生这种情况。 (除了Intel的popcnt / lzcnt / tzcnt的实现在目标上有错误的dep之外。)

– Peter Cordes
2015年11月12日11:15



@Zboson:没有依赖关系的指令的“延迟”仅在管道中存在气泡时才重要。这对于消除运动很有用,但对于调零指令,零延迟优势只有在出现分支错误预测或I $ miss等情况下才会发挥作用,执行等待解码的指令,而不是数据准备就绪。但是,是的,消除运动并不能使运动自由,只有零延迟。 “不占用执行端口”部分通常并不重要。尤其是,融合域的吞吐量很容易成为瓶颈。与负载或存储混合。

– Peter Cordes
2015年11月12日下午13:35

根据Agner的说法,KNL无法识别64位寄存器的独立性。因此xor r64,r64不仅浪费字节。正如您所说的xor r32,r32是最佳选择,尤其是对于KNL。如果您想了解更多信息,请参见本micrarch手册中的15.7节“特殊情况”。

–Z玻色子
16 Dec 22'在10:22



嗯,旧的MIPS哪里好,需要时带有“零寄存器”。

– Hayalci
17-12-29 at 0:24