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

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

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

1
2
3
xorl   %eax, %eax
mov    $0, %eax
andl   $0, %eax


TL;DR总结:xor same, same是所有CPU的最佳选择。没有其他方法比它有任何优势,而且它至少比任何其他方法有一些优势。这是英特尔和AMD的官方推荐。在64位模式下,仍然使用xor r32, r32,因为写入32位寄存器0会使上32位变为零。xor r64, r64浪费了一个字节,因为它需要一个rex前缀。好的。

更糟糕的是,silvermont只将xor r32,r32识别为DEP中断,而不是64位操作数大小。因此,即使由于归零r8..r15而仍然需要rex前缀,也要使用xor r10d,r10d,而不是xor r10,r10。好的。

实例:好的。

1
2
3
4
5
6
7
8
9
xor   eax, eax       ; RAX = 0
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
mov   eax, 0        ; doesn't touch FLAGS, but not faster and takes more bytes

调零矢量寄存器通常最好用pxor xmm, xmm来完成。这通常是GCC所做的(甚至在与fp指令一起使用之前)。好的。

xorps xmm, xmm是有意义的。它比pxor短一个字节,但xorps需要在Intel Nehalem上使用执行端口5,而pxor可以在任何端口(0/1/5)上运行。(Nehalem在integer和fp之间的2c旁路延迟通常不相关,因为无序执行通常会在新的依赖链开始时隐藏它)。好的。

在SNB家族的微体系结构中,XOR归零的味道都不需要执行端口。在AMD和Pre-Nehalem P6/Core2 Intel上,xorpspxor的处理方式相同(与矢量整数指令相同)。好的。

使用128B矢量指令的AVX版本也会使寄存器的上部归零,因此vpxor xmm, xmm, xmm对于归零YMM(AVX1/AVX2)或ZMM(AVX512)或任何未来的矢量扩展都是一个不错的选择。但是,vpxor ymm, ymm, ymm不需要额外的字节来编码,并且运行相同的代码。AVX512 ZMM归零需要额外的字节(用于evex前缀),因此应首选xmm或ymm归零。好的。

有些CPU将sub same,same识别为一种归零习惯用法,如xor,但所有识别任何归零习惯用法的CPU都识别xor。只需使用xor,就不必担心哪个CPU识别哪个调零习惯用法。好的。

xor(与mov reg, 0不同,xor是公认的归零习语)有一些明显的和微妙的优势(总结清单,然后我将在这些方面进行扩展):好的。

  • mov reg,0更小的代码大小。(所有CPU)
  • 避免对以后的代码进行部分注册惩罚。(Intel P6系列和SNB系列)。
  • 不使用执行单元,节省电源并释放执行资源。(英特尔SNB系列)
  • 较小的UOP(无即时数据)会在UOP缓存线中留下空间,以便在需要时获取附近的指令。(英特尔SNB系列)。
  • 不会用尽物理寄存器文件中的条目。(至少Intel SNB系列(和P4)也可能是AMD,因为他们使用类似的PRF设计,而不是像Intel P6系列微体系结构那样在Rob中保持注册状态。)

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

在Intel SNB系列微体系结构上不使用XOR执行单元的好处很小,但可以节省电力。在只有3个ALU执行端口的SNB或IVB上更可能发生问题。haswell和后来的haswell有4个执行端口,可以处理整数的ALU指令,包括mov r32, imm32,因此,通过调度程序的完美决策(实际上没有),即使HSW都需要执行端口,它仍然可以支持每时钟4个Uops。好的。

请参阅我对另一个关于调零寄存器的问题的回答,了解更多详细信息。好的。

布鲁斯·道森在迈克尔·佩奇链接的博客中(在对这个问题的评论中)指出,xor是在注册重命名阶段处理的,不需要执行单元(在未使用的域中为零Uops),但忽略了它仍然是融合域中的一个Uops这一事实。现代Intel CPU每时钟可发出和注销4个融合域UOP。这就是每个时钟限值4个零的来源。寄存器重命名硬件的复杂性增加只是将设计宽度限制为4的原因之一。(布鲁斯写了一些非常优秀的博客文章,比如他的关于FP数学和X87/SSE/舍入问题的系列文章,我强烈推荐)。好的。

在AMD推土机系列CPU上,mov immediate运行在与xor相同的ex0/ex1整数执行端口上。mov reg,reg也可以在agu0/1上运行,但这只用于寄存器复制,而不用于立即设置。因此,在AMD上,与xor相比,mov的唯一优势是编码更短。它还可以节省物理注册资源,但我没有看到任何测试。好的。

公认的归零习惯用法避免了英特尔CPU上的部分寄存器惩罚,后者将部分寄存器与完整寄存器(p6&snb系列)分开重命名。好的。

xor将把寄存器的上半部分标记为零,因此xor eax, eax/inc al/inc eax避免了以前的IVB CPU通常的部分寄存器惩罚。即使没有xor,当修改高位8位(AH时,ivb也只需要一个合并Uop,然后读取整个寄存器,而haswell甚至删除了它。好的。

摘自Agner Fog的Microarch指南,第98页(Pentium M部分,后面部分包括SNB参考):好的。

The processor recognizes the XOR of a register with itself as setting
it to zero. A special tag in the register remembers that the high part
of the register is zero so that EAX = AL. This tag is remembered even
in a loop:

Ok.

1
2
3
4
5
6
7
8
9
10
    ; 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

(from pg82): The processor remembers that the upper 24 bits of EAX are zero as long as
you don't get an interrupt, misprediction, or other serializing event.

Ok.

该指南的第82页也证实了mov reg, 0不被认为是归零习语,至少在早期的p6设计中,如piii或pm。如果他们把晶体管花在后来的CPU上检测,我会非常惊讶的。好的。(P)EDOCX1 0 silian sets flags,which means you have to be careful when testing conditions.由于EDOCX1的英文本是不及时的,只有在一个8比特的目的地可供使用,你通常需要注意避免具体的刑事登记。好的,好的。(P)如果X86-64被重新命名的OPCodes(Like AAM)中有一个是16/32/64 Bit EDOCX1的英文字母2,而且传道者在R/M外勤地的来源地——登记处3-比特地(The way some other single-operation instructions use the m as OPCode bits),那么它将是一个独特的例子。但是,他们没有说,而且他们不会帮助X86-32任何方式。好的,好的。(P)理想主义者,你应该使用0/set flags/EDOCX1,1/read full register:好的,好的。字母名称(P)这是所有CPUS(不是Stalls,Merging Uops,就是fals dependencies)的最佳表现。好的,好的。(P)当你不想在发出指示之前就这么做的时候,事情就更加复杂了。E.G.You want to branch on on e condition and then setcc on another condition from the same flags.E.G.EDOCX1(英文)5,EDOCX1(英文)6,而你却没有斯巴达登记册,或你想保留EDOCX1(英文)0,而不采用适当的编码。好的,好的。(P)There are no recognized zeroing idions that don't affect flags,so the best choice depends on the target microarchitecture.On Core2,插入Merging Uop might cause 2 or 3 cycle stall.It appears to be cheaper on SNB,but I did't spend much time trying to measure.Using EDOCX1 original 8/EDOCX1 penalty 1 communal would have a significant penalty on older intel cpus,and still be some worse on newer intel.好的,好的。(P)1.Using EDOCX1 original 1/EDOCX1/Americano 11 is probably the best alternative for intel P6&;SNB families,if you can't Xor-Zero ahead of the flag-setting instruction.在Xor-Zeroing之后,最好不要重复试验。(Don't even consider EDOCX1 plus 12 welcx1/EDOCX1 plus 13 welcx1 plus 14 welcx1)IVB can eliminate EDOCX1 pental 11 silian(I.E.handle it with register-renaming with no execution unit or postacy,like Xor-Zeroing).Haswell and later only eliminate regular EDOCX1 indications,so EDOCX1 original 18.Takes an execution unit and has non-Zero潜伏,making test/EDOCX1 universal/EDOCX1/EDOCX1 indicative 18 worse than EDOCX1 indicative 0/test/EDOCX1/EDOCX1,but still at least as good test/EDOCX1/EDOCX1好的,好的。(P)1.Using EDOCX1 original/EDOCX1 plus 18 with no Zeroing first is bad on AMD/P4/Silvermont,because they don't track deps separately for sub-registers.这将是一个错误的记录,以旧的登记价值为依据。1.Using EDOCX1 original 8/EDOCX1 plus 1 for zeroing/dependency-breaking is probably the best alternative when n EDOCX1 penal 0/test/EDOCX1好的,好的。

当然,如果您不需要setcc的输出大于8位,则不需要将任何内容归零。但是,如果您选择了一个最近成为长依赖链一部分的寄存器,那么要注意对p6/snb以外的CPU的错误依赖。(如果调用的函数可能会保存/恢复正在使用的寄存器的一部分,请注意导致部分寄存器暂停或额外的UOP。)好的。

即时为零的and并不是特殊情况,因为它独立于我所知道的任何CPU上的旧值,所以它不会破坏依赖链。它与xor相比没有优势,也有许多缺点。好的。

请参阅http://agner.or g/optimize/了解微搜索文档,包括哪些归零习惯用法被认为是断开依赖关系(例如,sub same,same在某些CPU上,而不是所有CPU上,而xor same,same在所有CPU上都被识别)。mov不会断开寄存器旧值上的依赖链(无论源值是否为零,因为这就是mov的工作原理。xor只在src和dest是同一个寄存器的特殊情况下断开依赖链,这就是为什么mov被排除在特殊识别的依赖断开器列表之外的原因。(还有,因为它不被认为是一个归零的习语,还有其他好处。)好的。

有趣的是,最古老的p6设计(通过Pentium III的ppro)没有将xor调零识别为依赖性中断,只是为了避免部分寄存器暂停而将其作为调零习惯用法,因此在某些情况下,这两种方法都值得使用。(参见Agner Fog的示例6.17。在他的微创PDF中。他说这也适用于p2,p3,甚至(早期?)下午。链接博客帖子上的一条评论说,只有PPRO有这种疏忽,但我在katmai piii上进行了测试,在Pentium M上测试了@fanael,我们都发现它没有打破对延迟限制的imul链的依赖。)好的。

如果它真的使代码更好或保存了指令,那么当然,只要不引入代码大小以外的性能问题,就可以使用mov归零以避免触摸标志。不过,避免删除标志是不使用xor的唯一合理原因。好的。好啊。