是否有适用于双打 (__m128d) 的 Move (_mm_move_ss) 和 Set (_mm_set_ss) 内在函数?

Are there Move (_mm_move_ss) and Set (_mm_set_ss) intrinsics that work for doubles (__m128d)?

多年来,我曾几次看到使用 float 参数转换为 __m128 的内部函数,代码如下:__m128 b = _mm_move_ss(m, _mm_set_ss(a));.

例如:

void MyFunction(float y)
{
    __m128 a = _mm_move_ss(m, _mm_set_ss(y)); //m is __m128
    //do whatever it is with 'a'
}

我想知道是否有类似的方法使用 _mm_move_mm_set 内在函数对双打 (__m128d) 做同样的事情?

_mm_move_sd_mm_set_sd。它们是 SSE2 内在函数(而不是 SSE),因此您需要 #include <emmintrin.h>.

几乎每个 _ss_ps 内在/指令都有一个带有 _sd_pd 后缀的 double 版本。 (标量双精度或压缩双精度)。

例如,搜索 (double in Intel's intrinsic finder to find intrinsic functions that take a double as the first arg. Or just figure out what optimal asm would be, then look up the intrinsics for those instructions in the insn ref manual. Except that it doesn't list all the intrinsics for movsd,因此在内部函数查找器中搜索指令名称通常有效。

回复:头文件:总是只包含 <immintrin.h>。它包括所有英特尔 SSE/AVX 内在函数。


另见 ways to put a float into a vector, and the tag wiki for links about how to shuffle vectors. (i.e. the tables of shuffle instructions in Agner Fog's optimizing assembly guide)

(请参阅下面的神栓 link 到一些有趣的编译器输出)

回复:你的序列

如果你真的想合并两个向量,只使用 _mm_move_ss(或 sd)。

您没有说明 m 是如何定义的。您使用 a 作为浮点数和向量的变量名意味着向量中唯一有用的信息是 float arg。变量名冲突当然意味着它不能编译。

不幸的是,似乎没有任何方法可以将 "cast" a floatdouble 放入前 3 个元素中带有垃圾的向量中,就像 __m128 -> __m256:
__m256 _mm256_castps128_ps256 (__m128 a). I posted a new question about this limitation with intrinsics: How to merge a scalar into a vector without the compiler wasting an instruction zeroing upper elements? Design limitation in Intel's intrinsics?

我尝试使用 _mm_undefined_ps() 来实现这一点,希望这会在编译器中提示它可以将传入的高垃圾留在原地,在

// don't use this, it doesn't make better code
__m128d double_to_vec_highgarbage(double x) {
  __m128d undef = _mm_undefined_pd();
  __m128d x_zeroupper = _mm_set_sd(x);
  return _mm_move_sd(undef, x_zeroupper);
}

但 clang3.8 将其编译为

    # clang3.8 -O3 -march=core2
    movq    xmm0, xmm0              # xmm0 = xmm0[0],zero
    ret

所以没有优势,仍然将上半部分归零而不是将其编译为 ret。 gcc 实际上编写了非常糟糕的代码:

double_to_vec_highgarbage:  # gcc5.3 -march=nehalem
    movsd   QWORD PTR [rsp-16], xmm0      # %sfp, x
    movsd   xmm1, QWORD PTR [rsp-16]      # D.26885, %sfp
    pxor    xmm0, xmm0      # __Y
    movsd   xmm0, xmm1    # tmp93, D.26885
    ret

_mm_set_sd 似乎是将标量转换为矢量的最佳方法。

__m128d double_to_vec(double x) {
  return _mm_set_sd(x);
}

clang 用 -march=generic.

编译成 movq xmm0,xmm0,gcc 编译成 store/reload

其他有趣的编译器输出 from the float and double versions on the Godbolt compiler explorer

float_to_vec:   # gcc 5.3 -O3 -march=core2
    movd    eax, xmm0       # x, x
    movd    xmm0, eax       # D.26867, x
    ret

float_to_vec:   # gcc5.3 -O3 -march=nehalem
    insertps        xmm0, xmm0, 0xe # D.26867, x
    ret

double_to_vec:    # gcc5.3 -O3 -march=nehalem.  It could still have use movq or insertps, instead of this longer-latency store-forwarding round trip
    movsd   QWORD PTR [rsp-16], xmm0      # %sfp, x
    movsd   xmm0, QWORD PTR [rsp-16]      # D.26881, %sfp
    ret

float_to_vec:   # clang3.8 -O3 -march=core2 or generic (no -march)
    xorps   xmm1, xmm1
    movss   xmm1, xmm0              # xmm1 = xmm0[0],xmm1[1,2,3]
    movaps  xmm0, xmm1
    ret

double_to_vec:  # clang3.8 -O3 -march=core2, nehalem, or generic (no -march)
    movq    xmm0, xmm0              # xmm0 = xmm0[0],zero
    ret


float_to_vec:    # clang3.8 -O3 -march=nehalem
    xorps   xmm1, xmm1
    blendps xmm0, xmm1, 14          # xmm0 = xmm0[0],xmm1[1,2,3]
    ret

所以 clang 和 gcc 对 floatdouble 使用不同的策略,即使它们可以使用相同的策略。

在浮点运算之间使用 movq 等整数运算会导致额外的旁路延迟延迟。使用 insertps 将输入寄存器的上部元素归零应该是 float 或 double 的最佳策略,因此所有编译器 应该 在 SSE4.1 可用时使用它。 xorps + blend 也很好,并且可以 运行 在比 insertps 更多的端口上。 store/reload 可能是最差的,除非我们在 ALU 吞吐量上遇到瓶颈,并且延迟无关紧要。