基于x86 Intrinsic的复数浮点矩阵乘法延迟优化规则咨询
这段代码用AVX指令实现了分离实部、虚部的复数矩阵与普通浮点矩阵的乘法,针对这类SIMD运算的延迟优化,核心规则如下:
指令调度:提前预加载隐藏内存延迟
当前代码里先加载数据紧接着就做融合乘加,CPU会在等待内存加载完成时闲置。优化思路是把加载和运算指令交错,提前预加载下一轮循环的数据,让CPU处理当前运算的同时,内存加载并行执行。比如调整内层k循环:// 先加载第一轮数据 VecReA = _mm256_load_ps(pRowReA); VecImA = _mm256_load_ps(pRowImA); VecB = _mm256_load_ps(pSrcB); pRowReA += 8; pRowImA += 8; pSrcB += 8; for (int k = 1; k < (ColsA >> 3); k++) { // 用上一轮加载的数据做运算 ReSum = _mm256_fmadd_ps(VecReA, VecB, ReSum); ImSum = _mm256_fmadd_ps(VecImA, VecB, ImSum); // 预加载当前轮的下一组数据 VecReA = _mm256_load_ps(pRowReA); VecImA = _mm256_load_ps(pRowImA); VecB = _mm256_load_ps(pSrcB); pRowReA += 8; pRowImA += 8; pSrcB += 8; } // 处理最后一组加载的数据 ReSum = _mm256_fmadd_ps(VecReA, VecB, ReSum); ImSum = _mm256_fmadd_ps(VecImA, VecB, ImSum);填满FMA流水线,拆分长依赖链
_mm256_fmadd_ps这类融合乘加指令有多级流水线(比如Intel Skylake是6级),单条依赖链无法填满流水线。可以通过循环展开+多累加寄存器拆分依赖,比如用ReSum1/ReSum2、ImSum1/ImSum2分别累加不同组的数据,最后再合并结果,让CPU同时调度多个独立的运算流。保证内存对齐,降低加载延迟
AVX的对齐加载_mm256_load_ps比非对齐加载_mm256_loadu_ps延迟更低,要确保所有输入指针是32字节对齐的。可以用_mm_malloc分配内存,或者给数组加上对齐属性(比如__attribute__((aligned(32))));如果无法保证对齐,就改用非对齐加载指令。分块优化提升Cache命中率
当前循环顺序i→j→k已经是Cache友好的,但如果矩阵尺寸很大,j循环迭代时频繁切换B的列会导致Cache miss。可以把大矩阵拆成能放进L1/L2 Cache的小Tile,让每个Tile内的访问都命中Cache,减少Cache miss带来的额外延迟。消除冗余指令,简化求和逻辑
当前代码里实部、虚部的水平求和逻辑完全重复,可以封装成内联函数减少冗余。另外,_mm_hadd_ps可以替换成更高效的实现:比如用AVX2的_mm256_hadd_ps(支持的话),或者手动拆分加法步骤,减少指令数。
内容的提问来源于stack exchange,提问作者Zvi Vered

