结果 with/without SSE simd 操作不同

result with/without SSE simd operation is different

我正在尝试对数组的所有元素求和(unsigned char)

但是cv::Mat sum 的结果与SSE 结果不同(下面的代码)

有sse,数组结果之和比没有时大,但为什么呢??

ex) 我得到 2042115 的 sse 总和,但是 cv::mat 的总和结果是 2041104.

__m128i srcVal;
        __m128i src16bitlo;
        __m128i src16bithi;
        __m128i src32bitlolo;
        __m128i src32bitlohi;
        __m128i src32bithilo;
        __m128i src32bithihi;
        __m128i vsum = _mm_setzero_si128();
    for (int i = 0; i < nSrcSize; i += 16)
    {

        srcVal = _mm_loadu_si128((__m128i*) (pSrc + i));

        src16bitlo = _mm_unpacklo_epi8(srcVal, _mm_setzero_si128());
        src16bithi = _mm_unpackhi_epi8(srcVal, _mm_setzero_si128());

        src32bitlolo = _mm_unpacklo_epi16(src16bitlo, _mm_setzero_si128());
        src32bitlohi = _mm_unpackhi_epi16(src16bitlo, _mm_setzero_si128());
        src32bithilo = _mm_unpacklo_epi16(src16bithi, _mm_setzero_si128());
        src32bithihi = _mm_unpackhi_epi16(src16bithi, _mm_setzero_si128());

        vsum = _mm_add_epi32(src32bitlolo, vsum);
        vsum = _mm_add_epi32(src32bitlohi, vsum);
        vsum = _mm_add_epi32(src32bithilo, vsum);
        vsum = _mm_add_epi32(src32bithihi, vsum);


        // cout << "sumSrc : " << sumSrc << endl;
    }
    int sumSrc = vsum.m128i_i32[0] + vsum.m128i_i32[1] + vsum.m128i_i32[2] + vsum.m128i_i32[3];
    //int check = sumSrc;

    int remainSize = nSrcSize % 16;
    if (remainSize > 0)
    {
        unsigned char* arrTemp = new unsigned char[16]();  // 0으로 초기화
        memcpy(arrTemp, pSrc + nSrcSize - remainSize -1, remainSize);
        __m128i srcVal = _mm_loadu_si128((__m128i*)arrTemp);
        vsum = _mm_sad_epu8(srcVal, _mm_setzero_si128());
        sumSrc += vsum.m128i_i16[0] + vsum.m128i_i16[1] + vsum.m128i_i16[2] + vsum.m128i_i16[3] + vsum.m128i_i16[4] + vsum.m128i_i16[5] + vsum.m128i_i16[6] + vsum.m128i_i16[7];
    }

您有 2 个错误:

i < nSrcSize 当最终向量延伸超过 nSrcSize 时可以为真。由于您已经在使用带符号的 int i,因此您可以使用 i < nSrcSize - 15 来查找可以加载从 i+0i+15 的完整 16 个字节的最高 i 值.或者如果您使用 size_t.

,请使用 nSrcSize & -16U

new unsigned char[16]() 不会将内存清零,所以你在总结一些额外的垃圾。您 不需要 new,并且您忘记删除它,所以您正在泄漏内存!您可以 使用本地数组,而不是动态分配任何内容。

alignas(16) unsigned char arrTemp[16] = {0};  // implicitly initializes later elements to 0

但是可变大小的 memcpy 效率不高,重新加载 memcpy 结果会导致存储转发停顿。 OTOH,你可以 _mm_add_epi32(vsum, cleanup_sad) 并且只做一个水平向量和。

更高效的方法可能是根据大小对自己进行分支(而不是将工作传递给 memcpy)并使用 SIMD 加载执行 8 字节和 4 字节块。或者,一旦剩余的字节数少于 8 个,执行不会跨越缓存行边界的 8 个字节的加载以获取所有内容。

检查从您想要的第一个字节开始的加载是否会跨越 64 字节边界。如果否,则进行 64 位左移以移入零,您可以安全地进行 hsum。如果是,则执行 endslast 所需字节的加载,然后右移。您必须将班次计数计算为 8 * (8 - bytes_to_keep)。您可以使用标量移位,然后 _mm_cvtsi64_si128 进入 SIMD 向量,或直接 _mm_loadl_epi64 (movq) 并使用 SIMD 移位。 (不幸的是 SSE/AVX 没有可变计数的字节移位,只有位移,并且计数需要在另一个 SIMD 向量中。)


仅供参考,psadbw 对零将把一个无符号字符的向量水平求和为两个 qwords,这比你的 SIMD 循环更有效。 . See also how How to count character occurrences using SIMD 在外循环中使用它来将字节向量累积到具有更宽元素的 SIMD 向量中。

您已经在清理中使用了 psadbw,但您将所有八个 16 位元素相加,即使其中六个为零。