我看到传递
-ffast-math
会导致 cmath
、log
等 cos
函数被视为纯函数,因为编译器能够执行循环不变代码运动。举个简单的例子。
double log_demo(double x, int n) {
double rv = 0.0l;
for (int i = 0; i < n; i++) {
// log only needs to be called once
// but the compiler does not see that without specific flags
rv += log(x);
}
return rv;
}
Without
-ffast-math
log(x)
在循环中计算,否则从循环中取出。在 compiler explorer here 上看到这个 - 分支目标 .L3
是循环分支目标,你可以在 gcc 的输出中看到 call log
,而循环体内没有 -ffast-math
,而在其他循环中只有 addsd
一个。
现在,我不想启用所有不安全的优化并违反 IEEE 754 规范。在不使用
-ffast-math
本身的情况下如何实现这一目标?
您需要的旗帜是
-fno-math-errno
。
-ffast-math
是一组标志,而不仅仅是一个标志。其中一些标志违反了 IEEE 754。来自 man gcc
-快速数学
设置选项-fno-math-errno, -funsafe-数学优化,-仅限有限数学, -fno-舍入数学、-fno-signaling-nans、-fcx-有限范围 和-fexcess- precision =快速。
所以,事实证明 libm 中的数学函数不是纯粹的,因为它们可能会设置
errno
。因此,使它们纯净所需的标志是 -fno-math-errno
。这样做似乎使编译器将它们视为纯函数,并且编译器现在可以执行常见的优化,例如循环不变代码运动。在 编译器资源管理器中查看此内容。
当然,结果是一些与错误条件相关的信息可能会丢失。但是,数学函数中的错误应该主要是由于导致 nan 和 infs 信号的域错误造成的。例如。
man 3 log
没有提及设置errno
,所以它很可能甚至不使用errno
。