gcc的快速数学实际上是做什么的?


144

我知道gcc的--ffast-math标志可以大大提高浮动操作的速度,并且超出了IEEE标准,但是我似乎无法找到有关启动时实际发生的情况的信息。谁能解释一些细节,或者给出一个清晰的例子,说明如果打开或关闭该标志会发生什么变化?

我确实尝试通过SO寻找类似问题,但找不到任何能解释快速数学原理的东西。

Answers:


86

如您所提到的,它允许不保留严格的IEEE合规性的优化。

一个例子是这样的:

x = x*x*x*x*x*x*x*x;

x *= x;
x *= x;
x *= x;

由于浮点算术不是关联的,因此由于四舍五入,运算的顺序和因式分解会影响结果。因此,不能在严格的FP行为下完成此优化。

我实际上没有检查过GCC是否确实进行了这种特殊的优化。但是想法是一样的。


25
@Andrey:对于此示例,您从7乘以3下降到了
Mysticial 2011年

4
@Andrey:从数学上讲,这是正确的。但是由于舍入不同,最后几位的结果可能会略有不同。
Mysticial 2011年

1
在大多数情况下,这种微小的差异并不重要(相对于,大约为10 ^ -16 double,但因应用而异)。要注意的一件事是,快速数学优化不一定会添加“更多”的四舍五入。不符合IEEE标准的唯一原因是,答案与所写内容有所不同(尽管略有不同)。
Mysticial 2011年

1
@user:误差的大小取决于输入数据。相对于结果,它应该很小。例如,如果x小于10,则Mystical示例中的错误将降低10 ^ -10左右。但是,如果x = 10e20错误可能是数百万。
Ben Voigt

3
@stefanct实际上是关于-fassociative-math其中包含的内容,-funsafe-math-optimizations而其中又启用了-ffast-math GCC为什么不优化a*a*a*a*a*a(a*a*a)*(a*a*a)
phuclv

255

-ffast-math 除了打破严格的IEEE规范外,还可以做更多的事情。

首先,当然会破坏严格的IEEE规范,例如允许将指令重新排序为在数学上相同(理想情况下)但在浮点上不完全相同的东西。

其次,它禁用了errno单指令数学函数之后的设置,这意味着避免写入线程局部变量(这在某些体系结构上对于那些函数可能造成100%的差异)。

第三,它假设所有数学都是有限的,这意味着不会对NaN(或零)进行检查,否则会产生不利影响。仅仅假设这不会发生。

第四,它可以进行倒数逼近除法和倒数平方根的。

此外,它禁用有符号零(即使目标支持,代码也假定不存在有符号零)和舍入数学,这可以在编译时进行常量折叠。

最后,它生成的代码假定不会因信号发送/陷阱数学运算而发生硬件中断(也就是说,如果无法在目标体系结构上禁用这些中断,并且因此而发生,则不会进行处理)。


15
达蒙,谢谢!您可以添加一些参考吗?类似于gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html-ffast-math 设置-fno-math-errno,-funsafe-math优化,-ffinite-math-only,-fno-rounding-math,-fno-signaling -nans和-fcx-limit-range。此选项将定义预处理程序宏FAST_MATH “以及glibc中的某些内容,例如(math.h靠近math_errhandling)” 默认情况下,所有功能均支持errno和异常处理。在gcc的快速数学模式下,如果定义了内联函数,则可能不正确。
osgx 2014年

4
@javapowered:是否“危险”取决于您需要的保证。-ffast-math允许编译器偷工减料并兑现某些承诺(如前所述),这通常并不危险,对大多数人来说也不是问题。对于大多数人来说,是一样的,只是速度更快。但是,如果您的代码假设并依赖于这些承诺,那么您的代码可能会表现出与预期不同的行为。通常,这意味着该程序在大多数情况下似乎都可以正常运行,但是某些结果可能是“意外”的(例如,在物理模拟中,两个对象可能无法正确碰撞)。
戴蒙2014年

2
@Royi:两者应该彼此独立。-O2通常会进行“每一个”法律优化,但那些以速度为代价的法律除外。-O3还可以进行以速度为代价的优化。它仍然保持100%正确性。-ffast-math试图通过允许“轻微不正确”的行为来加快数学运算的速度,这种行为通常是无害的,但是被标准的措辞认为是不正确的。如果您的代码在两个编译器上的速度确实有很大不同(不仅仅是1-2%),请检查您的代码是否严格符合标准,并且...
Damon

1
...产生零警告。另外,请确保您不会妨碍使用别名规则和自动矢量化之类的方法。原则上,GCC的性能至少应与MSVC一样好(通常以我的经验更好)。如果不是这种情况,您可能犯了一个微妙的错误,MSVC只是忽略了这个错误,但是这导致GCC禁用了优化。如果要同时使用,则应同时选择这两种选择。
达蒙

1
@Royi:对我来说,这段代码看起来并不小而简单,在几分钟(甚至几小时)之内就无法深入分析。除其他事项外,它涉及看似无害的事情,#pragma omp parallel for并且在循环体内,您既要读取和写入函数自变量所指向的地址,又要进行大量的分支。作为一个没有根据的猜测,您可能正在实现定义的线程调用中破坏高速缓存,而MSVC可能会错误地避免使用别名规则将强制执行的中间存储。不可能告诉。
达蒙
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.