结果 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+0
到 i+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。如果是,则执行 ends 与 last 所需字节的加载,然后右移。您必须将班次计数计算为 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 位元素相加,即使其中六个为零。
我正在尝试对数组的所有元素求和(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+0
到 i+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。如果是,则执行 ends 与 last 所需字节的加载,然后右移。您必须将班次计数计算为 8 * (8 - bytes_to_keep)
。您可以使用标量移位,然后 _mm_cvtsi64_si128
进入 SIMD 向量,或直接 _mm_loadl_epi64
(movq
) 并使用 SIMD 移位。 (不幸的是 SSE/AVX 没有可变计数的字节移位,只有位移,并且计数需要在另一个 SIMD 向量中。)
仅供参考,psadbw
对零将把一个无符号字符的向量水平求和为两个 qwords,这比你的 SIMD 循环更有效。
您已经在清理中使用了 psadbw
,但您将所有八个 16 位元素相加,即使其中六个为零。