在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之前, xorps和pxor的处理方式与矢量整数指令相同。 
  使用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) 较小的机器码大小 (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/jle , sete ,并且您没有备用寄存器,或者您想将xor全部保留在未采用的代码路径之外。 
  没有公认的归零成语不影响标志,因此最好的选择取决于目标微体系结构。  在Core2上,插入一个合并的uop可能会导致2或3个周期的停顿。  SnB似乎更便宜,但我没有花太多时间去测量。  使用mov reg, 0 / setcc会对旧的Intel CPU造成重大损失,而在新的Intel上仍然会有所恶化。 
  使用setcc / movzx r32, r8对于Intel P6和SnB系列来说可能是最好的选择,如果在标志设置指令之前不能执行异或操作。  这应该比在xor-zeroing之后重复测试更好。  (甚至不要考虑sahf / lahf或pushf / popf )。  IvB可以消除movzx r32, r8 (即处理寄存器重命名而没有执行单元或延迟,如异或)。  Haswell和后来movzx除了常规的mov指令,所以movzx需要一个执行单元并且具有非零延迟,使得test / setcc / movzx比xor / 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,same而xor 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的唯一明智理由。 
上一篇: 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++
