有/没有SSE simd操作的结果是不同的



我正在尝试对数组(无符号字符(的所有元素求和

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

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

ex(我得到了2042115的sse sum,但cv::mat的sum结果是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];
}

您有两个错误:

当最终向量延伸超过nSrcSize时,i < nSrcSize可能为真。由于您已经在使用带符号的int i,因此可以使用i < nSrcSize - 15查找最高的i值,该值可以加载从i+0i+15的完整16个字节。如果使用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。如果是,则加载,使以所需的最后一个字节结束,然后右移。您必须将移位计数计算为8 * (8 - bytes_to_keep)。可以使用标量移位,然后将_mm_cvtsi64_si128转换为SIMD矢量,也可以直接使用_mm_loadl_epi64(movq(并使用SIMD移位。(不幸的是,SSE/AVX没有可变计数字节移位,只有位移位,计数需要在另一个SIMD矢量中。(


FYI,psadbw对零将无符号字符的向量水平求和为两个q字,这比SIMD循环效率高得多。水平求和SSE无符号字节矢量的最快方法。另请参阅如何使用SIMD计算字符出现次数。在外循环中使用SIMD将字节向量累积为具有更宽元素的SIMD向量。

您已经在清理中使用了psadbw,但您正在将所有8个16位元素相加,尽管其中6个元素为零。

最新更新