14

我试图在 Intel i3 处理器上找到 32 个元素(每个 1 字节数据)的总和减少。我这样做了:

s=0; 
for (i=0; i<32; i++)
{
    s = s + a[i];
}  

但是,它需要更多时间,因为我的应用程序是一个需要更少时间的实时应用程序。请注意,最终总和可能超过 255。

有没有一种方法可以使用低级 SIMD SSE2 指令来实现?不幸的是,我从未使用过 SSE。为此,我尝试搜索 sse2 函数,但它也不可用。(sse)是否保证减少这种小型问题的计算时间?

有什么建议么??

注意:我已经使用 OpenCL 和 CUDA 实现了类似的算法,并且效果很好,但仅在问题规模很大时才有效。对于小型问题,开销成本更高。不确定它如何在 SSE 上运行

4

3 回答 3

9

您可以滥用PSADBW快速计算小的水平总和。

像这样的东西:(未测试)

pxor xmm0, xmm0
psadbw xmm0, [a + 0]
pxor xmm1, xmm1
psadbw xmm1, [a + 16]
paddw xmm0, xmm1
pshufd xmm1, xmm0, 2
paddw xmm0, xmm1 ; low word in xmm0 is the total sum

尝试的内在函数版本:

我从不使用内在函数,所以这段代码可能毫无意义。拆卸看起来还不错。

uint16_t sum_32(const uint8_t a[32])
{
    __m128i zero = _mm_xor_si128(zero, zero);
    __m128i sum0 = _mm_sad_epu8(
                        zero,
                        _mm_load_si128(reinterpret_cast<const __m128i*>(a)));
    __m128i sum1 = _mm_sad_epu8(
                        zero,
                        _mm_load_si128(reinterpret_cast<const __m128i*>(&a[16])));
    __m128i sum2 = _mm_add_epi16(sum0, sum1);
    __m128i totalsum = _mm_add_epi16(sum2, _mm_shuffle_epi32(sum2, 2));
    return totalsum.m128i_u16[0];
}
于 2012-06-07T14:11:28.227 回答
5

This is a bit long-winded but it should still be at least 2x faster than the scalar code:

uint16_t sum_32(const uint8_t a[32])
{
    const __m128i vk0 = _mm_set1_epi8(0);   // constant vector of all 0s for use with _mm_unpacklo_epi8/_mm_unpackhi_epi8
    __m128i v = _mm_load_si128(a);          // load first vector of 8 bit values
    __m128i vl = _mm_unpacklo_epi8(v, vk0); // unpack to two vectors of 16 bit values
    __m128i vh = _mm_unpackhi_epi8(v, vk0);
    __m128i vsum = _mm_add_epi16(vl, vh);
    v = _mm_load_si128(&a[16]);             // load second vector of 8 bit values
    vl = _mm_unpacklo_epi8(v, vk0);         // unpack to two vectors of 16 bit values
    vh = _mm_unpackhi_epi8(v, vk0);
    vsum = _mm_add_epi16(vsum, vl);
    vsum = _mm_add_epi16(vsum, vh);
    // horizontal sum
    vsum = _mm_add_epi16(vsum, _mm_srli_si128(vsum, 8));
    vsum = _mm_add_epi16(vsum, _mm_srli_si128(vsum, 4));
    vsum = _mm_add_epi16(vsum, _mm_srli_si128(vsum, 2));
    return _mm_extract_epi16(vsum, 0);
}

Note that a[] needs to be 16 byte aligned.

You can probably improve on the above code using _mm_hadd_epi16.

于 2012-06-07T15:20:44.010 回答
0

还有另一种方法可以使用 SSE 指令找到数组中所有元素的总和。该代码使用以下 SSE 构造。

  • __m256 寄存器
  • _mm256_store_ps(浮点 *a, __m256 b)
  • _mm256_add_ps(__m256 a, __m256 b)

该代码适用于任何大小的浮点数组。

float sse_array_sum(float *a, int size)
{
    /*
     *   sum += a[i] (for all i in domain)
     */

    float *sse_sum, sum=0;
    if(size >= 8)
    {
        // sse_sum[8]
        posix_memalign((void **)&sse_sum, 32, 8*sizeof(float));

        __m256 temp_sum;
        __m256* ptr_a = (__m256*)a;
        int itrs = size/8-1;

        // sse_sum[0:7] = a[0:7]
        temp_sum = *ptr_a;
        a += 8;
        ptr_a++;

        for(int i=0; i<itrs; i++, ptr_a++, a+=8)
            temp_sum = _mm256_add_ps(temp_sum, *ptr_a);

        _mm256_store_ps(sse_sum, temp_sum);
        for(int i=0; i<8; i++)  sum += sse_sum[i];
    }

    // if size is not divisible by 8
    int rmd_itrs = size%8;
    // Note: a is pointing to remainder elements
    for(int i=0; i<rmd_itrs; i++)   sum += a[i];

    return sum;
}


float seq_array_sum(float *a, int size)
{
    /*
     *  sum += a[i] (for all i)
     */

    float sum = 0;
    for(int i=0; i<size; i++)   sum += a[i];
    return sum;
}

基准:

对于域中的所有 i,大小 = 64000000
a[i] = 3141592.65358

顺序版本时间:194ms
SSE 版本时间 :49ms

机器规格:

每个内核的线程数:
每个插槽2 个内核:2 个
插槽:1
CPU MHz:1700.072
操作系统:Ubuntu

于 2020-03-31T05:59:19.500 回答