C++ SIMD/SSE:短点积和短最大值

C++ SIMD/SSE:短点积和短最大值,c++,x86,sse,simd,intrinsics,C++,X86,Sse,Simd,Intrinsics,我正在尝试优化两个c样式数组的点积,这两个数组都是contant、小尺寸和short类型 我已经阅读了一些关于SIMD本质的文档,以及许多关于使用该本质优化dot产品的博客文章 然而,我不明白如何在短阵列上使用此内部函数的点积可以给出正确的结果。在做点积时,计算出的值可以(并且在我的例子中总是)大于SHORT_MAX,因此是sum。因此,我将它们存储在双重类型的变量中 根据我对使用simd内在函数的点积的理解,我们使用_m128i变量类型和操作返回_m128i。所以,我不明白的是为什么它不会“溢

我正在尝试优化两个c样式数组的点积,这两个数组都是contant、小尺寸和short类型

我已经阅读了一些关于SIMD本质的文档,以及许多关于使用该本质优化dot产品的博客文章

然而,我不明白如何在短阵列上使用此内部函数的点积可以给出正确的结果。在做点积时,计算出的值可以(并且在我的例子中总是)大于SHORT_MAX,因此是sum。因此,我将它们存储在双重类型的变量中

根据我对使用simd内在函数的点积的理解,我们使用_m128i变量类型和操作返回_m128i。所以,我不明白的是为什么它不会“溢出”,以及如何将结果转换为可以处理它的值类型


感谢您的建议

根据您的数据值范围,您可能会使用一个内在函数,例如,它对16位数据执行乘法/加法运算,并生成32位术语。然后需要定期将32位术语累积到64位。您需要执行此操作的频率取决于输入数据的范围,例如,如果是12位灰度图像数据,则您可以在可能出现溢出之前,以每次迭代8个元素(即512个输入点)执行64次迭代。然而,在最坏的情况下,如果您的输入数据使用完整的16位范围,则您需要在每次迭代时(即每8点)进行额外的64位累加。

仅针对记录,以下是我如何为大小为36的2个int16阵列制作点积:

double dotprod(const int16_t* source, const int16_t* target, const int size){
#ifdef USE_SSE
    int res[4];
    __m128i* src = (__m128i *) source;
    __m128i* t = (__m128i *) target;
    __m128i s = _mm_madd_epi16(_mm_loadu_si128(src), mm_loadu_si128(t));
    ++src;
    ++t;
    s = _mm_add_epi32(s, _mm_madd_epi16(_mm_loadu_si128(src), _mm_loadu_si128(t)));
    ++src;
    ++t;
    s = _mm_add_epi32(s, _mm_madd_epi16(_mm_loadu_si128(src), _mm_loadu_si128(t)));
    ++src;
    ++t;
    s = _mm_add_epi32(s, _mm_madd_epi16(_mm_loadu_si128(src), _mm_loadu_si128(t)));

    /* return the sum of the four 32-bit sub sums */
    _mm_storeu_si128((__m128i*)&res, s);
    return res[0] + res[1] + res[2] + res[3] + source[32] * target[32] + source[33] * target[33] + source[34] * target[34] + source[35] * target[35];
#elif USE_AVX
    int res[8];
    __m256i* src = (__m256i *) source;
    __m256i* t = (__m256i *) target;
    __m256i s = _mm256_madd_epi16(_mm256_loadu_si256(src), _mm256_loadu_si256(t));
    ++src;
    ++t;
    s = _mm256_add_epi32(s, _mm256_madd_epi16(_mm256_loadu_si256(src), _mm256_loadu_si256(t)));

    /* return the sum of the 8 32-bit sub sums */
    _mm256_storeu_si256((__m256i*)&res, s);
    return res[0] + res[1] + res[2] + res[3] + res[4] + res[5] + res[6] + res[7] + source[32] * target[32] + source[33] * target[33] + source[34] * target[34] + source[35] * target[35];
#else
    return source[0] * target[0] + source[1] * target[1] + source[2] * target[2] + source[3] * target[3] + source[4] * target[4]+ source[5] * target[5] + source[6] * target[6] + source[7] * target[7] + source[8] * target[8] + source[9] * target[9] + source[10] * target[10] + source[11] * target[11] + source[12] * target[12] + source[13] * target[13] + source[14] * target[14] + source[15] * target[15] + source[16] * target[16] + source[17] * target[17] + source[18] * target[18] + source[19] * target[19] + source[20] * target[20] + source[21] * target[21] + source[22] * target[22] + source[23] * target[23] + source[24] * target[24] + source[25] * target[25] + source[26] * target[26] + source[27] * target[27] + source[28] * target[28] + source[29] * target[29] + source[30] * target[30] + source[31] * target[31] + source[32] * target[32] + source[33] * target[33] + source[34] * target[34] + source[35] * target[35];
#endif
}

为什么要优化两个小阵列的点积?您还打算用这些阵列做什么?你在关键循环中做什么?一些代码示例可能会有所帮助。我正在尝试优化dot product,因为它占用了整个过程80%的时间:一次运行几乎1500万个不同的dot product,因此任何改进都可以产生巨大的效果。我还没有看到这个函数,现在我看到了曙光。我已经实现了点积,它工作正常,但没有溢出。我的速度提高了近25%,这对我来说是巨大的<代码>\u mm\u madd\u epi16在我的上证综指排行榜的前十名中名列前茅除了点积之外,它还可用于统计类型的缩减,例如sum(x)、sum(x^2)等。顺便说一句,如果您想将代码作为新问题发布,则应该可以提高25%的增益。@florian,我忘记了
\u mm\u madd\u epi16
。谢谢保罗再次提醒我这件事。按照Paul的建议,查看您的代码和更多您正在做的事情会很有趣。您的操作可能是内存带宽受限的,因此除了更改您的操作之外,您可以做的事情不多,但如果不了解更多您正在做的事情,则很难确定。是的,我还想象这些操作是内存带宽受限的。此外,一般的过程是并行的,所以我不期望有25%的收益,但要小于25%。但是,这已经是一个巨大的进步(我预计是2/5%)。稍后我会发布我的代码,但为什么会在另一个主题中?另外,作为另一个注释,avx对应的代码要慢一些。