gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 04:42:35 27 4
gpt4 key购买 nike

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

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

最佳答案

TL;DR 摘要 : xor same, same 是所有 CPU 的最佳选择 0x2518411921没有其他方法比它有任何优势,它至少比任何其他方法都有一些优势。它是 Intel 和 AMD 官方推荐的,以及编译器的作用。在 64 位模式下,仍然使用 xor r32, r32 ,因为 writing a 32-bit reg zeros the upper 32xor r64, r64 是一个字节的浪费,因为它需要一个REX前缀。
更糟糕的是,Silvermont 只将 xor r32,r32 识别为 dep-breaking,而不是 64 位操作数大小。因此 即使仍然需要 REX 前缀,因为您将 r8..r15 归零,请使用 xor r10d,r10d ,而不是 xor r10,r10 0x251819212134。
GP-整数示例:

xor eax, eax ; RAX = 0. Including AL=0 etc.
xor r10d, r10d ; R10 = 0. Still prefer 32-bit operand-size.

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 on other CPUs 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 cl, cl ; false dep on some CPUs, not a zeroing idiom. Use xor ecx,ecx
mov cl, 0 ; only 2 bytes, and probably better than xor cl,cl *if* you need to leave the rest of ECX/RCX 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 的处理方式相同(作为向量整数指令)。
使用 128b 向量指令的 AVX 版本也会将 reg 的上部归零,因此 vpxor xmm, xmm, xmm 是归零 YMM(AVX1/AVX2) 或 ZMM(AVX512) 或任何 future 向量扩展的不错选择。不过,vpxor ymm, ymm, ymm 不需要任何额外的字节来编码,并且在 Intel 上运行相同,但在 Zen2(2 uop)之前在 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 来写 1s),但用于 ZMM regs 的 AVX512 vpternlogd 甚至不是破坏性的。在循环内部考虑从另一个寄存器复制而不是使用 ALU uop 重新创建寄存器,尤其是使用 AVX512。
但归零很便宜:在循环中对 xmm reg 进行异或归零通常与复制一样好,除了在某些 AMD CPU(Bulldozer 和 Zen)上,它们对向量 reg 进行了移动消除,但仍需要 ALU uop 为 xor 写入零-调零。

在各种 uarches 上将 xor 等习语归零有什么特别之处
一些 CPU 将 sub same,same 识别为归零习语,如 xor ,但 识别任何归零习语的所有 CPU 都识别 0x25181223181342121243131342121只需使用 xor 这样您就不必担心哪个 CPU 识别哪个归零习语。xor(作为公认的归零习语,与 xor 不同)有一些明显和一些微妙的优势(总结列表,然后我将扩展这些):
  • 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 个字节)始终是一个优势:更高的代码密度导致更少的指令缓存未命中,更好的指令获取和潜在的解码带宽。

    在英特尔 SnB 系列微架构上不使用执行单元 进行异或的好处很小,但可以节省电量。在 SnB 或 IvB 上更重要,因为它们只有 3 个 ALU 执行端口。 Haswell 及更高版本有 4 个可以处理整数 ALU 指令的执行端口,包括 mov reg,0 ,因此通过调度程序的完美决策(这在实践中并不总是发生),即使它们都需要,HSW 仍然可以维持每个时钟 4 uop ALU 执行端口。
    有关更多详细信息,请参阅 my answer on another question about zeroing registers
    Michael Petch 链接的 Bruce Dawson's blog post(在对该问题的评论中)指出 mov r32, imm32 在寄存器重命名阶段处理而无需执行单元(未融合域中的零 uop),但错过了它仍然是一个 uop 的事实融合域。现代英特尔 CPU 每个时钟可以发出和停用 4 个融合域 uops。这就是每个时钟限制 4 个零的来源。寄存器重命名硬件的复杂性增加只是将设计宽度限制为 4 的原因之一。(Bruce 写了一些非常出色的博客文章,比如他关于 FP math and x87 / SSE / rounding issues 的系列文章,我强烈推荐)。

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

    已识别的归零习惯用法 避免了英特尔 CPU 上的部分寄存器惩罚 ,该 CPU 将部分寄存器与完整寄存器(P6 和 SnB 系列)分开重命名。mov 将寄存器标记为将上半部分归零 ,所以 xor/xor eax, eax/0x251818431234 部分 CPU 避免了/0x2518431234134133434334334333433433433434334343343434343434343434334343434343434343434343434343434343434343343343334即使没有 inc al ,当修改高 8 位( inc eax )然后读取整个寄存器时,IvB 只需要合并 uop,Haswell 甚至将其删除。
    来自 Agner Fog 的微架构指南,第 98 页(Pentium M 部分,包括 SnB 在内的后续部分引用):

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

        ; 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 asyou don't get an interrupt, misprediction, or other serializing event.


    该指南的第 82 页还确认 xor 不被认为是归零习惯用法,至少在 PIII 或 PM 等早期 P6 设计中是这样。如果他们在后来的 CPU 上使用晶体管来检测它,我会感到非常惊讶。

    AH 设置标志 ,这意味着您在测试条件时必须小心。不幸的是,由于 mov reg, 0 仅适用于 8 位目标 ,因此您通常需要注意避免部分寄存器惩罚。
    如果 x86-64 将已删除的操作码之一(如 AAM)重新用于 16/32/64 位 xor ,并将谓词编码在 r/m 字段的源寄存器 3 位字段(其他一些单操作数指令将它们用作操作码位的方式)。但是他们没有这样做,无论如何这对 x86-32 没有帮助。
    理想情况下,您应该使用 setcc/set flags/setcc r/m/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 。例如xorsetcc ,并且您要么没有备用寄存器,要么希望将 cmp/jle 完全排除在未采用的代码路径之外。
    没有不影响标志的公认归零习惯用法,因此最佳选择取决于目标微体系结构。在 Core2 上,插入合并 uop 可能会导致 2 或 3 个周期的停顿。它在 SnB 上似乎更便宜,但我没有花太多时间尝试测量。使用 sete/xor 会对较旧的 Intel CPU 产生重大影响,但在较新的 Intel CPU 上仍然会更糟。
    使用 mov reg, 0/setcc 可能是英特尔 P6 和 SnB 系列的最佳选择,如果您不能在标志设置指令之前进行异或零。这应该比在异或归零后重复测试要好。 (甚至不要考虑 setcc/movzx r32, r8sahf/lahf )。 IvB 可以消除 pushf(即通过寄存器重命名处理它,没有执行单元或延迟,如异或归零)。 Haswell的,后来只有消除常规popf指令,因此movzx r32, r8需要一个执行单元,具有非零延迟,使得测试/mov/movzxsetcc/测试/movzx差,但至少还要好,因为测试/xor/setcc(和在较旧的 CPU 上要好得多)。
    在 AMD/P4/Silvermont 上使用 mov r,0/setcc 而不先归零是不好的,因为它们不会单独跟踪子寄存器的 deps。寄存器的旧值会有一个错误的依赖。当 setcc/test/movzx 不是一个选项时,使用 mov reg, 0/setcc 进行归零/依赖破坏可能是最好的选择。
    当然,如果您不需要 xor 的输出宽于 8 位,则不需要将任何内容归零。但是,如果您选择的寄存器最近是长依赖链的一部分,请注意对 P6/SnB 以外的 CPU 的错误依赖。 (如果您调用的函数可能会保存/恢复您正在使用的寄存器的一部分,请注意导致部分 reg 停顿或额外的 uop。)

    setcc 立即为零 不是特殊情况,独立于我所知道的任何 CPU 上的旧值,因此它不会破坏依赖链。它与 setcc 相比没有任何优势,但有许多缺点。
    当您希望将依赖项作为延迟测试的一部分,但希望通过归零和添加来创建已知值时,它仅适用于编写微基准测试。

    http://agner.org/optimize/为microarch细节,其中包括零成语被认为是依赖打破(例如and是一些但不是所有的CPU,而xor上大家公认的。)sub same,same确实打破了依赖链上的寄存器的旧值(无论源值如何,零与否,因为这就是 xor same,same 的工作方式)。 mov 仅在 src 和 dest 是同一个寄存器的特殊情况下破坏依赖链,这就是为什么 mov 被排除在特别识别的依赖破坏者列表之外的原因。 (另外,因为它不被认为是归零习语,还有其他好处。)
    有趣的是,最古老的 P6 设计(PPro 到 Pentium III)并没有将 xor -zeroing 识别为依赖项破坏者,只是为了避免部分寄存器停顿 0x2513411921 的目的的归零习语,所以在某些情况下值得movxor 都归零以破坏 dep 然后再次归零 + 设置内部标记位,即高位为零,因此 EAX=AX=AL。
    参见 Agner Fog 的示例 6.17。在他的微拱pdf中。他说这也适用于 P2、P3,甚至(早期?)PM。 A comment on the linked blog post 说只有 PPro 有这种疏忽,但我已经在 Katmai PIII 上进行了测试,@Fanael 在 Pentium M 上进行了测试,我们都发现它没有打破对延迟限制 mov 链的依赖。不幸的是,这证实了 Agner Fog 的结果。

    电话:DR:
    如果它确实使您的代码更好或节省了指令,那么可以肯定,将 xor 归零以避免触及标志,只要您不引入代码大小以外的性能问题。避免破坏标志是不使用 imul 的唯一合理原因,但有时如果您有备用寄存器,您可以在设置标志之前进行异或零。mov - xor 之前的零比 mov 之后的延迟更好(英特尔除外,当您可以选择不同的寄存器时),但代码大小更糟。

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

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