C++ SSE 内在函数:将结果存储在变量中

C++ SSE Intrinsics: Storing results in variables

我无法理解 SSE 内在函数将某些 SIMD 计算的结果存储回 "normal variables" 的用法。例如,_mm_store_ps 内在函数在 "Intel Intrinsics Guide" 中的描述如下:

void _mm_store_ps (float* mem_addr, __m128 a)

Store 128-bits (composed of 4 packed single-precision (32-bit) floating-point elements) from a into memory. mem_addr must be aligned on a 16-byte boundary or a general-protection exception may be generated.

第一个参数是一个指向大小为 32 位的浮点数的指针。但是描述指出,内在函数会将 128 位从 a 复制到目标 mem_addr.

这是一个简单的结构,其中 doSomething() 将 1 添加到结构的 x/y。缺少的是如何将结果存储回 x/y 的部分,同时仅使用更高的 32 位宽元素 2 和 3,而 1 和 0 未使用。

struct vec2 {
   union {
         struct {
            float data[2];
         };
         struct {
            float x, y;
         };
      };

   void doSomething() {
      __m128 v1 = _mm_setr_ps(x, y, 0, 0);
      __m128 v2 = _mm_setr_ps(1, 1, 0, 0);
      __m128 result = _mm_add_ps(v1, v2);
      // ?? How to store results in x,y ??
   }
}

这是一个 128 位加载或存储,所以是的,arg 就像 float mem[4]。请记住,在 C 中,将数组传递给函数/内在函数与传递指针相同。

Intel 的内在函数有些特殊,因为它们不遵循正常的严格别名规则,至少对于整数而言。 (例如 _mm_loadu_si128((const __m128i*)some_pointer) 不违反严格别名,即使它是指向 long 的指针。我认为这同样适用于 float/double load/store 内在函数,因此您可以安全地使用它们到 load/store from/to 任何你想要的。通常你会使用 _mm_load_ps 来加载单精度 FP 位模式,并且通常你会把它们保存在 [= 类型的 C 对象中16=],虽然。

How can I access only a specific 32bit element in a and store it in a single float?

使用向量随机播放,然后 _mm_cvtss_f32 将向量转换为标量。


加载/存储 64 位

理想情况下,您可以同时对两个打包在一起的向量进行操作,或者对一个 X 值数组和一个 Y 值数组进行操作,因此对于一对向量,您将拥有 4 对 XY 的 X 和 Y 值坐标。 (数组结构而不是结构数组)。

但是您可以像这样有效地表达您想要做的事情:

struct vec2 {
    float x,y;
};

void foo(const struct vec2 *in, struct vec2 *out) {
    __m128d tmp = _mm_load_sd( (const double*)in );  //64-bit zero-extending load with MOVSD
    __m128  inv = _mm_castpd_ps(tmp);             // keep the compiler happy
    __m128  result = _mm_add_ps(inv,  _mm_setr_ps(1, 1, 0, 0) );

    _mm_storel_pi( out, result );
}

GCC 8.2 compiles it like this (on Godbolt),对于 x86-64 系统 V,奇怪的是使用 movq 而不是 movsd 来加载。 gcc 6.3 使用 movsd.

foo(vec2 const*, vec2*):
        movq    xmm0, QWORD PTR [rdi]           # 64-bit integer load
        addps   xmm0, XMMWORD PTR .LC0[rip]     # packed 128-bit float add
        movlps  QWORD PTR [rsi], xmm0           # 64-bit store
        ret

对于向量低半部分的 64 位存储(2 float 或 1 double),您可以使用 _mm_store_sd。或者更好 _mm_storel_pi (movlps)。不幸的是,它的内在函数需要 __m64* arg 而不是 float*,但这只是英特尔内在函数的设计怪癖。他们通常需要类型转换。

请注意,我使用 _mm_load_sd((const double*)&(in->x)) 而不是 _mm_setr 来执行零扩展到 128 位向量的 64 位加载。您不需要 movlps 加载,因为它会合并到现有向量中。这将对之前的任何值产生错误的依赖,并花费额外的 ALU uop。