为了账号安全,请及时绑定邮箱和手机立即绑定

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

/ 猿问

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

慕斯王 2019-05-28 16:47:34

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

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


xorl   %eax, %eax

mov    $0, %eax

andl   $0, %eax


查看完整描述

1 回答

?
慕姐829404

TL; DR摘要xor same, same所有CPU最佳选择。没有其他方法比它有任何优势,它至少比任何其他方法都有一些优势。它是由英特尔和AMD正式推荐的。在64位模式下,仍然使用xor r32, r32,因为写32位寄存器会将上面的32复位。 xor r64, r64是浪费一个字节,因为它需要一个REX前缀。

更糟糕的是,Silvermont只承认xor r32,r32破坏而不是64位操作数。因此,即使因为你将r8..r15归零而仍然需要REX前缀,请使用xor r10d,r10d,而不是xor r10,r10

例子:

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在整数和FP之间的2c旁路延迟延迟通常是不相关的,因为无序执行通常可以在新的依赖链的开始处隐藏它)。


在SnB系列微体系结构中,xor-zeroing的味道都不需要执行端口。在AMD和预Nehalem的P6 / 2英特尔,xorps和pxor被处理的相同方式(如向量整数指令)。


使用AVX版本的128b向量指令也会将reg的上半部分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)有一些明显的和一些微妙的优点(摘要列表,然后我将扩展那些):

  • 代码大小比mov reg,0。(所有CPU)

  • 避免对以后的代码进行部分寄存器处罚。(英特尔P6系列和SnB系列)。

  • 不使用执行单元,节省电力并释放执行资源。(英特尔SnB系列)

  • 较小的uop(没有立即数据)在uop缓存行中留出空间,以便在需要时附近的指令借用。(英特尔SnB系列)。

  • 不会使用物理寄存器文件中的条目。(英特尔SnB系列(和P4)至少可能是AMD,因为他们使用类似的PRF设计而不是像ROB P6系列微架构那样在ROB中保持寄存器状态。)


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


在Intel SnB系列微体系结构上不使用 xor 执行单元的好处很小,但节省了功耗。它更可能与SnB或IvB有关,它只有3个ALU执行端口。Haswell以及后来有4个执行端口可以处理整数ALU指令,包括mov r32, imm32,所以通过调度程序完美决策(实际上不会发生),HSW仍然可以维持每个时钟4个uop,即使它们都需要执行端口。

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

Bruce Dawson的博客帖子 Michael Petch链接(在对问题的评论中)指出xor在注册重命名阶段处理而不需要执行单元(在未融合域中为零uops),但错过了它仍然是一个uop的事实在融合域中。现代英特尔CPU可以每个时钟发出和退出4个融合域uop。这就是每时钟限制4个零的来源。寄存器重命名硬件的复杂性增加只是将设计宽度限制为4的原因之一。(Bruce撰写了一些非常优秀的博客文章,比如关于FP数学和x87 / SSE /舍入问题的系列文章,我这样做了极力推荐)。


在AMD Bulldozer系列CPU上mov immediate运行在相同的EX0 / EX1整数执行端口上xormov reg,reg也可以在AGU0 / 1上运行,但这仅用于寄存器复制,而不是用于设置。所以,据我所知,在AMD公司唯一的优势xor过分mov的是较短的编码。它也可能节省物理寄存器资源,但我还没有看到任何测试。


公认的归零成语避免了对Intel CPU的部分寄存器处罚,后者将部分寄存器与完整寄存器(P6和SnB系列)分开重命名。

xor标记寄存器为具有上部归零,所以xor eax, eaxinc alinc eax避免了通常的局部寄存器惩罚该IVB预CPU具有。即使没有xor,当AH修改高8位()然后读取整个寄存器时,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

(来自第82页):只要您没有得到中断,错误预测或其他序列化事件,处理器就会记住EAX的高24位为零。


该引导件的pg82还证实,mov reg, 0被未识别为归零成语,至少在早期的设计P6像PIII或PM。如果他们花费晶体管在后来的CPU上检测它,我会感到非常惊讶。


xor设置标志,这意味着在测试条件时必须小心。由于setcc遗憾的是只能使用8位目的地,因此您通常需要注意避免部分注册处罚。


如果x86-64将一个被移除的操作码(如AAM)重新用于16/32/64位setcc r/m,并且在r / m字段的源寄存器3位字段中编码谓词,那就太好了一些其他单操作数指令将它们用作操作码位)。但他们没有这样做,无论如何这对x86-32没有帮助。


理想情况下,您应该使用xor/ set flags setcc//读取完整寄存器:


...

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上都具有最佳性能(无停顿,合并uop或错误依赖)。


当你不想在标志设置指令之前进行xor时,事情会变得更复杂。例如,你想在一个条件上分支,然后在同一个标志的另一个条件下setcc。例如cmp/jle,sete您要么没有备用寄存器,要么完全不使用xor未采用的代码路径。


没有公认的归零成语不会影响标志,因此最佳选择取决于目标微体系结构。在Core2上,插入合并uop可能会导致2或3个周期停顿。它似乎在SnB上更便宜,但我并没有花太多时间来测量。使用mov reg, 0/ setcc会对较旧的英特尔CPU造成重大损失,并且在较新的英特尔上仍然会有所改善。


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


在AMD / P4 / Silvermont上使用setcc/ movzx没有归零是不好的,因为它们不会分别跟踪子寄存器的deps。寄存器的旧值会有一个错误的缺陷。当/ test / 不是一个选项时,使用mov reg, 0/ setcc进行归零/依赖性破坏可能是最好的选择。xorsetcc


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

and具有立即零并不是特殊的,与我所知的任何CPU上的旧值无关,因此它不会破坏依赖链。它没有优点xor,也有许多缺点。

请参阅http://agner.org/optimize/获取microarch文档,包括哪些归零成语被识别为依赖性破坏(例如sub same,same,在某些但不是所有CPU上,而在所有CPU xor same,same上都被识别。) mov确实打破了旧值的依赖关系链寄存器(无论源值如何,零或不,因为这是mov有效的)。 xor只有在src和dest是同一个寄存器的特殊情况下才会断开依赖链,这就是为什么它被mov排除在特别识别的依赖断层列表之外。(另外,因为它不被认为是归零成语,具有其他好处。)

有趣的是,最古老的P6设计(PPro到Pentium III)并没有认识到xor- 为了避免部分寄存器停顿而仅仅作为归零用语,因此在某些情况下值得使用两者。(参见Agner Fog的例子6.17。在他的microarch pdf中。他说这也适用于P2,P3,甚至(早期?)PM。 对链接博客文章的评论说只有PPro有这种疏忽,但我'我们在Katmai PIII上进行了测试,并且@Fanael在Pentium M上进行了测试,我们都发现它没有打破延迟限制imul链的依赖性。)


如果它确实使您的代码更好或保存指令,那么确保零,mov以避免触摸标志,只要您不引入除代码大小之外的性能问题。但是,避免使用破坏标志是不使用的唯一合理理由xor


查看完整回答
反对 回复 2019-05-28
  • 1 回答
  • 0 关注
  • 440 浏览
我要回答
慕课专栏
更多

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信