两个卷积循环是否等价?SIMD优化后结果不符问题咨询
我正在处理卷积运算,为提升执行速度采用SIMD指令同时执行两次乘法,将一次结果存入64位变量的高32位,另一次存入低32位,但优化后的新代码运行结果与原代码不一致。
原代码实现
int32_t v32; int16_t arr_2[1024]; int16_t data[96]; int32_t accu; ... for(int j=0; j<INPUT_F; j++){ v32 = arr_2[l*OUT_F+j] * data[k*K*INPUT_F+(l-i+K/2)*INPUT_F+j]; accu += v32; } ...
优化后的代码实现
uint64_t v64; int16_t arr_2[1024]; int16_t data[96]; int32_t accu; ... for(int j=0; j<INPUT_F/2; j++){ v64 = __mul(arr_2[l*OUT_F+2*j], data[k*K*INPUT_F+(l-i+K/2)*INPUT_F+2*j]); // 用SIMD指令执行数组中连续两个值的乘法 accu += ((int32_t)(v64 & 0xFFFFFFFF)); // 第一个结果 accu += ((int32_t)((v64 >> 32) & 0xFFFFFFFF)); // 第二个结果 } ...
__mul的定义为uint64_t __mul(uint32_t a, uint32_t b);,尽管操作数是uint32_t,但其会处理内部的两个int16_t值。
除乘法函数外的操作问题分析
你当前的优化代码存在几处明显错误,导致和原代码不等价:
数组索引与参数打包错误:
原代码每次循环处理一组(arr_2[j], data[j])的乘法,优化后要同时处理(arr_2[2j], data[2j])和(arr_2[2j+1], data[2j+1])两组。但你的代码只将arr_2[2j]和data[2j]传给__mul,没有把连续的第二个元素(2j+1位置)打包到uint32_t参数中。
按照__mul的设计(处理参数内部的两个int16_t),你需要把两个连续的int16_t打包成一个uint32_t:比如将arr_2[2j]存在参数a的低16位,arr_2[2j+1]存在高16位;同理data[2j]和data[2j+1]打包到参数b中。当前的参数传递只传入了单个int16_t(会被符号扩展为32位,高16位不是第二个元素的值),导致__mul处理的是错误的操作数。循环次数遗漏问题:
如果INPUT_F是奇数,原循环执行INPUT_F次,而优化后的循环执行INPUT_F/2次(整数除法),会直接漏掉最后一组j=INPUT_F-1的乘法操作,导致累加结果少了一个值。必须确保INPUT_F是偶数,或者在循环结束后单独处理剩余的一次乘法。符号转换的潜在风险:
原代码中int16_t相乘得到的是有符号int32_t结果。优化后你将uint64_t的低/高32位直接强转为int32_t,若__mul是将有符号的int32_t结果以补码形式存储在uint64_t中,多数编译器能正确完成无符号到有符号的转换,但这属于实现定义行为。更稳妥的方式是先将对应32位区域视为有符号数,比如用*(int32_t*)&v64取低32位,*(int32_t*)((uint8_t*)&v64 + 4)取高32位(需注意字节序,若SIMD指令是小端存储则正确)。
内容的提问来源于stack exchange,提问作者Dresult

