我有一个Fortran程序,它在32位系统中使用-O0
和-O1
提供不同的结果。追踪差异,我想出了以下测试用例(test.f90
):
program test
implicit none
character foo
real*8 :: Fact,Final,Zeta,rKappa,Rnxyz,Zeta2
read(5,*) rKappa
read(5,*) Zeta
backspace(5)
read(5,*) Zeta2
read(5,*) Rnxyz
Fact=rKappa/Sqrt(Zeta**3)
write(6,'(ES50.40)') Fact*Rnxyz
Fact=rKappa/Sqrt(Zeta2**3)
Final = Fact*Rnxyz
write(6,'(ES50.40)') Final
end program test
使用此data
文件:
4.1838698196228139E-013
20.148674000000000
-0.15444754236171612
程序应该写出完全相同的数字。请注意Zeta2
与Zeta
相同,因为再次读取相同的数字(这是为了防止编译器意识到它们是相同的数字并隐藏问题)。唯一的区别是,首先完成一项操作"在飞行中"写入时,然后将结果保存在变量中并打印变量。
现在我用gfortran 4.8.4(Ubuntu 14.04版本)编译并运行它:
$ gfortran -O0 -m32 test.f90 && ./a.out < data
-7.1447898573566615177997578153994664188136E-16
-7.1447898573566615177997578153994664188136E-16
$ gfortran -O1 -m32 test.f90 && ./a.out < data
-7.1447898573566615177997578153994664188136E-16
-7.1447898573566605317236262891347096541529E-16
因此,-O0
数字相同,-O1
数字不相同。
我尝试使用-fdump-tree-optimized
检查优化代码:
final.10_53 = fact_44 * rnxyz.9_52;
D.1835 = final.10_53;
_gfortran_transfer_real_write (&dt_parm.5, &D.1835, 8);
[...]
final.10_63 = rnxyz.9_52 * fact_62;
final = final.10_63;
[...]
_gfortran_transfer_real_write (&dt_parm.6, &final, 8);
我看到的唯一区别是,在一种情况下,打印的数字是fact*rnxyz
,而在另一种情况下,它是rnxyz*fact
。这会改变结果吗?从高性能Mark的答案来看,我想这可能与哪个变量何时进入哪个寄存器有关。我也尝试查看使用-S
生成的汇编输出,但我不能说我理解它。
然后,没有-m32
标志(在64位机器上),数字也相同......
修改:如果我添加-ffloat-store
或-mfpmath=sse -sse2
(最后请参阅here),则数字相同。我想,当我在i686机器上编译时,这是有道理的,因为编译器默认使用387数学。但是当我使用-m32
在x86-64机器上编译时,根据文档不应该需要它:
-mfpmath = sse [...]
对于i386编译器,您必须使用
-march=cpu-type
,-msse
或-msse2
开关来启用SSE扩展并使此选项生效。 对于x86-64编译器,默认情况下会启用这些扩展。[...]
这是x86-64编译器的默认选择。
也许-m32
会使这些&#34;默认&#34;无效的?但是,运行gfortran -Q --help=target
表示mfpmath为387且msse2已禁用...
答案 0 :(得分:1)
评论太长了,但更多的是怀疑而非答案。 OP写道
唯一的区别是首先完成一项操作&#34;在飞行中&#34; 写作时,然后将结果保存在变量和 变量被打印出来。
让我思考x86_64架构的内部80位f-p算法。当中间值从80位调整到64位时,f-p算术运算序列的精确结果将受到影响。这就是从一个编译器优化级别到另一个编译器优化级别可能不同的东西。
另请注意,由O1版本的代码打印的两个数字之间的差异在第15个十进制数字处开始,大约是64位f-p算法中可用的精度限制。
一些摆弄的东西给出了
1 01111001100 1001101111011110011111001110101101101100011000001110
作为
的IEEE-754表示-7.1447898573566615177997578153994664188136E-16
和
1 01111001100 1001101111011110011111001110101101101100011000001101
作为
的IEEE-754表示-7.1447898573566605317236262891347096541529E-16
这两个数字的有效位置相差1
。可能在O0
你的编译器遵守IEEE-754 fp算法规则(这些规则对于低阶位舍入等事项是严格的)但O1
仅适用于def example(a, b, c=None, r="w" , d=[], *ae, **ab):
对Fortran来说,算术更放松了。 (Fortran标准不要求使用IEEE-754算法。)
您可以找到一个编译器选项,以在更高的优化级别强制遵守IEEE-754规则。您可能还会发现,这种依从性会花费您可测量的运行时间。