-ffast-math 可以安全地用于典型项目吗?

Can -ffast-math be safely used on a typical project?

在回答我建议 -ffast-math 的问题时,有评论指出这很危险。

个人感觉,除了科学计算之外,还可以。我还假设严肃的金融应用程序使用定点而不是浮点。

当然,如果你想在你的项目中使用它,最终的答案是在你的项目中测试它,看看它对它的影响有多大。但我认为尝试过并有过此类优化经验的人可以给出一个笼统的答案:

ffast-math可以安全地用于正常项目吗?

鉴于 IEEE 754 浮点数存在舍入误差,假设您已经接受了不精确的计算。


This answer 特别说明 -ffast-math 所做的不仅仅是重新排序操作,结果会略有不同(不检查 NaN 或零,禁用带符号的零只是为了举几个例子),但我看不出这些最终会在真实代码中产生什么影响。


我试图想到 典型 浮点数的用途,这就是我想出的:

和学校项目,但这些在这里并不重要。

是的,您可以在普通项目上使用 -ffast-math,以获得 "normal projects." 的适当定义,这可能包括所有已编写程序的 95%。

但话又说回来,所有编写的程序中有 95% 也不会从 -ffast-math 中获益太多,因为它们没有进行足够的浮点数学运算,所以它很重要。

Given that IEEE 754 floating point has rounding errors, the assumption is that you are already living with inexact calculations.

你应该回答的问题不是程序是否期望不精确的计算(它最好期望它们,否则它会在有或没有 -ffast-math 的情况下中断),而是程序是否期望近似值与预测值完全相同由 IEEE 754,以及与 IEEE 754 预测的行为完全相同的特殊值;或者程序是否设计为在每个操作都会引入一个小的不可预测的相对误差的较弱假设下正常工作。

许多算法不使用特殊值(无穷大、NaN),并且设计为在每个操作都会引入较小的不确定性相对误差的计算模型中运行良好。这些算法在 -ffast-math 下运行良好,因为它们不使用每个操作的误差恰好是 IEEE 754 预测的误差这一假设。当舍入模式不是默认的舍入到- 最近:最后的误差可能更大(或更小),但向上舍入模式的 FPU 也实现了这些算法期望的计算模型,因此它们在这些条件下或多或少地工作得很好。

其他算法(例如Kahan summation, “double-double” libraries in which numbers are represented as the sum of two doubles) expect the rules to be respected to the letter, because they contain smart shortcuts based on subtle behaviors of IEEE 754 arithmetic. You can recognize these algorithms by the fact that they do not work when the rounding mode is other than expected either. I once asked a question关于设计适用于所有舍入模式的双双操作(对于可能被抢占而没有机会恢复舍入模式的库函数):它是额外的工作,这些改编的实现仍然不适用于 -ffast-math.

简短的回答:不,您不能安全地使用 -ffast-math,除非是在设计用于它的代码上。它会为各种重要的结构生成完全错误的结果。特别是,对于任意大的 x,存在具有正确值 x 的表达式,但将计算为 0-ffast-math,反之亦然。

作为一个更宽松的规则,如果您确定您正在编译的代码是由实际上并不了解浮点数学的人编写的,那么使用 -ffast-math 可能不会产生任何结果错误(与程序员的意图相比)比他们已经发生的更多。这样的程序员不会执行有意的舍入或其他严重中断的操作,可能不会使用 nan 和无穷大等。最可能的负面后果是让已经存在精度问题的计算爆炸并变得更糟。我认为这种代码已经很糟糕了,你不应该在生产中使用它,无论有没有 -ffast-math.

根据个人经验,我已经收到了很多来自尝试使用 -ffast-math 的用户(甚至是将其隐藏在默认值 CFLAGS 中的用户的错误报告,呃!),我是强烈倾向于将以下片段放入任何具有浮点数学的代码中:

#ifdef __FAST_MATH__
#error "-ffast-math is broken, don't use it"
#endif

如果您仍想在生产中使用 -ffast-math,您需要实际花费精力(大量的代码审查时间)来确定它是否安全。在此之前,您可能想先衡量是否有任何好处值得花费这些时间,答案很可能是否定的。

几年后更新:事实证明,-ffast-math 授予 GCC 进行转换的许可,这些转换有效地将未定义的行为引入您的程序,导致编译错误 -大的后果。请参阅 PR 93806 和相关错误的示例。所以真的,不,使用起来永远不安全

我不建议避免使用此选项,但我提醒一个意外浮点行为反击的例子。

代码就像这样无辜的构造:

float X, XMin, Y;
if (X < XMin)
{
    Y= 1 / (XMin - X);
}

这有时会引发被零除的错误,因为在进行比较时,使用了完整的 80 位表示法 (Intel FPU),而稍后在执行减法时,值被截断为 32 位代表性,可能是平等的。

它做的特别危险的事情之一是暗示 -ffinite-math-only,它允许显式 NaN 测试假装不存在 NaN。对于任何显式处理 NaN 的代码来说,这都是个坏消息。它会尝试测试 NaN,但测试将通过它的牙齿说谎,并声称没有任何东西是 NaN,即使它是。

这可能会产生非常明显的结果,例如让 NaN 冒泡给用户,而以前它们会在某个时候被过滤掉。这当然很糟糕,但您可能会注意到并修复它。

当 NaN 检查用于错误检查时,一个更隐蔽的问题出现了,因为某些东西真的不应该是 NaN。但也许由于某些错误、错误数据或 -ffast-math 的其他影响,它无论如何都会变成 NaN。现在你不检查它,因为假设没有任何东西是 NaN,所以 isnanfalse 的同义词。在您已经发布您的软件很久之后,事情会虚假地出错,并且您将收到 "impossible" 错误报告 - 您确实检查了 NaN,它就在代码中,它不会失败!但确实如此,因为有一天有人将 -ffast-math 添加到标志中,也许你甚至自己做了,不完全知道它会做什么或者忘记你使用了 NaN 检查。

那么我们可能会问,这正常吗?这变得非常主观,但我不会说检查 NaN 特别不正常。完全循环并断言它不正常 因为 -ffast-math 打破它可能是个坏主意。

它还会做很多其他可怕的事情,详见其他答案。

是的,只要您知道自己在做什么,就可以安全地使用它们。这意味着您了解它们代表量级,而不是精确值。这意味着:

  1. 您始终对任何外部 fp 输入进行健全性检查。
  2. 你永远不会除以 0。
  3. 你永远不会检查是否相等,除非你知道它是一个绝对值低于尾数最大值的整数。
  4. 等等

事实上,我认为相反。除非您在 NaN 和非正规数有意义的非常特定的应用程序中工作,或者如果您真的需要一点点的可重复性,那么 -ffast-math 应该默认打开。这样,您的单元测试就有更好的机会清除错误。基本上,只要您认为 fp 计算具有可重现性或精度,即使在 ieee 下,您就错了。