在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++