使用Intrinsics实现矩阵乘法加载矩阵错误结果全0如何解决
问题产生原因
- 直接原因:循环变量
k定义在所有循环外部,首次内层k循环执行完成后,k的值会大于等于N-3,后续进入新的j循环时,k < N-3的条件不成立,内层计算逻辑完全不会执行,累加寄存器r始终保持初始的0值,最终写入result数组的结果自然全为0。 - 累加逻辑错误:累加寄存器
r仅在所有循环开始前清零一次,即便解决了循环变量的问题,每次计算新的result[i][j]元素时也会累加之前的计算结果,导致数值错误。 - 矩阵乘原理性错误:
_mm_mul_ps是向量逐元素相乘的指令,现有逻辑直接将mat1行的4个元素与mat2加载的4个元素逐元素乘加,并没有完成矩阵乘法要求的行与列的点积计算;且&mat2[k][j]加载的是mat2第k行的连续4个元素,不是第j列的4个元素,完全不符合矩阵乘法的行列运算规则。- 收尾循环的计算逻辑错误,现有代码写为
mat1[i][j] * mat2[k][j],正确的矩阵乘运算应该是mat1[i][k] * mat2[k][j]。
- 内存对齐风险:
_mm_load_ps要求加载的内存地址必须16字节对齐,如果你的mat1、mat2数组没有做对齐处理,加载操作会触发未定义行为,也可能导致结果异常。
修复方案
- 调整循环变量的作用域,将
i、j、k放在对应循环的声明位置,避免循环变量残留值影响后续执行逻辑。 - 将累加寄存器
r的清零操作移动到每个result[i][j]元素计算之前,也就是j循环内部、内层k循环的外部,保证每次计算新元素的累加都是从0开始。 - 修正矩阵乘法的运算逻辑:可以先将
mat2做转置处理,转置后原mat2的列变为转置矩阵的行,就可以直接用向量加载指令读取原矩阵的列元素;之后可以用_mm_dp_ps指令直接计算两个向量的点积,或是手动通过向量移位加操作计算点积。 - 如果你无法保证矩阵内存16字节对齐,可以改用
_mm_loadu_ps执行非对齐加载,避免对齐限制导致的异常。 - 修正收尾循环的计算逻辑。
修复后参考代码
// 提前对mat2做转置,存在mat2_T中,mat2_T[j][k] = mat2[k][j] for (int i = 0; i < N; ++i) { for (int j = 0; j < N; j += 4) { __m128 r = _mm_setzero_ps(); int k = 0; // 向量计算部分 for (; k < N - 3; k += 4) { __m128 mat1_v = _mm_loadu_ps(&mat1[i][k]); __m128 mat2_v = _mm_loadu_ps(&mat2_T[j][k]); // 计算4个元素的点积,结果存到r的所有四个位置 __m128 dp = _mm_dp_ps(mat1_v, mat2_v, 0xF1); r = _mm_add_ps(r, dp); } float sum = r.m128_f32[0]; // 收尾计算剩余不足4个的元素 for (; k < N; k++) { sum += mat1[i][k] * mat2[k][j]; } // 写入结果,这里示例j每次步长4,j+1、j+2、j+3的计算逻辑和上面一致,可自行补充 result[i][j] = sum; } }
注:如果你的编译环境不支持_mm_dp_ps(SSE4.1指令集),可以自行用向量移位相加的方式计算点积。
内容的提问来源于stack exchange,提问作者Leon
相关产品推荐
相关产品推荐

