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

以下所有说明都做同样的事情:将%eax设置为零。 哪种方式最优(需要最少的机器周期)?

xorl   %eax, %eax
mov    $0, %eax
andl   $0, %eax

TL; DR总结xor same, same 对于所有的CPU来说都是最好的选择 。 没有其他方法比它具有任何优势,并且它至少比其他方法有一些优势。 它由英特尔和AMD正式推荐。 在64位模式下,仍然使用xor r32, r32 ,因为在32位上写入一个32位的reg零xor r64, r64浪费了一个字节,因为它需要一个REX前缀。

清零矢量寄存器通常最好用pxor xmm, xmm 。 这通常是gcc所做的(甚至在与FP指令一起使用之前)。

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

在SnB系列微架构中,xor-zeroing的任何一种风格都不需要执行端口。 在AMD和Nehalem P6 / Core2之前, xorpspxor的处理方式与矢量整数指令相同。

使用128b向量指令的AVX版本也会将reg的上半部分置0,因此vpxor xmm, xmm, xmm是清零YMM(AVX1 / AVX2)或ZMM(AVX512)或任何未来矢量扩展的理想选择。 vpxor ymm, ymm, ymm不需要额外的字节来编码,而且运行相同。 AVX512 ZMM调零需要额外的字节(用于EVEX前缀),所以应该首选XMM或YMM调零。


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

xor (与mov reg, 0不同,它是一个公认的调零习惯用法)有一些明显的和一些微妙的优点(总结列表,然后我将对其进行扩展):

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

  • 较小的机器码大小 (2个字节而不是5个)总是一个优点:更高的代码密度导致更少的指令缓存未命中,更好的指令取出和潜在的解码带宽。


    在英特尔SnB系列微架构上不使用 xor 执行单元的好处很小,但可以节省电力。 对于只有3个ALU执行端口的SnB或IvB,它更有可能是重要的。 Haswell和后来的4个执行端口可以处理整数ALU指令,包括mov r32, imm32 ,所以通过调度程序做出完美的决策(实践中不会发生这种情况),HSW仍然可以保持每个时钟4个uops,即使它们都需要执行端口。

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

    Bruce Dawson的博文中Michael Petch指出, xor在寄存器重命名阶段处理,不需要执行单元(在未融合域中为零),但错过了它仍然是一个事实在融合的领域中。 现代英特尔CPU可以在每个时钟发布并退出4个融合域微软。 这是每个时钟限制4个零来自的地方。 寄存器重命名硬件的复杂性增加仅仅是将设计宽度限制为4的原因之一。(Bruce写了一些非常优秀的博客文章,例如他关于FP数学和x87 / SSE /舍入问题的系列文章,我这样做强烈推荐)。


    在AMD Bulldozer家族CPU上mov immediate运行在与xor相同的EX0 / EX1整数执行端口上。 mov reg,reg也可以在AGU0 / 1上运行,但这只适用于寄存器复制,不适用于立即设置。 所以AFAIK,在AMD上xor over mov的唯一优点是编码较短。 它也可能节省物理寄存器资源,但我还没有看到任何测试。


    可识别的归零习惯避免了对部分寄存器(P6和SnB系列)进行重新命名的英特尔CPU上的部分寄存器惩罚

    xor会将寄存器标记为高位部分为零 ,因此xor eax, eax / inc al / inc eax避免了IvB之前的CPU具有的通常的部分寄存器损失。 即使没有xor ,当高8位( AH )被修改,然后整个寄存器被读取时,IvB只需要一个合并的uop,Haswell甚至会删除它。

    来自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):只要您没有得到中断,错误预测或其他序列化事件,处理器就会记住EAX的高24位为零。

    该指南的mov reg, 0 82页也证实mov reg, 0不被认为是调零成语,至少在P6或PM等早期的P6设计中。 如果他们花费晶体管在后来的CPU上检测晶体管,我会非常惊讶。


    xor设置标志 ,这意味着在测试条件时你必须小心。 由于setcc不幸只能用于8位目标 ,所以通常需要注意避免部分寄存器的惩罚。

    如果x86-64为16/32/64位setcc r/m重新设置了一个被删除的操作码(如AAM),并将谓词编码在setcc r/m的源寄存器3位字段中字段(一些其他单操作数指令将它们用作操作码位的方式)。 但他们没有这样做,而这对x86-32无济于事。

    理想情况下,你应该使用xor / set flags / setcc / read full register:

    ...
    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上仍然会有所恶化。

    使用setcc / movzx r32, r8对于Intel P6和SnB系列来说可能是最好的选择,如果在标志设置指令之前不能执行异或操作。 这应该比在xor-zeroing之后重复测试更好。 (甚至不要考虑sahf / lahfpushf / popf )。 IvB可以消除movzx r32, r8 (即处理寄存器重命名而没有执行单元或延迟,如异或)。 Haswell和后来movzx除了常规的mov指令,所以movzx需要一个执行单元并且具有非零延迟,使得test / setcc / movzxxor / test / setcc更差,但仍然至少与test / mov r,0 / setcc (在旧CPU上更好)。

    AMD / P4 / Silvermont使用setcc / movzx而不先movzx零,因为它们不会分别追踪子寄存器的deps。 这个登记册的旧价值会被误解。 当xor / test / setcc不是选项时mov reg, 0使用mov reg, 0 / setcc进行清零/依赖关系可能是最好的选择。

    当然,如果你不需要setcc的输出宽于8位,你不需要任何零。 但是,如果您选择最近是长依赖链的一部分的寄存器,请注意P6 / SnB以外的CPU的错误依赖关系。 (如果您调用可能保存/恢复您正在使用的寄存器的函数,请注意造成部分注册或额外的升级。)


    and立即为零并不是独立于任何我知道的CPU上的旧值,因此它不会破坏依赖链。 它与xor没有什么优势,还有很多缺点。

    请参阅http://agner.org/optimize/获取微文档文档,其中包括哪些归零习惯被认为是依赖关系中断(例如, sub same,same在一些但不是全部CPU上是xor same,samexor same,same在所有CPU上都是xor same,same 。) mov确实会破坏寄存器旧值的依赖关系链(无论源值是否为0,因为这就是mov工作原理)。 xor只在src和dest是同一个寄存器的特殊情况下破坏依赖链,这就是为什么mov被排除在专门识别的依赖断路器列表之外的原因。 (另外,因为它不被认为是一个零调整的习惯用语,还有其他的好处。)

    有趣的是,最古老的P6设计(PPro)并不认为xor -zeroing是一个依赖断言器,仅仅是为了避免部分寄存器停顿的零点习惯用法,所以在某些情况下值得使用它们。 (参见Agner Fog的例子6.17。在他的microarch pdf中,他声称这也适用于P2,P3甚至(早期?)PM,但我对此持怀疑态度。对相关博客文章的评论说,它只是PPro有这种监督,看起来确实不太可能多代P6家族没有认识到xor-zeroing是一个破坏破坏者。)


    如果它真的让你的代码更好或者保存指令,那么肯定的话,用mov来避免触及标志,只要你不会引入代码大小以外的性能问题。 尽管如此,避免破坏标志是不使用xor的唯一明智理由。

    链接地址: http://www.djcxy.com/p/28765.html

    上一篇: What is the best way to set a register to zero in x86 assembly: xor, mov or and?

    下一篇: The data types the 4 different casts can take (or normally take) in c++