禁用编译器优化时AVX矩阵乘法性能更低,原因何在?
为什么无优化时AVX版本DGEMM代码没有更快?
我先写了如下手动循环展开的DGEMM实现:
void dgemm3(double* A, double* B, double* C, int n){ register int i, j, k, n4 = n * 4; register double cij0, cij1, cij2, cij3; register double *a0, *a1, *a2, *a3, *b0, *b1, *b2, *b3; for (i=0; i<n; ++i){ for (j=0; j<n; ++j){ a0 = &A[i*n]; a1 = a0 + 1; a2 = a1 + 1; a3 = a2 + 1; b0 = &B[j]; b1 = b0 + n; b2 = b1 + n; b3 = b2 + n; cij0 = cij1 = cij2 = cij3 = 0; for(k = 0; k < n; k+=4, a0+=4, a1+=4, a2+=4, a3+=4, b0+=n4, b1+=n4, b2+=n4, b3+=n4){ cij0 += *a0 * *b0; cij1 += *a1 * *b1; cij2 += *a2 * *b2; cij3 += *a3 * *b3; } *C++ = cij0 + cij1 + cij2 + cij3; } } }
随后我又实现了基于AVX指令集的版本:
void dgemm_avx (double* A, double* B, double* C, int n) { for (int i=0; i<n; i++) { for (int j=0; j<n; j+=4) { __m256d c0 = _mm256_setzero_pd(); for (int k=0; k<n; k++) { __m256d m1 = _mm256_broadcast_sd(A+i*n+k); __m256d m2 = _mm256_loadu_pd(B+k*n+j); __m256d m3 = _mm256_mul_pd(m1,m2); c0 = _mm256_add_pd(c0,m3); } _mm256_storeu_pd(C+i*n+j, c0); } } }
我原本以为AVX版本的运行速度会更快,但实际测试发现并非如此;不过当给两个版本都加上-O1编译优化后,AVX版本确实表现出了更快的速度。我想知道:为什么禁用优化时,AVX版本的代码没能体现出优势?
核心原因与细节
无优化(默认-O0)时,编译器会严格按代码字面生成机器指令,几乎不做任何优化,这会让AVX代码的额外开销被放大,同时手动展开的版本反而能避开无优化下的低效问题:
AVX指令的冗余开销被放大
无优化时,编译器不会对__m256d类型变量做寄存器分配优化,会频繁在内存和AVX寄存器之间来回读写,而AVX内存操作本身有延迟,加上循环中重复计算A+i*n+k这类地址、冗余的变量读写,直接抵消了SIMD并行的优势。手动展开版本在无优化下更高效
第一个手动展开的版本,手动实现了4路并行,且用register关键字提示编译器将变量放入通用寄存器(无优化下该关键字仍有一定作用)。无优化时,这个版本的内存访问模式更规整,还手动减少了循环分支的开销,每轮循环处理4个元素,相比AVX版本的冗余操作,执行效率反而更高。优化开启后AVX的优势才真正体现
开启-O1及以上优化后,编译器会做寄存器分配、循环展开、地址计算优化、冗余操作消除等:- 把
__m256d变量尽量留在AVX寄存器中,减少内存读写; - 提前计算
A+i*n+k这类地址并缓存到通用寄存器,避免重复计算; - 优化AVX指令调度,让指令流水线更顺畅,真正发挥一次处理4个双精度浮点数的并行优势。
此时AVX的SIMD并行增益会超过手动展开的效果,所以速度更快。
- 把
内容的提问来源于stack exchange,提问作者Mohammad
相关产品推荐
相关产品推荐

