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