【问题标题】:SSE operation on 4 arrays of integer size对 4 个整数大小的数组进行 SSE 操作
【发布时间】:2012-11-18 01:11:32
【问题描述】:

抱歉之前的非描述性问题。请允许我重新表述一下这个问题:

设置:

我需要使用 SSE 同时对来自 4 个数组的 4 个 32 位值进行 ADD 和一些位操作。这 4 个数组中的所有元素都是整数大小(32 位)。结果进入第 5 个数组。

所以我的问题是:

  1. 我需要包含哪些头文件和编译器标志才能使用 C 运行 SSE?
  2. Paul 提供的示例代码是否仍然有效?

另一个问题,如果我需要从整数 A 中读取最后一位,从整数 B 中读取第一位,并将整数 C 中的最后一位和第一位替换为我刚刚读取的值,我可以在这里使用 SSE 吗?或者有什么快速的方法吗?而不是正常情况下的 3 次访问?

Paul 提供的代码

#include <stdint.h>
#include <emmintrin.h>

const size_t N = 4096;  // size of input/output arrays

int32_t array0[N];      // 4 x input arrays
int32_t array1[N];
int32_t array2[N];
int32_t array3[N];
int32_t array_sum[N];   // output array

for (size_t i = 0; i < N; i += 4)
{
    __m128i v0 = _mm_load_si128(&array0[i]); // load 4 x vectors of 4 x int
    __m128i v1 = _mm_load_si128(&array1[i]);
    __m128i v2 = _mm_load_si128(&array2[i]);
    __m128i v3 = _mm_load_si128(&array3[i]);
    __m128i vsum = _mm_add_epi32(v0, v1);    // sum vectors
    __m128i vsum = _mm_add_epi32(vsum, v2);
    __m128i vsum = _mm_add_epi32(vsum, v3);
    _mm_store_si128(&array_out[i], vsum);    // store sum
}

【问题讨论】:

  • 请注意,对于_mm_load_si128(),不能保证数组正确对齐,这要求其操作数是 16 字节对齐的。大多数编译器只会将int32_t 对齐到 4 字节边界。
  • _mm_loadu_si128 允许您从未对齐的内存中加载。数组上不需要 __declspec(align(16))__attribute__((aligned(16))

标签: c assembly sse simd intrinsics


【解决方案1】:

要使用 gcc 编译 SSE 代码,您只需要 -msse2 标志,例如

$ gcc -Wall -O3 -msse2 foo.c -o foo

至于其他要求,如果您可以发布您想要矢量化的工作(标量)示例代码,这可能是最好的,因为从上面的描述中不是很清楚。

【讨论】:

    【解决方案2】:

    您拥有的代码确实可以工作,只需稍作改动。

    #include <stdint.h>
    #include <emmintrin.h>
    
    const size_t N = 4096;  // size of input/output arrays
    
    __declspec(align(16)) int32_t array0[N];      // 4 x input arrays
    __declspec(align(16)) int32_t array1[N];
    __declspec(align(16)) int32_t array2[N];
    __declspec(align(16)) int32_t array3[N];
    __declspec(align(16)) int32_t array_sum[N];   // output array
    
    for (size_t i = 0; i < N; i += 4)
    {
        __m128i v0 = _mm_load_si128(&array0[i]); // load 4 x vectors of 4 x int
        __m128i v1 = _mm_load_si128(&array1[i]);
        __m128i v2 = _mm_load_si128(&array2[i]);
        __m128i v3 = _mm_load_si128(&array3[i]);
        __m128i vsum = _mm_add_epi32(v0, v1);    // sum vectors
        __m128i vsum = _mm_add_epi32(vsum, v2);
        __m128i vsum = _mm_add_epi32(vsum, v3);
        _mm_store_si128(&array_sum[i], vsum);    // store sum
    }
    

    在使用内部函数时,我喜欢包含 &lt;immintrin.h&gt; 并使用 gcc -march=native 进行编译。这样就可以访问当前硬件上实际可用的所有指令集扩展。

    关于另一个问题,是的,你当然可以这样做,但只有在整数 A、B 和 C 的数组上这样做才有效。

    例子:

    __declspec(align(16)) int32_t A[N]; // input arrays
    __declspec(align(16)) int32_t B[N];
    __declspec(align(16)) int32_t C[N];
    __declspec(align(16)) int32_t R[N]; // output array
    
    __m128i* As = (__m128i*)A; // cast them to SSE type, avoids separate load/store calls later
    __m128i* Bs = (__m128i*)B;
    __m128i* Cs = (__m128i*)C;
    __m128i* Rs = (__m128i*)R;
    
    __m128i A_mask = _mm_set1_epi32(1<<31); // select these bits from A, B, and C
    __m128i B_mask = _mm_set1_epi32(1);
    __m128i C_mask = _mm_set1_epi32(0xffffffff ^ ( 1<<31 | 1 ));
    
    for (size_t i = 0; i < N / 4; i ++)
    {
        __m128i a = _mm_and_si128(A_mask, As[i]);
        __m128i b = _mm_and_si128(B_mask, Bs[i]);
        __m128i c = _mm_and_si128(C_mask, Cs[i]);
        Rs[i] = _mm_or_si128( _mm_or_si128(a, b), c );
    }
    

    像我上面所做的那样将 int32_t 数组别名为 __m128i 效率并不高,如果编译器不错,它应该编译成完全相同的代码,但它会产生更少冗长的代码。推荐:)

    【讨论】:

    • 您的第一个代码块在负载上缺少(const __m128i*)&amp;array0[i] 强制转换,并且在商店中缺少类似的强制转换。此外,C11 为 MSVC _declspec / GNU C __attribute__ 提供了一个可移植的 _Alignas(16)。 (#include &lt;stdalign.h&gt; 允许 alignas(16)。)否则是的,很好的例子。
    猜你喜欢
    • 2012-11-11
    • 2018-10-01
    • 2012-05-17
    • 2016-04-28
    • 2017-08-01
    • 2011-08-04
    • 2015-02-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多