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

bal*_*c55 109 optimization performance x86 assembly micro-optimization

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

xorl   %eax, %eax
mov    $0, %eax
andl   $0, %eax
Run Code Online (Sandbox Code Playgroud)

Pet*_*des 200

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.  Including AL=0 etc.
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
xor   r10,r10       ; bad on Silvermont (not dep breaking), same as r10d everywhere else because a REX prefix is still needed for r10d or r10.
mov   eax, 0        ; doesn't touch FLAGS, but not faster and takes more bytes
 and   eax, 0        ; false dependency.  (Microbenchmark experiments might want this)
 sub   eax, eax      ; same as xor on most but not all CPUs; bad on Silvermont for example.

xor   al, al        ; false dep on some CPUs, not a zeroing idiom.  Use xor eax,eax
mov   al, 0         ; only 2 bytes, and probably better than xor al,al *if* you need to leave the rest of EAX/RAX unmodified
Run Code Online (Sandbox Code Playgroud)

归零矢量寄存器通常最好用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英特尔,xorpspxor被处理的相同方式(如向量整数指令).

使用AVX版本的128b向量指令也会将reg的上半部分vpxor xmm, xmm, xmm归零,因此对于归零YMM(AVX1/AVX2)或ZMM(AVX512)或任何将来的向量扩展是一个很好的选择. vpxor ymm, ymm, ymm但是,不需要任何额外的字节来编码,并且运行相同.AVX512 ZMM归零将需要额外的字节(对于EVEX前缀),因此应首选XMM或YMM归零.


有些CPU认为k0..7是类似的归零vpcmpeqd,但所有识别任何归零习惯用语的CPU都能识别vpternlogd.只需使用sub same,same,您就不必担心哪个CPU识别哪个归零成语.

xor(作为公认的归零成语,不像xor)有一些明显的和一些微妙的优点(摘要列表,然后我将扩展那些):

  • 代码大小比xor.(所有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指令,包括xor,所以通过调度程序完美的决策(实际上不会发生),HSW仍然可以维持每个时钟4个uop,即使它们都需要执行端口.

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

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


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


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

mov reg,reg标记寄存器为具有上部归零,所以xor/ mov/ xor避免了通常的局部寄存器惩罚该IVB预CPU具有.即使没有xor eax, eax,当inc al修改高8位()然后读取整个寄存器时,IvB只需要合并uop ,而Haswell甚至会删除它.

来自Agner Fog的微型指南,第98页(Pentium M部分,后面的部分包括SnB参考):

处理器将自身的XOR识别为将其设置为零.寄存器中的特殊标记会记住寄存器的高位为零,因此EAX = AL.即使在循环中也会记住此标记:

    # Good:
 xorps   xmm0, xmm0         ; smallest code size (for non-AVX)
 pxor    xmm0, xmm0         ; costs an extra byte, runs on any port on Nehalem.
 xorps   xmm15, xmm15       ; Needs a REX prefix but that's unavoidable if you need to use high registers without AVX.  Code-size is the only penalty.

   # Good with AVX:
 vpxor xmm0, xmm0, xmm0    ; zeros X/Y/ZMM0
 vpxor xmm15, xmm0, xmm0   ; zeros X/Y/ZMM15, still only 2-byte VEX prefix

#sub-optimal AVX
 vpxor xmm15, xmm15, xmm15  ; 3-byte VEX prefix because of high source reg
 vpxor ymm0, ymm0, ymm0     ; decodes to 2 uops on AMD before Zen2


    # Good with AVX512
 vpxor  xmm15,  xmm0, xmm0     ; zero ZMM15 using an AVX1-encoded instruction (2-byte VEX prefix).
 vpxord xmm30, xmm30, xmm30    ; EVEX is unavoidable when zeroing zmm16..31, but still prefer XMM or YMM for fewer uops on probable future AMD.  May be worth using only high regs to avoid needing vzeroupper in short functions.
    # Good with AVX512 *without* AVX512VL (e.g. KNL / Xeon Phi)
 vpxord zmm30, zmm30, zmm30    ; Without AVX512VL you have to use a 512-bit instruction.

# sub-optimal with AVX512 (even without AVX512VL)
 vpxord  zmm0, zmm0, zmm0      ; EVEX prefix (4 bytes), and a 512-bit uop.  Use AVX1 vpxor xmm0, xmm0, xmm0 even on KNL to save code size.
Run Code Online (Sandbox Code Playgroud)

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

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


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

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

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

    ; 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
Run Code Online (Sandbox Code Playgroud)

这在所有CPU上都具有最佳性能(无停顿,合并uop或错误依赖).

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

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

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

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

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


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

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

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


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

  • 大多数算术指令OP R,S由无序CPU强制等待寄存器R的内容由先前的指令填充,寄存器R作为目标; 这是数据依赖.关键点在于,当遇到XOR R,R时,Intel/AMD芯片具有特殊的硬件来破坏寄存器R上的必须等待数据依赖性,而对于其他寄存器归零指令则不一定如此.这意味着可以安排XOR指令立即执行,这就是Intel/AMD*推荐使用它的原因. (6认同)
  • @IraBaxter:是的,为了避免任何混淆(因为我在 SO 上看到过这种误解),`mov reg, src` 也会破坏面向对象 CPU 的 dep 链(无论 src 是 imm32、`[mem]` 还是其他登记)。优化手册中没有提到这种破坏依赖关系,因为这不是仅在 src 和 dest 是同一寄存器时才会发生的特殊情况。对于不依赖于目标的指令,它*总是* 发生。(除了 Intel 的 `popcnt/lzcnt/tzcnt` 实现在 dest 上有一个错误的 dep。) (3认同)
  • @Zboson:没有依赖关系的指令的"延迟"仅在管道中存在气泡时才有意义.它对于移除消除非常好,但是对于归零指令,零延迟优势仅在分支错误预测或I $ miss之后才会发挥作用,其中执行正在等待解码的指令,而不是数据准备就绪.但是,是的,mov-elimination不会使`mov`自由,只有零延迟."不执行端口"部分通常并不重要.融合域吞吐量很容易成为瓶颈,尤其是瓶颈.在混合物中加载或存储. (2认同)
  • 根据Agner KNL不承认64位寄存器的独立性.所以`xor r64,r64`并不只是浪费一个字节.正如你所说的`xor r32,r32`是最好的选择,尤其是KNL.如果您想阅读更多内容,请参阅本分类手册中的第15.7节"独立的特殊情况". (2认同)
  • 啊,当你需要的时候,哪里有好旧[MIPS,带有"零注册"](http://www.cs.uwm.edu/classes/cs315/Bacon/Lecture/HTML/ch05s03.html). (2认同)
  • @ecm:我认为“两者”的上下文在之前的编辑中丢失了;固定的。感谢您指出。 (2认同)

归档时间:

查看次数:

36061 次

最近记录:

5 年,10 月 前