我想编写跨平台的C / C ++,它在不同的环境中具有可重现的行为。
我知道gcc的ffast-math可以实现各种浮点近似。这很好,但我需要两个单独编译的二进制文件来产生相同的结果。
假设我总是使用gcc,但不同的是用于Windows,Linux或其他任何版本,以及不同的编译器版本。
是否可以保证这些编译将为相同的源代码产生相同的浮点近似值?
不,并不是说它们允许特定的近似值,而是-ffast-math
允许编译器假设FP数学是不相关的。即,在转换代码时忽略舍入误差以允许更高效的asm。
选择操作顺序的任何微小差异都会通过引入不同的舍入来影响结果。
较旧的编译器版本可能会选择将sqrt(x)
实现为x * approx_rsqrt(x)
,并为-ffast-math
使用Newton-Raphson迭代,因为较旧的CPU具有较慢的sqrtps
指令,因此通常用相似的sqal + 3或4的近似值替换它更为合适。乘以并添加指令。对于最近的CPU,大多数代码通常不是这种情况,因此即使您使用相同的调整选项(例如默认的-mtune=generic
而不是-mtune=haswell
),选项所做的选择也可以在GCC版本之间进行更改。
没有-ffast-math
就很难获得确定性的FP;不同操作系统上的不同库具有不同的函数实现,如sin
和log
(与基本操作+ - * / sqrt不同,不需要返回“正确舍入”的结果,即最大错误0.5ulp)。
如果使用x87 FP数学编译32位x86,临时(FLT_EVAL_METHOD
)的额外精度可能会改变结果。 (-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
的舍入暂时在x*y + z
。
-ffast-math
:一个版本的gcc可能决定将循环展开2(并使用2个单独的累加器),当对数组求和时,而具有相同选项的旧版gcc可能仍然按顺序求和。
(实际上当前的gcc很糟糕,当它展开时(默认情况下)它通常仍然使用相同的(向量)累加器,因此它不会像clang那样隐藏FP延迟。例如,https://godbolt.org/z/X6DTxK对同一个变量使用不同的寄存器但它仍然只是一个累加器,在求和循环之后没有垂直加法。但希望未来的gcc版本会更好。而且gcc版本之间的区别在于它们如何处理YMM或XMM寄存器的水平和可能会在自动时引入差异矢量化)