我正在对科学应用程序进行一些数值优化。我注意到的一件事是 GCC 会pow(a,2)通过将调用编译成来优化调用a*a,但调用pow(a,6)并没有优化,实际上会调用库函数pow,这大大降低了性能。(相比之下,英特尔 C++ 编译器可执行文件icc将消除对 的库调用pow(a,6)。)
pow(a,2)
a*a
pow(a,6)
pow
icc
我很好奇的是,当我替换pow(a,6)为a*a*a*a*a*a使用 GCC 4.5.1 和选项“ -O3 -lm -funroll-loops -msse4”时,它使用了 5mulsd条指令:
a*a*a*a*a*a
-O3 -lm -funroll-loops -msse4
mulsd
movapd %xmm14, %xmm13 mulsd %xmm14, %xmm13 mulsd %xmm14, %xmm13 mulsd %xmm14, %xmm13 mulsd %xmm14, %xmm13 mulsd %xmm14, %xmm13
而如果我写(a*a*a)*(a*a*a),它会产生
(a*a*a)*(a*a*a)
movapd %xmm14, %xmm13 mulsd %xmm14, %xmm13 mulsd %xmm14, %xmm13 mulsd %xmm13, %xmm13
这将乘法指令的数量减少到 3。icc具有类似的行为。
为什么编译器无法识别这种优化技巧?
因为浮点数学不是关联的。在浮点乘法中对操作数进行分组的方式会影响答案的数值准确性。
结果,大多数编译器对重新排序浮点计算非常保守,除非他们可以确定答案将保持不变,或者除非您告诉他们您不关心数值准确性。例如:允许 gcc 重新关联浮点操作的 gcc 选项,或者甚至允许-fassociative-math更-ffast-math积极地在精度与速度之间进行权衡的选项。
-fassociative-math
-ffast-math