在这种情况下,_mm_movehdup_ps 和 _mm_shuffle_ps 有什么区别?

What is the difference between _mm_movehdup_ps and _mm_shuffle_ps in this case?

如果我的理解是正确的,

_mm_movehdup_ps(a)

给出与

相同的结果

_mm_shuffle_ps(a, a, _MM_SHUFFLE(1, 1, 3, 3))?

两者有性能差异吗?

_MM_SHUFFLE 先取高元素,所以 _MM_SHUFFLE(3,3, 1,1) 会进行 movshdup 随机播放。

主要区别在于汇编级别; movshdup 是一个 copy-and-shuffle,如果稍后仍然需要输入 a,则避免 movaps 复制输入(例如,作为水平总和的一部分:参见 Fastest way to do horizontal float vector sum on x86 的示例,说明它如何在没有 movaps 的情况下进行编译,而使用 shufps.

的 SSE1 版本

movshdup/movsldup也可以是带有内存源操作数的load+shuffle。 (shufps 显然不能,因为它需要两次相同的输入。)在现代英特尔 CPU (Sandybridge-family) 上,movshdup xmm0, [rdi] 解码为纯负载 uop,而不是micro-fused 与 ALU uop。因此它不会与其他洗牌竞争 ALU 洗牌吞吐量(端口 5)。加载端口包含执行广播加载(包括 movddup 64 位广播)和 movs[lh]dup 元素对复制的逻辑。更复杂的 load+shuffle 像 vpermilps xmm0, [rdi], 0x12pshufd xmm, [rdi], 0x12 仍然解码为多个 uops,可能 micro-fused 到 load+ALU 取决于 uarch。


两条指令长度相同:movshdup避免立即字节,但shufps是SSE1指令所以它只有2字节的操作码,比SSE2和SSE3指令短1字节. 但是启用 AVX 后,vmovshdup 确实节省了一个字节,因为 opcode-size 优势消失了。


在只有 64 位洗牌单元的旧 CPU 上(如 Pentium-M 和 first-gen Core 2 (Merom)),性能优势更大. movshdup 仅在向量的 64 位一半内随机播放。在 Core 2 Merom 上,movshdup xmm, xmm 解码为 1 微指令,但 shufps xmm, xmm, i 解码为 3 微指令。 (有关指令表和微架构指南,请参阅 https://agner.org/optimize/)。有关 Merom 和 K8 等 SlowShuffle CPU 的更多信息,另请参阅我的水平总和答案(之前已链接)。


在具有内部函数的 C++ 中

如果启用 SSE3,如果您的编译器没有将 _mm_shuffle_ps(a, a, _MM_SHUFFLE(3, 3, 1, 1)) 优化到与 _mm_movehdup_ps(a).

相同的程序集中,则这是一个错过的优化

一些编译器(如 MSVC)通常不会优化内在函数,因此程序员需要了解通过对 copy-and-shuffle 指令使用内在函数来避免 movaps 指令的 asm 含义(像 pshufdmovshdup) 而不是必须破坏其目标寄存器的洗牌 (像 shufps, 和像 psrldq byte-shifts.)

此外,MSVC 不允许您启用 SSE3 的编译器使用,如果您对它们使用内在函数,则只能获得超出基线 SSE2 的指令(或没有 SIMD)。或者,如果您启用 AVX,这将允许编译器也使用 SSE4.2 和更早版本,但它仍然选择不优化。因此,再次由人类程序员来寻找优化。国际刑事法院是相似的。如果您确切知道自己在做什么并且正在检查编译器的 asm 输出,有时这可能是一件好事,因为有时 gcc 或 clang 的优化会使您的代码悲观。

用 clang 编译并查看它是否使用与源代码中的内在指令相同的指令可能是个好主意;在支持 Intel intrinsics 的 4 个主要编译器中,它拥有迄今为止最好的 shuffle 优化器,基本上优化你的 intrinsics 代码的方式与编译器通常优化纯 C 的方式相同,即只需遵循 as-if 规则即可产生相同的结果.

最简单的例子:

#include <immintrin.h>

__m128 shuf1(__m128 a) {
    return _mm_shuffle_ps(a,a, _MM_SHUFFLE(3,3, 1,1));
}

compiled with gcc/clang/MSVC/ICC on Godbolt

GCC 和带有 -O3 -march=core2 的 clang 都发现了优化:

shuf1:
        movshdup        xmm0, xmm0
        ret

ICC -O3 -march=haswell 和 MSVC -O2 -arch:AVX -Gv(启用 vectorcall 调用约定,而不是通过引用传递 SIMD 向量。)

shuf1:
        vshufps   xmm0, xmm0, xmm0, 245                         #4.12
        ret                                                     #4.12