为何x86平台MS C编译器下AVX2+FMA代码性能下降?
开普勒方程初始值计算函数的AVX2性能异常问题
以下是求解开普勒方程的三种经典初始值计算函数,其中S3是代码简洁但时序表现异常的典型案例。问题出现在Microsoft C编译器的x86编译模式下:理论上更简单的S3函数在AVX2最高优化下的执行时间远长于S9函数,而Intel编译器无此问题(因Mingw的x87正弦函数问题无法测试GCC)。所有初始值函数的输入范围为:偏心率0 ≤ e ≤ 1,平近点角-π ≤ M ≤ π,这些经典函数由Gooding & Odel(1986)提出:
double S1(double e, double M) { if(M>0) return M + e; else return M - e; // 最简单的初始值,最终保证收敛 } // 当e趋近于1时,S3和S9都无法保证收敛 double Gooding_S3(double e, double M) { return M + e*sin(M)*(1+e*cos(M)); // 速度慢且实用性低,S9的精度高得多! } double Gooding_S9(double e, double M) { // 原论文中的写法为 sin(M)/sqrt(1-2*e*cos(M)+e*e) // 这是Halley方法的一种形式,由E-M的简单二次近似求根推导而来,在e<0.7时表现优秀且速度快! double y; y = 1-e; if (M == 0.0) return M; // 防止除零 double s = sin(M/2); return M+e*sin(M)/sqrt(y*y+4*e*s*s); // 更精确的形式(当e趋近于1时误差极大) }
快速统计操作数及类型后,理论执行时间应依次递增,但实际并非如此!S9有时反而更快(操作数统计可能存在误差):
- S1:仅加法操作
- S3:两次加法、三次乘法,调用
sincos - S9:一次减法、两次加法、五次乘法、一次除法,两次调用
sin、一次调用sqrt
使用标准_cdecl调用约定的x86代码生成及MSC 2022编译器,得到S3与S9的时序如下(S1因函数调用开销约为8周期,AVX2下的74并非笔误,切换为x64代码则无此问题):
| Starter | x87 | SSE2 | AVX | AVX2 | *_64 |
|---|---|---|---|---|---|
| S3 | 199 | 23 | 24 | 74 | 23 |
| S9 | 206 | 50 | 48 | 47 | 30 |
已分离出x86代码生成中S3的AVX与AVX2差异片段,但无法理解为何AVX2性能更差,同时惊讶于S9的快速且稳定的时序表现,x64代码则性能稳定且更快,对此深感困惑。
以下是S3的快速AVX代码(无FMA):
call ___libm_sse2_sincos_ (0BF7D50h) vshufpd xmm1,xmm0,xmm0,1 vmulsd xmm1,xmm1,mmword ptr [e] vmulsd xmm0,xmm0,mmword ptr [e] vaddsd xmm1,xmm1,mmword ptr [__real@3ff0000000000000 (0BFBFB8h)] vmulsd xmm0,xmm1,xmm0 vaddsd xmm0,xmm0,mmword ptr [M] vmovsd qword ptr [esp],xmm0 fld qword ptr [esp]
以下是S3的慢速AVX2代码(使用FMA,优化效果不佳):
call ___libm_sse2_sincos_ (0672600h) vmovsd xmm2,qword ptr [e] vpermilpd xmm1,xmm0,1 vfmadd213sd xmm2,xmm1,mmword ptr [__real@3ff0000000000000 (0676F50h)] vmulsd xmm0,xmm0,mmword ptr [e] vfmadd213sd xmm0,xmm2,mmword ptr [M] vmovsd qword ptr [esp],xmm0 fld qword ptr [esp]
现提出疑问:为何AVX2代码性能远低于AVX/SSE2,且仅在x86代码中出现此问题?同时寻求此类优化指令集反而导致性能下降的规避建议。该问题似乎与x86通过ST(0)返回浮点结果的旧链接约定有关,且可能与超越库函数调用相关。
内容的提问来源于stack exchange,提问作者Martin Brown
相关产品推荐
相关产品推荐

