我想编写跨平台的C/C++,它在不同的环境中具有可重现的行为。
我知道 gcc 的 ffast-math 支持各种浮点近似。这很好,但我需要两个单独编译的二进制文件来产生相同的结果。
假设我总是使用 gcc,但对于 Windows、Linux 或其他什么,以及不同的编译器版本,我使用不同的 gcc。
是否可以保证这些编译将为相同的源代码生成相同的浮点近似集?
不,并不是说它们允许特定的近似值,而是-ffast-math
允许编译器假设 FP 数学是关联的,而事实并非如此。 即在转换代码时忽略舍入错误以允许更高效的 ASM。
操作顺序选择的任何微小差异都可能通过引入不同的舍入来影响结果。
较旧的编译器版本可能会选择x * approx_rsqrt(x)
-ffast-math
的牛顿-拉夫森迭代来实现sqrt(x)
,因为较旧的CPU具有较慢的sqrtps
指令,因此通常值得将其替换为倒数sqrt + 3或4的近似乘法并添加指令。 在最新 CPU 的大多数代码中通常不是这种情况,因此即使您使用相同的优化选项(尤其是默认-mtune=generic
而不是-mtune=haswell
,该选项所做的选择也可能在 GCC 版本之间发生变化。
没有-ffast-math
就很难获得确定性FP;不同操作系统上的不同库具有不同的函数实现,如sin
和log
(与基本操作不同 + - */sqrt 不需要返回"正确舍入"的结果,.max即错误 0.5ulp)。
临时(FLT_EVAL_METHOD
)的额外精度可能会改变结果,如果你使用x87 FP数学编译32位x86。 (-mfpmath=387
是-m32
的默认值)。 如果你想在这里有任何希望,你会想要避免32位x86。 或者如果你被它困住了,也许你可以侥幸逃脱-msse2 -mfpmath=sse
......
你提到了Windows,所以我假设你只是在谈论x86 GNU/Linux,即使Linux在许多其他ISA上运行。
但即使在 x86 中,使用-march=haswell
进行编译也可以使用 FMA 指令,而 GCC 默认为#pragma STDC FP_CONTRACT ON
(即使跨 C 语句,超出了通常的 ISO C 规则允许的范围)。 所以实际上即使没有-ffast-math
,FMA 可用性也可以删除x*y + z
中临时x*y
的舍入。
-ffast-math
:
一个版本的 gcc可能会决定将循环展开 2(并使用 2 个单独的累加器),当对数组求和时,而具有相同选项的旧版本的 gcc 可能仍按顺序求和。
(实际上当前的 gcc 在这方面很糟糕,当它展开时(不是默认的),它通常仍然使用相同的(矢量)累加器,因此它不会像 clang 那样隐藏 FP 延迟。 例如,https://godbolt.org/z/X6DTxK 对同一变量使用不同的寄存器,但它仍然只是一个累加器,在 sum 循环之后没有垂直加法。 但希望未来的 gcc 版本会更好。gcc 版本之间在如何执行 YMM 或 XMM 寄存器的水平和方面的差异可能会在自动矢量化时引入差异)