X86 在英特尔上使用SSE2减少无符号字节的总和而不产生溢出

X86 在英特尔上使用SSE2减少无符号字节的总和而不产生溢出,x86,sse,simd,sse2,sse3,X86,Sse,Simd,Sse2,Sse3,我试图在Intel i3处理器上找到32个元素(每个1字节数据)的总和缩减。我这样做: s=0; for (i=0; i<32; i++) { s = s + a[i]; } s=0; 对于(i=0;i您可以滥用PSADBW快速计算小水平和 类似这样的内容:(未经测试) 尝试的内部版本: 我从不使用内部函数,所以这段代码可能毫无意义。不过反汇编看起来还可以 uint16\u t sum\u 32(const uint8\u t a[32]) { __m128i零=_mm_

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

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

对于(i=0;i您可以滥用
PSADBW
快速计算小水平和

类似这样的内容:(未经测试)


尝试的内部版本:

我从不使用内部函数,所以这段代码可能毫无意义。不过反汇编看起来还可以

uint16\u t sum\u 32(const uint8\u t a[32])
{
__m128i零=_mm_xor_si128(零,零);
__m128i sum0=_mm_sad_epu8(
零,,
_mm_荷载_si128(重新解释浇筑(a));
__m128i sum1=_mm_sad_epu8(
零,,
_mm_荷载_si128(重新解释铸件(&a[16]);
__m128i sum2=_mm_add_epi16(sum0,sum1);
__m128i totalsum=_mm_add_epi16(sum2,_mm_shuffle_epi32(sum2,2));
返回totalsum.m128i_u16[0];
}

这有点冗长,但至少比标量代码快2倍:

uint16\u t sum\u 32(const uint8\u t a[32])
{
const _m128i vk0=_mm_set1_epi8(0);//与_mm_unpaclo_epi8/_mm_unpachi_epi8一起使用的所有0的常量向量
__m128i v=_mm_load_si128(a);//加载8位值的第一个向量
__m128i vl=_mm_unpaclo_epi8(v,vk0);//解压缩到两个16位值的向量
__m128i vh=_mm_解压_epi8(v,vk0);
__m128i vsum=_mm_add_epi16(vl,vh);
v=_mm_load_si128(&a[16]);//加载8位值的第二个向量
vl=_mm_unpaclo_epi8(v,vk0);//解压为两个16位值的向量
vh=_mm_epi8(v,vk0);
vsum=_mm_add_epi16(vsum,vl);
vsum=_mm_add_epi16(vsum,vh);
//水平和
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));
返回_mm_extract_epi16(vsum,0);
}
请注意,
a[]
需要16字节对齐


您可能可以使用
\u mm\u hadd\u epi16

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

  • __m256寄存器
  • _mm256存储(浮点*a,m256 b)
  • _mm256添加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;
}
浮点sse_数组_和(浮点*a,整数大小)
{
/*
*总和+=a[i](适用于域中的所有i)
*/
浮点数*sse_和,和=0;
如果(大小>=8)
{
//上证综指[8]
posix_memalign((无效**)和sse_sum,32,8*sizeof(浮动));
__m256温度总和;
__m256*ptr_a=(__m256*)a;
int itrs=尺寸/8-1;
//sse_和[0:7]=a[0:7]
温度总和=*ptr\u a;
a+=8;
ptr_a++;

对于(int i=0;iYes),最终总和可能大于255。如何确保[]是16字节对齐的?在SSE中是否有类似于CUDA中的uuu align_uuu(16)的东西?这取决于您使用的编译器和操作系统-例如,对于具有非动态分配的gcc,使用
uu attribute_u((对齐(16)))
-对于Linux上的动态分配,请使用
memalign()
posix_memalign()
。必须对其进行否决表决;它是有效的,但是
psadbw
是正确的答案。对于有符号的
int8\t
,您可以使用
xor
将范围移位到无符号,将0x80添加到每个字节,然后从结果中减去
16*0x80
。(例如,请参见intrinsic。同样的想法适用于).但是这里的OP似乎已经没有签名了,所以psadbw是一个巨大的胜利。@PeterCordes:我确实说过它“有点冗长”。-)但是是的,哈罗德的答案是更好的解决方案(当然我已经投了更高的票)我可能应该删除这个,因为它实际上没有任何有用的用途。你能写英特尔®C++编译器的内在等价物吗?@ GPGEDY我试过了,但是我从来没有使用过内含物,所以我可能搞错了一些东西。那个<代码> RealTytCase看起来也不太好,但是我不知道如何得到RI。d.对
int8\u t
(而不是
uint8\u t
)使用相同的技巧:将范围移到无符号(与0x80异或),然后从总数中减去
16*0x80
。参见。相同的想法适用于)。首先,这个问题是关于求和
uint8\u t[]
。更重要的是,动态分配临时存储绝对不是一场胜利。只需像普通人一样使用一个
\uuum256 sum
临时存储即可。(或者更好,使用多个累加器来隐藏FP延迟。如果您想使用它们的本地“数组”,编译器通常会将其优化,并将它们全部保存在寄存器中).而且绝对不要
\u mm256\u store\u ps()
在内部循环中,尽管这可能也会优化。此外,如果
a
未按32对齐,则您的代码不安全;您取消引用
\uuuuum256*
而不是使用
\umm256\u loadu\u ps
。AVX
\uum256
版本就是您想要的。请注意它的内部循环是多么简单。对于末尾的水平和,请参阅。存储到一个
alignas(32)float tmp[8]
将是一个选项,但使用洗牌可以做得更好。此外,使用多个累加器展开主循环以隐藏FP延迟,如仅供参考:
\u m256
东西需要AVX。一般来说,将所有x86 SIMD东西称为“SSE”并不是完全错误的,但通常人们至少会将SSE*与AVX1/AVX2/FMA与AVX512分开。无论如何,感谢您的帮助,但不幸的是,这个答案并没有展示出好的做事方式,即使您将它张贴在至少能回答正确问题的地方,我也会对它投反对票。当您第一次学习一些东西时,这是正常的如果您的第一次尝试过于复杂且不安全(仅适用于对齐的
a
),请不要为此感到难过,
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;
}