在x86汇编中将寄存器设置为零的最佳方法是什么:xor,mov或and?


Answers:


222

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可以说得通。它比短1个字节pxor,但是xorps需要Intel Nehalem上的执行端口5,而pxor可以在任何端口(0/1/5)上运行。(Nehalem在整数和FP之间的2c旁路延迟等待时间通常无关紧要,因为乱序执行通常会在新的依赖链开始时将其隐藏)。

在SnB系列微体系结构上,异或归零的形式都不需要执行端口。在AMD和预Nehalem的P6 / 2英特尔,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调零是否更快?
什么是清除单个或骑士着陆几个ZMM寄存器的最有效方法是什么?

半相关:将__m256值设置为所有ONE位并将
CPU寄存器中的所有位有效设置为1的最快方法也涵盖了AVX512 k0..7掩码寄存器。SSE / AVX vpcmpeqd在许多方面都处在中断状态(尽管仍然需要写一个1),但是vpternlogdZMM regs的AVX512甚至没有中断。在循环内部,请考虑从另一个寄存器复制,而不是使用ALU uop(特别是使用AVX512)重新创建一个。

但是清零很便宜:在循环中对xmm reg进行零校正通常与复制一样好,除了在某些AMD CPU(Bulldozer和Zen)上具有对向量reg的消除功能外,但仍需要ALU uop才能为xor写入零。 -归零。


在各种uarches上将诸如xor之类的成语清零的特殊之处

有些CPU sub same,same像归零习惯一样识别xor,但是所有识别任何归零习惯的CPU都可以识别xor。只需使用即可xor,您不必担心哪个CPU可以识别哪个清零习惯。

xor(不同于,它是公认的归零惯用法mov reg, 0)具有一些明显和微妙的优点(摘要列表,然后我将对其进行扩展):

  • 比的代码小mov reg,0。(所有CPU)
  • 避免对稍后的代码进行部分注册惩罚。(英特尔P6系列和SnB系列)。
  • 不使用执行单元,从而节省了电量并释放了执行资源。(英特尔SnB系列)
  • 较小的uop(没有立即数据)会在uop缓存行中留出空间,以便附近的指令在需要时借用。(英特尔SnB系列)。
  • 不会用完物理寄存器文件中的条目。(至少英特尔SnB系列(和P4),可能也是AMD,因为它们使用类似的PRF设计,而不是像英特尔P6系列微体系结构那样在ROB中保持寄存器状态。)

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


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

有关更多详细信息,请参见我对另一个有关将寄存器清零的问题的答案

布鲁斯·道森(Bruce Dawson)在迈克尔·帕奇(Michael Petch)所链接的博客文章中(对问题的评论)指出,该问题xor是在寄存器重命名阶段进行处理的,不需要执行单元(未融合域中的零微指令),但是却错过了它仍然是一个微指令的事实。在融合域中。现代的Intel CPU可以每个时钟发出和退出4个融合域uops。这就是每个时钟限制4个零的来源。增加了寄存器重命名的硬件复杂性只是其中的原因限制了设计的宽度为4一(布鲁斯已经写了一些非常优秀的博客文章,就像他在系列FP数学和的x87 / SSE /四舍五入的问题,这是我做的极力推荐)。


在AMD推土机系列CPUmov immediate在同一EX0 / EX1整数执行港口的运行xormov reg,reg也可以在AGU0 / 1上运行,但这仅用于寄存器复制,而不用于立即数的设置。所以,据我所知,在AMD的唯一优势xormov越短的编码。它也可能节省物理寄存器资源,但是我还没有看到任何测试。


公认的清零习惯避免了对Intel CPU的部分寄存器惩罚,后者将部分寄存器与完整寄存器(P6和SnB系列)分开重命名。

xor标记寄存器为具有上部归零,所以xor eax, eax/ inc al/ inc eax避免了通常的局部寄存器惩罚该预IVB CPU具有。即使没有xor,仅当AH修改高8位()然后读取整个寄存器时,IvB才需要合并uop ,而Haswell甚至将其删除。

摘自Agner Fog的微体系结构指南,第98页(Pentium M部分,随后包括SnB的部分参考):

处理器将寄存器与自身的异或识别为零。寄存器中的一个特殊标记记住该寄存器的高位为零,因此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):处理器记住,只要您没有中断,错误预测或其他序列化事件,EAX的高24位就为零。

该指南的pg82还确认,至少在早期的P6设计(如PIII或PM)上,mov reg, 0被视为归零习惯。如果他们花晶体管在以后的CPU上检测它,我会感到非常惊讶。


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

如果x86-64将已删除的操作码之一(例如AAM)重新定位为16/32/64位setcc r/m,而谓词编码在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/jle,,sete或者您没有备用寄存器,或者您希望将其完全xor排除在未使用的代码路径之外。

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

如果您不能在标志设置指令之前进行异或为零,则使用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而不进行归零是不好的,因为它们不会分别跟踪子寄存器的dep。寄存器的旧值将有错误的查询。当/ test / 不是选项时,使用mov reg, 0/ setcc进行归零/打破依赖关系可能是最好的选择。xorsetcc

当然,如果您不需要setcc输出的宽度大于8位,则无需将任何内容归零。但是,请注意,如果您选择的寄存器最近是长依赖链的一部分,那么除了P6 / SnB以外,还要注意对CPU的错误依赖。(并且要小心,如果调用一个可能保存/恢复您正在使用的部分寄存器的函数,则会导致部分注册表停顿或额外的uop。)


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

仅当您希望将依赖项作为等待时间测试的一部分,而又想通过归零和添加来创建一个已知值时,它才对编写微基准有用。


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

有趣的是,最古老的P6设计(通过的PPro奔腾III)承认xor-zeroing作为依赖断路器,仅作为用于避免局部寄存器档位的目的,调零成语,所以在某些情况下,这是值得使用二者 mov,然后xor-以该顺序清零以断开dep,然后再次清零+将内部标记位设置为高位为零,因此EAX = AX = AL。

参见Agner Fog的示例6.17。在他的microarch pdf中。他说,这也适用于P2,P3,甚至(早期?)PM。 在链接的博客文章上的评论说,只有PPro受到了监督,但是我已经在Katmai PIII上进行了测试,而@Fanael在Pentium M上进行了测试,我们都发现它并没有打破延迟的依赖关系。界imul链。不幸的是,这证实了Agner Fog的结果。


TL:DR:

如果它确实使您的代码更好,或保存了指令,那么mov只要您不引入代码大小以外的性能问题,就可以肯定地使用0 来避免触及这些标志。避免破坏标志是不使用的唯一明智的原因xor,但是如果您有备用寄存器,有时您可以在设置标志的值之前进行异或为零。

mov-零提前setcc比延迟更好movzx reg32, reg8(在Intel可以选择不同的寄存器时除外),但延迟更短。


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

3
@IraBaxter:是的,并且只是为了避免造成任何混乱(因为我对SO抱有这样的误解),mov reg, src也打破了OO CPU的dep链(无论​​src是imm32 [mem],还是其他寄存器)。在优化手册中没有提到此依赖断开,因为这不是特殊情况,只有在src和dest是同一寄存器时才会发生。它总是发生于那些不依赖于他们的DEST说明。(除了英特尔实施的popcnt/lzcnt/tzcnt在目标上执行虚假dep的操作。)
彼得·科德斯

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

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

3
嗯,旧的MIPS哪里好需要时带有“零寄存器”
hayalci
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.