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

如何用带进位反馈的AVX实现16位值并行加法(PE校验和)

用AVX SIMD实现带溢出进位的8个16位值并行加法优化方案

问题背景

需要用AVX SIMD实现8个16位无符号整数的并行加法,要求每个元素加法溢出时,结果额外加1(类似对每个元素独立执行带自身进位的加法)。原实现通过标量循环处理进位,即使开启-O3优化效率仍极低,需优化为纯SIMD方案。

优化方案

核心思路

无符号16位加法溢出的判断条件:若a + b的结果(模2^16后)小于a(或b),则说明加法溢出。利用SIMD比较指令生成溢出掩码,再通过SIMD指令完成条件加1操作,全程无需标量循环。


方案1:AVX2实现(无需K寄存器)

使用AVX2指令集的无符号比较和向量操作,全程在XMM寄存器内完成:

#include <immintrin.h>

__m128i parallel_add_with_carry(__m128i n1, __m128i n2) {
    // 1. 计算并行加法结果(溢出时自动模2^16)
    __m128i sum = _mm_add_epi16(n1, n2);
    // 2. 无符号比较:sum < n1 时,对应元素生成0xFFFF,否则0x0000
    __m128i overflow_mask = _mm_cmplt_epu16(sum, n1);
    // 3. 将掩码转换为0x0001(溢出)或0x0000(无溢出)的向量
    __m128i carry = _mm_and_si128(overflow_mask, _mm_set1_epi16(1));
    // 或用移位替代:__m128i carry = _mm_srli_epi16(overflow_mask, 15);
    // 4. 对溢出元素加1
    return _mm_add_epi16(sum, carry);
}

关键指令说明

  • _mm_cmplt_epu16:对应汇编指令vpcmpuw $0(无符号小于比较),直接生成每个元素的溢出掩码。
  • _mm_and_si128/_mm_srli_epi16:将掩码向量转换为仅保留0x0001或0x0000的进位向量。

方案2:AVX-512实现(利用K寄存器)

AVX-512的掩码寄存器(K0-K7)可直接控制SIMD操作的元素范围,无需生成掩码向量,效率更高:

#include <immintrin.h>

__m128i parallel_add_with_carry_avx512(__m128i n1, __m128i n2) {
    // 1. 计算并行加法结果
    __m128i sum = _mm_add_epi16(n1, n2);
    // 2. 生成8位K掩码:对应元素溢出则置1
    __mmask8 overflow_mask = _mm_cmplt_epu16_mask(sum, n1);
    // 3. 仅对掩码为1的元素执行加1操作
    return _mm_mask_add_epi16(sum, overflow_mask, sum, _mm_set1_epi16(1));
}

关键指令说明

  • _mm_cmplt_epu16_mask:对应汇编指令vpcmpuw $0, %xmm0, %xmm1, %k1,直接生成K寄存器掩码。
  • _mm_mask_add_epi16:对应汇编指令vpaddw %xmm2, %xmm0, %xmm0 {%k1},仅对掩码选中的元素执行加法。

对问题的针对性解答

  1. vpcmp<CC>uw指令的作用:完全有用。vpcmpuw $0(LT条件)是实现SIMD溢出判断的核心,能直接并行比较每个16位元素的无符号大小,生成溢出掩码。
  2. K寄存器的使用方式:通过无符号比较指令生成对应位数的K掩码,再配合带掩码的SIMD加法指令,直接对溢出元素执行加1操作,避免了掩码向量的转换开销,最大化SIMD并行效率。

原代码的问题分析

原代码使用volatile union存储向量数据,后续通过标量循环处理进位,完全破坏了SIMD的并行性:

  • volatile会强制编译器将向量数据写入内存,而非保留在寄存器中,增加内存读写开销。
  • 标量循环将SIMD计算的结果拆分为单个元素处理,完全丧失了SIMD的并行优势。

测试验证

替换原parallel_add_with_carry函数后,测试用例输出结果与预期一致:

void test(void) {
    uint16vec v1 = {0}, v2 = {0}, res;
    v1.y[0] = 0x000A; v2.y[0] = 0x0014; // 10+20=30,无溢出,结果0x001E
    v1.y[1] = 0xFFF0; v2.y[1] = 0x0013; // 0xFFF0+0x0013=0x10003,溢出后模2^16得0x0003,加1后为0x0004
    res.x = parallel_add_with_carry(v1.x, v2.x);
    fprintf(stdout,"%04X | %04X\n", res.y[0], res.y[1]);
}

输出:001E | 0004

内容的提问来源于stack exchange,提问作者Devvy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 11:29:53