我想编写跨平台的C / C ++,它在不同环境中的行为都可重现。
我了解gcc的ffast-math可以实现各种浮点近似。很好,但是我需要两个单独编译的二进制文件才能产生相同的结果。
假设我一直使用gcc,但对于Windows,Linux或其他任何版本以及不同的编译器版本,我都会使用gcc。
是否可以保证这些编译对于相同的源代码产生相同的浮点近似值集?
答案 0 :(得分:3)
否,不是它们允许特定的近似值,而是-ffast-math
允许编译器假定FP数学是关联的,而不是。即在转换代码以允许更有效的asm时忽略舍入错误。
通过选择不同的舍入,操作顺序选择上的任何细微差异都会影响结果。
较旧的编译器版本可能选择将sqrt(x)
实现为x * approx_rsqrt(x)
,并为-ffast-math
进行Newton-Raphson迭代,因为较旧的CPU的sqrtps
指令较慢,因此指令更多通常值得用近似的倒数+ 3或4个更多的乘法和加法指令代替它。对于大多数最新的CPU,通常不是这种情况,因此即使您使用相同的调整选项(尤其是默认的-mtune=generic
而不是-mtune=haswell
),则该选项所做的选择可以在GCC版本之间进行更改。
要获得确定性FP 而没有 -ffast-math
很难;不同操作系统上的不同库具有不同功能的实现,例如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对此很糟糕,当它展开(默认情况下不是默认状态)时,它通常仍使用相同的(向量)累加器,因此不会像lang那样隐藏FP延迟。例如https://godbolt.org/z/X6DTxK对同一变量使用不同的寄存器,但它仍然只是一个累加器,求和循环后没有垂直加法运算,但希望将来的gcc版本会更好,而且gcc版本之间在如何对YMM或XMM寄存器进行水平求和方面的差异自动向量化时可能会在其中引入差异