我正在研究一个问题,即使使用编译选项 -fno-fast-math 和 -fno-unsafe-math-optimizations,gfortran 在极少数情况下也会根据优化级别返回不同的结果,因此结果不应依赖于优化级别。我使用 gfortran-11.2.1、gfortran-11.4.1(均为 RedHat)和 gfortran-14.2(godbolt)对此进行了测试。为了获得可重现的结果,我还需要使用 OpenLibM 数学库而不是 gcc 数学库,因此我将继续讨论 godbolt 反汇编。
结果差异来自计算 y = cos(atan(x)),对于 -O1 或更高版本,它被替换为 y = 1/sqrt(1+x^2):编译
double precision function foo(x)
implicit none
double precision, intent(in) :: x
foo = cos(atan(x))
end function foo
使用
gfortran -c file.f90 -O1
,请参阅https://godbolt.org/z/c9b4rvhdW并检查创建的代码:没有调用cos或atan,而是使用sqrt。如果使用 -O0
,则使用 atan 和 cos 函数。
从文档中我希望
-fno-fast-math
或 -fno-unsafe-math-optimizations
应该禁用此优化,但看起来这些对 gfortran 没有影响。只有 -fno-tree-forwprop
禁用此优化,以便返回与 -O0 相同的结果。
对于 gcc C 编译器的行为符合预期:对于相同代码的 C 变体 (https://godbolt.org/z/E7Mzh6Taq),此优化仅由编译器在指定
-ffast-math
时完成,但默认情况下不这样做 -O1
。
使用
-O0
或 '-fno-tree-forwprop` 是禁用这些数学优化的唯一选项吗?我希望确保所有优化级别都获得相同的结果,但保持尽可能好的性能。
准确性不重要是一件好事。 在区间 [0:137] 中测试 1000 万个
x
值表明,如果阻止优化,则 8984174 (89.84%) 的 cos(atan(x))
结果的 ULP 超过 2 ULP。 观察到的最大 ULP 为 129.42,相当于 5 个错误的尾随数字。 如果计数阈值增加到 100 ULP,则会发现 144035 (1.44%) 个值超过 100 ULP。
经过优化,只有 26 个结果超过 2。
如上所述,无需使用编译器即可阻止优化。
foo()
可以写成
double precision function foo(x)
implicit none
double precision, intent(in) :: x
double precision, volatile :: tmp
tmp = atan(x)
foo = cos(tmp)
end function foo
volatile
属性告诉编译器,在计算 tmp
和在 atan(x)
中引用 tmp
之间,cos(tmp)
的值可能会通过编译器无法控制的某种方式发生变化。 编译器将从内存中重新加载 tmp。