如何优化基于AVX/2的手动向量化矩阵向量乘加函数性能?
AVX2手动向量化全连接前向函数优化建议
- 内存对齐优化
你当前用的_mm256_loadu_ps是不对齐加载指令,相比对齐加载指令_mm256_load_ps延迟更高、吞吐量更低。你可以将a1、w、b、a2所有涉及的浮点数组都按32字节(AVX2向量宽度)对齐,分配内存时用aligned_alloc或者编译器对齐属性声明,将所有非对齐加载替换为对齐加载,大矩阵场景下可以提升10%~15%的性能。另外你定义的#define re *restrict写法容易造成语法歧义,建议直接显式写float *restrict a2这类声明,避免编译器理解错误。 - 替换高延迟的水平求和指令
你当前用的_mm256_dp_ps点积指令跨向量lane操作延迟很高,主流AVX2 CPU上该指令延迟是1114个周期,远高于普通向量运算的34周期。你可以用普通向量加法组合实现水平求和,性能提升明显:
替换后还可以删掉你定义的// 替换原来的dp_ps和z[0]+z[4]的代码 __m128 low = _mm256_castps256_ps128(z); __m128 high = _mm256_extractf128_ps(z, 1); __m128 sum128 = _mm_add_ps(low, high); sum128 = _mm_hadd_ps(sum128, sum128); sum128 = _mm_hadd_ps(sum128, sum128); a2[i] += _mm_cvtss_f32(sum128);one常量向量,节省一个向量寄存器。 - 内层循环展开+多累加器并行
主流AVX2 CPU的FMA单元通常有2个流水线,支持每个周期发射2条FMA指令,你当前只用了1个z累加器,无法跑满FMA流水线。你可以将内层j循环展开2~4次,声明多个累加器并行计算:
该修改可以让FMA单元利用率提升2~3倍。// 内层j循环示例,展开4次 __m256 z0 = _mm256_setzero_ps(), z1 = _mm256_setzero_ps(); __m256 z2 = _mm256_setzero_ps(), z3 = _mm256_setzero_ps(); for(int j=0; j<n1; j+=32) { // 每次处理4*8=32个元素 x = _mm256_load_ps(&a1[j]); y = _mm256_load_ps(&w[i][j]); z0 = _mm256_fmadd_ps(x, y, z0); x = _mm256_load_ps(&a1[j+8]); y = _mm256_load_ps(&w[i][j+8]); z1 = _mm256_fmadd_ps(x, y, z1); x = _mm256_load_ps(&a1[j+16]); y = _mm256_load_ps(&w[i][j+16]); z2 = _mm256_fmadd_ps(x, y, z2); x = _mm256_load_ps(&a1[j+24]); y = _mm256_load_ps(&w[i][j+24]); z3 = _mm256_fmadd_ps(x, y, z3); } __m256 z = _mm256_add_ps(_mm256_add_ps(z0, z1), _mm256_add_ps(z2, z3)); - 提升缓存复用率
你当前的遍历模式下,a1向量会被重复读取n2次,当n1很大时a1无法完全放在L1缓存里,会产生大量缓存 miss。你可以调整循环顺序,每次计算8个输出元素a2[i]~a2[i+7],加载一次a1[j]同时和8行权重做FMA运算,a1的读取次数从n2次降到1次,大幅提升L1缓存命中率。 - 编译选项优化
编译时添加-O3 -mavx2 -mfma -fno-math-errno -fno-trapping-math选项,如果是在本地运行可以加-march=native让编译器针对你当前的CPU架构做指令调度优化,额外获得5%左右的性能提升。
内容的提问来源于stack exchange,提问作者PanJa
相关产品推荐
相关产品推荐

