【问题标题】:Best way to load/store from/to general purpose registers to/from xmm/ymm register从/从通用寄存器加载/存储到/从xmm/ymm寄存器的最佳方式
【发布时间】:2017-03-30 03:47:30
【问题描述】:

在 SIMD 寄存器中加载和存储生成目的寄存器的最佳方法是什么?到目前为止,我一直在使用堆栈作为临时工具。例如,

mov [rsp + 0x00], r8
mov [rsp + 0x08], r9
mov [rsp + 0x10], r10
mov [rsp + 0x18], r11
vmovdqa ymm0, [rsp] ; stack is properly aligned first.

我认为没有任何指令可以直接(或其他方向)执行此操作,因为这意味着一条指令有五个操作数。但是,上面的代码对我来说似乎很愚蠢。有更好的方法吗?我只能想到一种选择,使用pinsrd 和相关说明。但这似乎并没有好转。

动机是,有时在 AVX2 中执行某些操作会更快,而在其他通用寄存器中执行某些操作会更快。例如,假设在一小段代码中,有四个 64 位无符号整数,我需要来自 BMI2 的四个xor,两个mulx。使用vpxor 执行xor 会更快,但是mulx 没有AVX2 等效项。 vpxor vs 4 xor 的任何增益性能都会因打包和解包过程而丢失。

【问题讨论】:

    标签: assembly x86 simd sse2 avx2


    【解决方案1】:

    您的瓶颈是延迟、吞吐量还是融合域微指令?如果是延迟,那么存储/重新加载是可怕的,因为从窄存储到宽负载的存储转发停滞。

    对于吞吐量和融合域微指令,这并不可怕:只有 5 个融合域微指令,在存储端口上出现瓶颈。如果周边代码多为ALU uop,还是值得考虑的。


    对于您建议的用例:

    花费大量指令/微指令在整数和向量寄存器之间移动数据通常是个坏主意。 PMULUDQ 确实为您提供了相当于 32 位多路复用器的功能,但您说得对,AVX2 中不能直接使用 64 位乘法器。 (AVX512 有)。

    您可以使用 PMULUDQ 的常用扩展精度技术进行 64 位向量乘法。我对Fastest way to multiply an array of int64_t? 的回答发现,使用 AVX2 256b 向量对 64 x 64 => 64b 乘法进行向量化是值得的,但对于 128b 向量则不然。但那是内存中的数据,而不是以向量 regs 开头和结尾的数据。

    在这种情况下,可能从多个 32x32 => 64 位向量乘法中构建一个 64x64 => 128b 的全乘法是值得的,但它可能需要太多的指令,所以不值得它。如果您确实需要上半部分的结果,最好解包为标量(或做整个标量)。

    整数 XOR 非常便宜,具有出色的 ILP(延迟 = 1,吞吐量 = 每个时钟 4)。如果您在那里没有其他任何对矢量友好的事情要做,那么绝对不值得将您的数据移动到矢量 regs 中以对其进行异或。有关性能链接,请参阅tag wiki


    可能最好的延迟方法是:

    vmovq   xmm0, r8
    vmovq   xmm1, r10            # 1uop for p5 (SKL), 1c latency
    vpinsrq xmm0, r9, 1          # 2uops for p5 (SKL), 3c latency
    vpinsrq xmm1, r11, 1
    vinserti128 ymm0, ymm0, ymm1, 1    # 1uop for p5 (SKL), 3c latency
    

    总计:p5 有 7 个微指令,有足够的 ILP 几乎可以背靠背运行它们。因为大概 r8 会比 r10 早一两个周期准备好,所以你不会损失太多。


    还值得考虑:无论您在做什么来生成 r8..r11,都使用向量整数指令执行此操作,以便您的数据已经在 XMM regs 中。但是,您仍然需要使用 2x PUNPCKLQDQ 和 VINSERTI128 将它们混在一起。

    【讨论】:

    • 再次感谢您的详细回答。 xor 可能是一个糟糕的例子。事实上,除了mulx 之外的所有内容都可以使用 AVX2 完成。然而,这还不足以证明加载/存储的成本是合理的。此外,从 YMM 加载到 r/64 将需要一些 shuffle/permute 或 pextrq 等。尽管在一次循环迭代中处理多个块 (YMM) 可以隐藏一些延迟。我想我只需要自己尝试并找出答案。
    • @YanZhou:YMM-> 带有存储/重新加载的整数比另一个方向的延迟要低得多,因为存储转发从对齐的宽存储工作到完全重叠的窄负载。此外,负载的吞吐量是商店的两倍。如果有足够的向量工作要做,那么提取到标量可能是值得的。
    • @YanZhou:哦,我只记得用 32 位向量乘法构建 64 位向量乘法实际上可能比标量和返回更有效。请参阅my answer on this question 了解有效的 64 x 64 => 64 位向量乘法。如果您需要 64 x 64 => 128 位向量乘法的上半部分结果,则需要额外的指令。 (我忘了还有多少工作;也许太多了。)
    • 但是,手写 asm 很有趣 :) 哦。了解更多有关优化的其他方法:使用性能计数器查看源中的微小变化如何更改不同的计数器。
    • @YanZhou:是的,编译器令人沮丧。在某些情况下,似乎没有办法让他们编写出不烂的代码。例如dividing by a power of 2, rounding up,我没有进行基准测试,但是对于大多数算法,我无法让编译器手动制作出尽可能好的东西。在我尝试手持编译器不起作用后,我就放弃了。
    猜你喜欢
    • 1970-01-01
    • 2017-10-16
    • 2016-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-14
    • 2012-07-22
    • 2021-03-21
    相关资源
    最近更新 更多