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

如何用NEON Intrinsics正确实现带符号8位数组乘累加?

ARM7平台带符号8位数组乘累加的NEON优化实践

需求与初始状态

需要在ARM7嵌入式设备上实现两个带符号8位数组的乘累加操作,程序每毫秒运行一次。已启用编译优化选项:
-mtune=cortex-a15.cortex-a7 -mfpu=neon-vfpv4 -ftree-vectorize -ffast-math -mfloat-abi=hard
性能虽有提升,但仍接近运行极限。

原C实现代码:

for(i = 4095; i >= 0; --i)
{ 
  accum += arr1[i]*arr2[i];
}

首次NEON实现的问题

尝试用NEON Intrinsics改写后,循环速度提升约5倍,但计算结果错误,推测是累加过程溢出或结果合并方式不当。首次NEON代码如下:

for(int i = 256; i > 0; --i)
{   
  int8x16_t vec16a = vld1q_s8(&arr1[index]);                                                                                                                  
  int8x16_t vec16b = vld1q_s8(&arr2[index]);
  vec16res = vmlaq_s8(vec16res, vec16a, vec16b);
  index+=16;
} 

问题根源:vmlaq_s8是将两个int8向量的对应元素相乘后,累加到int8类型的结果向量中。int8的取值范围仅为-128~127,乘累加过程中极易发生溢出,且最终合并向量值时会丢失大量精度,导致结果错误。

优化方案与性能测试

改用8x8向量操作,添加__builtin_prefetch()预取数据,并通过10次平均测试验证性能,最终达标。测试结果:

$ ./test 10
original code time ~ 30392nS
optimized C time   ~  8458nS
NEON elapsed time  ~  3199nS

最终NEON实现代码

int32_t   sum    = 0;                                                                                                                                            
int16x8_t vecSum = vdupq_n_s16(0);
int8x8_t  vec8a;
int8x8_t  vec8b;
int32x4_t sum32x4;
int32x2_t sum32x2;

#pragma unroll
for (i = 512; i > 0; --i)
{
  vec8a  = vld1_s8(&A[index]);
  vec8b  = vld1_s8(&B[index]);
  vecSum = vmlal_s8(vecSum,vec8a,vec8b);
  index += 8;
}

sum32x4 = vaddl_s16(vget_high_s16(vecSum),vget_low_s16(vecSum));
sum32x2 = vadd_s32(vget_high_s32(sum32x4),vget_low_s32(sum32x4));
sum    += vget_lane_s32(vpadd_s32(sum32x2,sum32x2),0);

关键优化点:

  • 使用vmlal_s8指令:将int8元素相乘得到的int16结果,直接累加到int16类型的向量中,避免了中间溢出
  • 分阶段合并向量值:从int16向量逐步扩展到int32,最后合并为单个int32累加值,全程保证计算精度
  • 用#pragma unroll展开循环,减少循环控制开销
  • 添加内存预取操作,降低内存访问延迟

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 06:45:36