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

Posted

技术标签:

【中文标题】SIMD/SSE:短点积和短最大值【英文标题】:SIMD/SSE : short dot product and short max value 【发布时间】:2015-02-09 10:43:08 【问题描述】:

我正在尝试优化两个 c 样式数组的点积,它们的大小为 contant、small 和 short 类型。

我已经阅读了几篇关于 SIMD 内在函数的文档以及许多关于使用此内在函数进行点积优化的博客文章/文章。

但是,我不明白使用此内在函数的短数组上的点积如何给出正确的结果。在制作点积时,计算值可以(并且在我的情况下总是)大于 SHORT_MAX,所以总和也是如此。因此,我将它们存储在 double 类型的变量中。

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

感谢您的建议

【问题讨论】:

为什么要优化两个small数组的点积?你还用这些数组做什么?你在关键循环中做什么?一些代码示例会有所帮助。 我正在尝试优化点积,因为它占用了整个过程 80% 的时间:一次运行几乎 1500 万个不同的点积,因此任何改进都会产生巨大的影响。 【参考方案1】:

根据数据值的范围,您可能会使用诸如 _mm_madd_epi16 之类的内部函数,它对 16 位数据执行乘法/加法并生成 32 位项。然后,您需要定期将 32 位项累积到 64 位。您需要多久执行一次取决于输入数据的范围,例如如果它是 12 位灰度图像数据,那么您可以在每次迭代中以 8 个元素(即 512 个输入点)进行 64 次迭代,然后才有可能溢出。然而,在最坏的情况下,如果您的输入数据使用完整的 16 位范围,那么您需要在每次迭代(即每 8 点)上进行额外的 64 位累加。

【讨论】:

我没见过这个功能,现在我看到了曙光。我已经实现了点积并且它可以工作,tihout 溢出。我的速度提升了将近 25%,这对我来说是巨大的! _mm_madd_epi16 在我的 SSE 内在函数前 10 名列表中名列前茅。 ;-) 除了点积之外,它对于减少统计类型也很有用,例如sum(x)、sum(x^2) 等。顺便说一句,如果您想将代码作为新问题发布,那么应该可以提高 25% 的收益。 @florian,我忘记了_mm_madd_epi16。感谢保罗再次提醒我。看到您的代码以及 Paul 建议的更多您正在做的事情会很有趣。您的操作可能受内存带宽限制,因此除了更改您的操作之外您无能为力,但如果不了解您在做什么,很难确定。 是的,我也想象过这些操作受内存带宽限制。此外,一般过程是并行化的,所以我预计不会有 25% 的收益,但会低于这个数字。但是,它已经有了很大的改进(我预计 2 / 5 %)。我稍后会发布我的代码,但为什么在另一个主题中? 而且,作为另一条评论,avx对应的代码比较慢。【参考方案2】:

仅作记录,以下是我如何为 2 个大小为 36 的 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

【讨论】:

以上是关于SIMD/SSE:短点积和短最大值的主要内容,如果未能解决你的问题,请参考以下文章

Java中的字节和短点(我已经阅读了其他问题)

Numpy:点积和 dot() 矩阵相乘

在 Cython 中调用点积和线性代数运算?

向量的点积和叉积

关于向量点积和叉积的回顾和总结

C 内在函数、SSE2 点积和 gcc -O3 生成的程序集