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

以下所有说明都执行相同的操作:将%eax设置为零。 哪种方法是最佳的(需要最less的机器周期)?

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

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

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

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

在SnB系列微架构中,xor-zeroing的风格甚至不需要执行端口。 在AMD和Nehalem P6 / Core2之前, xorpspxor的处理方式与vector-integer指令相同。

使用128b向量指令的AVX版本也会使reg的上半部分为零,所以vpxor xmm, xmm, xmm对于归零YMM(AVX1 / AVX2)或ZMM(AVX512)或任何未来的向量扩展都是不错的select。 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(没有即时数据)在uopcaching行中留有空间,以便在需要时借用附近的指令。 (Intel SnB系列)。
  • 不会使用物理寄存器文件中的条目 。 (至less有Intel SnB系列(和P4),可能还有AMD,因为它们使用类似的PRFdevise,而不像ROBO-P6系列微架构那样保持寄存器状态)。

较小的机器码大小 (2个字节而不是5个)始终是一个优点:更高的代码密度导致更less的指令caching缺失,更好的取指令和潜在的解码带宽。


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

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

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


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


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

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

从Agner Fog的微型指南,第98页(奔腾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 被认为是一个调零成语,至less在像PIII或PM这样的早期P6devise中。 如果他们将晶体pipe花费在更高的CPU上,我会非常惊讶。


xor设置标志 ,这意味着您在testing条件时必须小心。 由于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全部保留在未被采用的代码path之外。

没有公认的调零习语不会影响标志,所以最好的select取决于目标微体系结构。 在Core2上,插入合并的uop可能会导致2或3个周期的停顿。 SnB似乎更便宜,但我没有花太多时间去测量。 使用mov reg, 0 / setcc会对旧的Intel CPU造成重大的损失,对于新的Intel来说仍然会有所恶化。

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

先使用setcc / movzx而不进行调零对于AMD / P4 / Silvermont来说是不好的,因为它们不会为子寄存器单独追踪deps。 对登记簿的旧价值会有一个错误的看法。 当xor / test / setcc不是选项时mov reg, 0使用mov reg, 0 / setcc进行清零/依赖关系可能是最好的select。

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


and立即为零是不是特别的独立于任何CPU的旧值我知道,所以它不会打破依赖链。 它比xor没有什么优势,还有很多缺点。

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

有趣的是,最古老的P6devise(PPro) 并不认为xor -zeroing是一个依赖断言器,只是为了避免部分寄存器失速,所以在某些情况下值得使用它们 。 (参见Agner Fog的例子6.17。在他的microarch pdf中,他声称这也适用于P2,P3,甚至(早?)下午,但我对此持怀疑态度。有这种监督的情况下,似乎真的不可能有多代P6家族存在,而没有认识到xor-zeroing是一个破坏者。


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