【问题标题】:Are there Move (_mm_move_ss) and Set (_mm_set_ss) intrinsics that work for doubles (__m128d)?是否有适用于双打 (__m128d) 的 Move (_mm_move_ss) 和 Set (_mm_set_ss) 内在函数?
【发布时间】:2016-12-22 04:22:18
【问题描述】:

多年来,我曾几次看到带有 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) 执行相同的操作?

【问题讨论】:

  • m 是什么?你知道这是合并,对吧?这不仅仅是从float 中制作一个向量。
  • @PeterCordes Yap,你是对的。我凭记忆写了代码示例,但确实是y是函数参数。而m 是一个__m128 向量,在函数外部声明和赋值。

标签: c++ x86 double sse intrinsics


【解决方案1】:

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

例如,搜索(double in Intel's intrinsic finder 以查找将double 作为第一个参数的内部函数。或者只是弄清楚最佳 asm 是什么,然后在 insn ref 手册中查找这些指令的内在函数。除了 doesn't list all the intrinsics for movsd,因此在内部函数查找器中搜索指令名称通常是可行的。

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


另请参阅ways to put a float into a vector 标签wiki,了解有关如何洗牌向量的链接。 (即Agner Fog's optimizing assembly guide中的shuffle指令表)

(请参阅下面的链接到一些有趣的编译器输出)

re: 你的序列

如果您确实想要合并两个向量,请仅使用 _mm_move_ss(或 sd)。

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

不幸的是,似乎没有任何方法可以将 floatdouble “投射”到上面 3 个元素中有垃圾的向量中,就像 __m128 -> __m256 一样:
__m256 _mm256_castps128_ps256 (__m128 a).我发布了一个关于内在函数限制的新问题: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 将其编译为 movq xmm0,xmm0,gcc 使用 -march=generic 进行存储/重新加载。


其他有趣的编译器输出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 将输入寄存器的高位元素归零应该是浮点或双精度的最佳策略,因此所有编译器应该在 SSE4.1 可用时使用它。 xorps + blend 也很好,并且可以在比 insertps 更多的端口上运行。存储/重新加载可能是最糟糕的,除非我们在 ALU 吞吐量上遇到瓶颈,并且延迟无关紧要。

【讨论】:

  • 出色的答案,谢谢!它不仅回答了我的简单问题,而且老实说已经回答了我心中的其他一些疑问。
【解决方案2】:

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

【讨论】:

  • 您总是可以只使用#include <immintrin.h>。无需为不同扩展名的头文件弄乱所有旧名称。
猜你喜欢
  • 2020-01-17
  • 2017-07-16
  • 1970-01-01
  • 2022-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-19
  • 1970-01-01
相关资源
最近更新 更多