如何用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
相关产品推荐
相关产品推荐

