gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-30 16:40:52 28 4
gpt4 key购买 nike

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

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

最佳答案

TL; DR摘要:xor same, same是所有CPU的最佳选择。没有其他方法比它有任何优势,并且它比任何其他方法都至少具有某些优势。英特尔和AMD正式推荐使用它以及编译器的功能。在64位模式下,仍然使用xor r32, r32,因为writing a 32-bit reg zeros the upper 32xor r64, r64浪费了一个字节,因为它需要一个REX前缀。

更糟糕的是,Silvermont仅将xor r32,r32识别为dep-breaking,而不是64位操作数大小。因此,即使由于将r8..r15清零而仍需要REX前缀时,也应使用xor r10d,r10d,而不是xor r10,r10

GP整数示例:

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




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

xorps xmm, xmm很有道理。它比 pxor短一个字节,但是 xorps需要Intel Nehalem上的执行端口5,而 pxor可以在任何端口(0/1/5)上运行。 (Nehalem在整数和FP之间的2c旁路延迟等待时间通常无关紧要,因为乱序执行通常会在新的依赖链开始时将其隐藏)。

在SnB系列微体系结构上,异或归零的形式都不需要执行端口。在AMD和Nehalem P6 / Core2之前的Intel上, xorpspxor的处理方式相同(作为矢量整数指令)。

使用128x向量指令的AVX版本也将reg的上部置零,因此 vpxor xmm, xmm, xmm是将YMM(AVX1 / AVX2)或ZMM(AVX512)或任何未来向量扩展置零的不错选择。但是, vpxor ymm, ymm, ymm不需要花费任何额外的字节来编码,并且在Intel上运行相同,但是在Zen2之前(2微秒)在AMD上运行较慢。 AVX512 ZMM调零将需要额外的字节(用于EVEX前缀),因此应首选XMM或YMM调零。

XMM / YMM / ZMM示例

    # 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.


参见 Is vxorps-zeroing on AMD Jaguar/Bulldozer/Zen faster with xmm registers than ymm?
What is the most efficient way to clear a single or a few ZMM registers on Knights Landing?

半相关: Fastest way to set __m256 value to all ONE bits
  Set all bits in CPU register to 1 efficiently还涵盖了AVX512 k0..7掩码寄存器。 SSE / AVX vpcmpeqd在许多方面都是断断续续的(尽管仍然需要uop来写1),但是ZMM regs的AVX512 vpternlogd甚至还没有断断续续。在循环内部,请考虑从另一个寄存器复制,而不是使用ALU uop(特别是使用AVX512)重新创建一个。

但是清零很便宜:在循环内对xmm reg进行零校正通常与复制一样好,除了在某些AMD CPU(Bulldozer和Zen)上具有对向量reg进行移动消除的AMD CPU之外,但仍需要ALU uop才能为xor写入零。 -归零。



在各种uarch上将诸如xor之类的成语清零的特殊之处

某些CPU像 sub same,same一样将 xor识别为清零习惯,但是所有识别任何清零习惯的CPU都可以识别 xor。只需使用 xor,就不必担心哪个CPU可以识别哪个清零习惯。

xor(与 mov reg, 0不同,它是一种公认​​的归零惯用法)具有一些明显和微妙的优点(摘要列表,然后我将对其进行扩展):


小于 mov reg,0的代码大小。 (所有CPU)
避免对稍后的代码进行部分注册惩罚。 (英特尔P6系列和SnB系列)。
不使用执行单元,从而节省了功率并释放了执行资源。 (英特尔SnB系列)
较小的uop(没有立即数据)会在uop缓存行中留出空间,以便附近的指令在需要时借用。 (英特尔SnB系列)。
doesn't use up entries in the physical register file。 (至少英特尔SnB系列(和P4),可能也是AMD,因为它们使用类似的PRF设计,而不是像英特尔P6系列微体系结构那样在ROB中保持寄存器状态。)




较小的机器代码大小(2个字节而不是5个字节)始终是一个优点:更高的代码密度可以减少指令高速缓存未命中的次数,并且可以更好地提取指令并可能解码带宽。



在Intel SnB系列微体系结构上不对xor使用执行单元的好处很小,但可以节省功耗。在SnB或IvB上更重要,因为它们只有3个ALU执行端口。 Haswell及其更高版本具有4个执行端口,可以处理整数ALU指令(包括 mov r32, imm32),因此,通过调度程序的完美决策(实际上并不总是这样),即使HSW即使它们始终保持每个时钟4 oups都需要ALU执行端口。

有关更多详细信息,请参见 my answer on another question about zeroing registers

Michael Petch链接的 Bruce Dawson's blog post(在问题评论中)指出, xor是在寄存器重命名阶段处理的,不需要执行单元(未融合域中的零uops),但错过了它仍然存在的事实融合域中的一个uop。现代的Intel CPU可以每个时钟发出和退出4个融合域uops。这就是每个时钟限制4个零的来源。寄存器重命名硬件的复杂性增加只是将设计的宽度限制为4的原因之一。(Bruce写了一些非常出色的博客文章,例如他强烈推荐的 FP math and x87 / SSE / rounding issues系列文章)。



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



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

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

摘自Agner Fog的微体系结构指南,第98页(Pentium M部分,随后包括SnB的部分参考):


  处理器将寄存器自身的异或识别为设置
  它为零。寄存器中的特殊标签记住
  寄存器的值是零,因此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位为零,只要
  您不会遇到中断,错误预测或其他序列化事件。


该指南的pg82还确认,至少在早期的P6设计(例如PIII或PM)上, mov reg, 0不被视为归零习惯。如果他们花晶体管在以后的CPU上检测它,我会感到非常惊讶。



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

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

理想情况下,您应该使用 xor /设置标志/ 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上都具有最佳性能(没有停顿,合并uops或错误的依赖项)。

当您不想在标志设置指令之前进行异或运算时,事情会变得更加复杂。例如您想在一个条件下分支,然后从同一标志在另一个条件下设置setcc。例如 cmp/jlesete,或者您没有备用寄存器,或者您想将 xor完全排除在未使用的代码路径之外。

没有公认的不会影响标记的清零习惯,因此最佳选择取决于目标微体系结构。在Core2上,插入合并的uop可能会导致2或3个周期的停顿。它在SnB上似乎更便宜,但我并没有花费太多时间来进行测量。使用 mov reg, 0 / setcc在较旧的Intel CPU上将有很大的损失,而在较新的Intel上仍然会更糟。

如果您不能在标志设置指令之前进行异或为零,则使用 setcc / movzx r32, r8可能是Intel P6&SnB系列的最佳选择。这应该比在异或归零后重复测试更好。 (甚至不考虑 sahf / lahfpushf / popf)。 IvB可以消除 movzx r32, r8(即通过寄存器重命名来处理它,而无需执行单元或等待时间,例如异或归零)。 Haswell及其以后仅消除常规的 mov指令,因此 movzx采用执行单元并且具有非零延迟,这使得test / setcc / movzxxor / test / setcc差,但仍然至少与test / mov r,0 / setcc一样好(在较旧的CPU上更好)。

首先在AMD / P4 / Silvermont上使用不设置零的 setcc / movzx是不好的,因为它们不会分别跟踪子寄存器的dep。寄存器的旧值将有错误的查询。如果不选择 mov reg, 0 / test / setcc,则使用 xor / setcc进行调零/打破依赖关系可能是最佳选择。

当然,如果不需要 setcc的输出宽于8位,则不需要将任何内容归零。但是,如果选择的寄存器最近是长依赖性链的一部分,请当心对除P6 / SnB以外的CPU的虚假依赖性。 (并且要小心,如果您调用的函数可能会保存/恢复您正在使用的寄存器的一部分,则会导致部分注册表停止或额外的uop。)



立即数为零的 and在我所知道的任何CPU上都没有特殊情况,因为它与旧值无关,因此不会破坏依赖链。与 xor相比,它没有优点,但有很多缺点。

请参阅 http://agner.org/optimize/了解微体系结构文档,包括哪些调零成语被识别为依赖项中断(例如, sub same,same在某些但不是所有CPU上,而 xor same,same在所有CPU上都可以识别。) mov确实打破了对寄存器的旧值(与源值无关,是否为零,因为 mov是这样工作的)。 xor仅在src和dest是同一寄存器的特殊情况下才断开依赖关系链,这就是为什么 mov不在特殊识别的依赖破坏者列表中的原因。 (此外,因为它不被视为归零习惯,还有其他好处。)

有趣的是,最古老的P6设计(从PPro到Pentium III)并没有将 xor -zeroing视为依赖破坏者,只是为了避免部分寄存器停顿而将其作为归零习惯,因此在某些情况下值得使用依次 movxor置零以中断dep,然后再次置零+将内部标记位设置为高位为零,所以EAX = AX = AL。 (请参阅Agner Fog的Example 6.17。在他的microarch pdf文件中。他说这也适用于P2,P3甚至是(早期?)PM。 A comment on the linked blog post说只有PPro对此进行了监督,但是我已经在Katmai上进行了测试。 PIII和@Fanael在奔腾M上进行了测试,我们都发现它并没有打破对延迟绑定的 imul链的依赖性。不幸的是,这证实了Agner Fog的结果。)



如果它确实使您的代码更好,或保存了指令,那么只要您没有引起代码大小以外的性能问题,就可以使用 mov设置为零以避免触及这些标志。但是,避免使用破坏性标志是不使用 xor的唯一明智的原因。

关于performance - 在x86汇编中将寄存器设置为零的最佳方法是什么:xor,mov或and?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46481490/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com