You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用SSE/SSE2无溢出计算__m128寄存器中所有字节的和?

解决SSE/SSE2计算__m128寄存器字节和的溢出问题

首先,你的原有代码存在指令使用错误,并非真正的溢出问题,导致结果错误:

  1. 未显式将__m128转换为__m128i,直接传入_mm_sad_epu8可能导致字节内容被错误解析;
  2. _mm_cvtsi128_si32(sum)会读取寄存器低32位(包含两个16位的和),而非低8字节的单独和;
  3. _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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 06:28:10