- fast-math可以安全地用于一个典型的项目吗?

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

本文关键字:一个 典型的 项目 fast-math 安全 用于      更新时间:2023-10-16

在回答我建议使用-ffast-math的问题时,有人指出它很危险。

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

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

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

考虑到IEEE 754浮点数有舍入误差,假设您已经生活在不精确的计算中。


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


我试着思考典型的对浮点数的使用,这就是我想到的:

  • GUI (2D, 3D,物理引擎,动画)
  • 自动化(如汽车电子)
  • 机器人
  • 工业测量(如电压)

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

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

这可能会产生非常明显的结果,例如让NaN冒泡到用户面前,而以前它们会在某个时候被过滤掉。这当然是不好的,但也许你会注意到并修复它。

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

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

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

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

代码是这样说的:

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

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

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

作为一个更宽松的规则,如果你确定你编译的代码是由一个不懂浮点数学的人写的,使用-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和相关的bug。所以,真的,不,使用是不安全的。

考虑到IEEE 754浮点数有舍入误差,假设您已经生活在不精确的计算中。

你应该回答的问题不是程序是否期望不精确的计算(它最好期望它们,否则使用或不使用-ffast-math都会中断),而是程序是否期望近似与IEEE 754预测的完全相同,以及与IEEE 754预测的完全相同的特殊值;或者程序是否设计得能够很好地处理较弱的假设,即每个操作都会引入一个不可预测的小相对误差。

许多算法不使用特殊值(无穷大,NaN),并且被设计成在每个操作引入一个小的不确定性相对误差的计算模型中很好地工作。这些算法在-ffast-math中工作得很好,因为它们没有使用假设,即每个操作的误差都与IEEE 754预测的误差完全一致。当舍入模式不是默认的四舍五入模式时,这些算法也能很好地工作:最终的误差可能会更大(或更小),但在向上舍入模式下的FPU也实现了这些算法所期望的计算模型,因此它们在这些条件下或多或少地工作得一样好。

其他算法(例如Kahan求和,"双双"库,其中数字表示为两个双精度数的和)希望规则得到严格遵守,因为它们包含基于IEEE 754算法微妙行为的智能快捷方式。您可以通过以下事实来识别这些算法:当舍入模式与预期不同时,它们也不起作用。我曾经问过一个关于设计双双操作的问题,它可以在所有舍入模式下工作(对于可能被预先占用而没有机会恢复舍入模式的库函数):这是额外的工作,这些适应的实现仍然不能与-ffast-math一起工作。

是的,您可以在正常项目中使用-ffast-math,以适当地定义"正常项目"。这可能包括所有已编写程序的95%。

但是,95%的程序也不会从-ffast-math中受益,因为它们没有做足够的浮点数学运算,因此它是重要的。

是的,只要您知道自己在做什么,它们可以安全地使用。这意味着你明白它们代表的是大小,而不是精确的值。这意味着:

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

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