Why doesn't GCC optimize a*a*a*a*a*a to (a*a*a)*(a*a*a)?
Why doesn’t GCC optimize aaaaaa to (aaa)(aaa)?
技术背景
在代码编写中,我们常常希望编译器能够进行优化,以提高代码的执行效率。例如,将 a*a*a*a*a*a
优化为 (a*a*a)*(a*a*a)
,这样可以减少乘法运算的次数。然而,GCC 编译器在处理这类优化时却较为保守,尤其是对于浮点数运算。
实现步骤
浮点数运算不优化的原因
浮点数运算不满足结合律,操作数的分组方式会影响结果的数值精度。例如:
1 |
|
此代码输出 1.000000e-05 0.000000e+00
,说明不同的分组方式导致了不同的结果。因此,大多数编译器在重新排序浮点数计算时非常保守,除非能确保结果不变,或者用户明确表示不关心数值精度。
编译器选项
GCC 提供了一些选项来允许重新关联浮点运算,如 -fassociative-math
选项允许 GCC 重新关联浮点运算,-ffast-math
选项则允许更激进地在精度和速度之间进行权衡。
使用 pow
函数
在具有良好数学库的平台上,pow(a, 6)
比 a*a*a*a*a*a
或 (a*a*a)*(a*a*a)
更准确。例如:
1 |
|
使用 pow
而不是乘法树可以将误差范围缩小 4 倍。
自定义幂函数
可以使用模板元编程实现一个自定义的幂函数:
1 |
|
使用方式为 power<6>(a)
。
整数运算优化
当 a
是整数时,GCC 会将 a*a*a*a*a*a
优化为 (a*a*a)*(a*a*a)
。例如:
1 |
|
生成的汇编代码如下:
1 |
|
FP_CONTRACT
编译指示
ISO C 标准中的 FP_CONTRACT
编译指示允许编译器将表达式视为单个操作。如果将其设置为 ON
,编译器可以用内部幂函数替换表达式,从而提高速度和精度。GCC 默认假设其为 ON
,若要防止 a*b + c
转换为 fma(a, b, c)
,可以使用 -ffp-contract=off
或 -std=c99
选项。
核心代码
自定义幂函数模板
1 |
|
最佳实践
- 对于需要高精度的浮点数运算,优先使用
pow
函数。 - 如果对精度要求不高,且希望提高性能,可以使用
-ffast-math
选项。 - 对于整数运算,GCC 会自动进行优化,无需额外处理。
- 在代码中合理使用自定义幂函数,以提高代码的可读性和性能。
常见问题
为什么 GCC 不默认对浮点数运算进行优化?
因为浮点数运算不满足结合律,优化可能会改变结果的精度,而编译器默认尊重程序员编写代码的意图,除非用户明确要求。
如何让 GCC 对浮点数运算进行优化?
可以使用 -fassociative-math
或 -ffast-math
选项,但这些选项会牺牲一定的精度。
pow
函数和多次乘法运算哪个更准确?
在大多数情况下,pow
函数更准确,尤其是对于任意浮点数。但对于一些特殊值,多次乘法运算可能更准确。