如何用SSE/SSE2无溢出计算__m128寄存器中所有字节的和?
解决SSE/SSE2计算__m128寄存器字节和的溢出问题
首先,你的原有代码存在指令使用错误,并非真正的溢出问题,导致结果错误:
- 未显式将
__m128转换为__m128i,直接传入_mm_sad_epu8可能导致字节内容被错误解析; _mm_cvtsi128_si32(sum)会读取寄存器低32位(包含两个16位的和),而非低8字节的单独和;_mm_extract_epi16(sum,4)取错了高8字节和的位置,正确索引应为1。
修正后的_mm_sad_epu8实现(无溢出)
_mm_sad_epu8计算的8字节块和最大为8*255=2040,完全在16位无符号整数范围内,两个块和相加也仅为4080,不会溢出32位整数。修正后的代码如下:
#include <emmintrin.h> unsigned int sum_bytes_sad(__m128 bytes) { // 将浮点数寄存器转换为整数寄存器,保留原始字节内容 __m128i bytes_i = _mm_castps_si128(bytes); // 计算低8字节和高8字节的无符号字节和(与零的SAD等价于直接求和) __m128i sum_blocks = _mm_sad_epu8(bytes_i, _mm_setzero_si128()); // 提取低8字节的和(索引0的16位元素)与高8字节的和(索引1的16位元素) unsigned int lo_sum = _mm_extract_epi16(sum_blocks, 0); unsigned int hi_sum = _mm_extract_epi16(sum_blocks, 1); return lo_sum + hi_sum; }
纯寄存器无溢出通用实现(支持有/无符号字节)
如果需要更通用的无溢出实现(比如处理有符号字节),可以将每个字节扩展为16位后逐步累加,全程使用32位整数存储中间结果,彻底避免溢出:
无符号字节版本
#include <emmintrin.h> unsigned int sum_bytes_sse2_unsigned(__m128 bytes) { __m128i bytes_i = _mm_castps_si128(bytes); __m128i zero = _mm_setzero_si128(); // 将低8字节和高8字节分别扩展为16位无符号整数 __m128i lo_16 = _mm_unpacklo_epi8(bytes_i, zero); __m128i hi_16 = _mm_unpackhi_epi8(bytes_i, zero); // 定义水平累加16位元素为32位总和的辅助逻辑 auto horizontal_sum_16_to_32 = [](__m128i vec) { // 将16位元素扩展为32位 __m128i lo_32 = _mm_unpacklo_epi16(vec, zero); __m128i hi_32 = _mm_unpackhi_epi16(vec, zero); // 合并高4个与低4个16位元素的和 __m128i sum_32 = _mm_add_epi32(lo_32, hi_32); // 累加前两个32位元素与后两个32位元素 __m128i shuf = _mm_shuffle_epi32(sum_32, _MM_SHUFFLE(0,0,3,2)); sum_32 = _mm_add_epi32(sum_32, shuf); // 累加剩余两个32位元素 shuf = _mm_shuffle_epi32(sum_32, _MM_SHUFFLE(0,0,0,1)); sum_32 = _mm_add_epi32(sum_32, shuf); return _mm_cvtsi128_si32(sum_32); }; // 分别计算低8字节和高8字节的总和,再相加 return horizontal_sum_16_to_32(lo_16) + horizontal_sum_16_to_32(hi_16); }
有符号字节版本
只需将字节扩展改为符号扩展即可:
#include <emmintrin.h> int sum_bytes_sse2_signed(__m128 bytes) { __m128i bytes_i = _mm_castps_si128(bytes); __m128i zero = _mm_setzero_si128(); // 将低8字节和高8字节分别扩展为16位有符号整数 __m128i lo_16 = _mm_cvtepi8_epi16(bytes_i); __m128i hi_16 = _mm_cvtepi8_epi16(_mm_srli_si128(bytes_i, 8)); // 复用相同的水平累加逻辑 auto horizontal_sum_16_to_32 = [](__m128i vec) { __m128i lo_32 = _mm_unpacklo_epi16(vec, zero); __m128i hi_32 = _mm_unpackhi_epi16(vec, zero); __m128i sum_32 = _mm_add_epi32(lo_32, hi_32); __m128i shuf = _mm_shuffle_epi32(sum_32, _MM_SHUFFLE(0,0,3,2)); sum_32 = _mm_add_epi32(sum_32, shuf); shuf = _mm_shuffle_epi32(sum_32, _MM_SHUFFLE(0,0,0,1)); sum_32 = _mm_add_epi32(sum_32, shuf); return _mm_cvtsi128_si32(sum_32); }; return horizontal_sum_16_to_32(lo_16) + horizontal_sum_16_to_32(hi_16); }
存入数组求和的实现
如果不需要极致性能,将寄存器内容存入字节数组后遍历求和是最简单的方法,完全无溢出风险:
#include <emmintrin.h> unsigned int sum_bytes_array(__m128 bytes) { unsigned char byte_arr[16]; __m128i bytes_i = _mm_castps_si128(bytes); // 将寄存器内容存入数组,无需内存对齐 _mm_storeu_si128((__m128i*)byte_arr, bytes_i); unsigned int sum = 0; for (int i = 0; i < 16; ++i) { sum += byte_arr[i]; } return sum; }
内容的提问来源于stack exchange,提问作者user17784058
相关产品推荐
相关产品推荐

