【发布时间】:2017-02-17 21:51:21
【问题描述】:
我有一个 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。这能改变结果吗?根据高性能标记的回答,我想这可能与哪个变量何时进入哪个寄存器有关。我还尝试查看使用-S 生成的程序集输出,但我不能说我理解它。
然后,没有-m32 标志(在 64 位机器上),数字也是相同的......
编辑:如果我添加-ffloat-store 或-mfpmath=sse -sse2,数字是相同的(请参阅最后的here)。我想,当我在 i686 机器上编译时,这是有道理的,因为编译器默认使用 387 数学。但是当我在 x86-64 机器上编译时,使用-m32,根据文档,它不应该是必需的:
-mfpmath=sse [...]
对于 i386 编译器,您必须使用
-march=cpu-type、-msse或-msse2开关来启用 SSE 扩展并使此选项生效。 对于 x86-64 编译器,默认启用这些扩展。[...]
这是 x86-64 编译器的默认选择。
也许-m32 使这些“默认值”无效?但是,运行 gfortran -Q --help=target 表示 mfpmath 为 387 并且 msse2 已禁用...
【问题讨论】:
-
检查汇编生成的代码。 O1 可能会对您发布的代码进行一些优化。您可能对stackoverflow.com/questions/19618679/… 感兴趣。
-
@Harald 我用
-S检查了输出,但这开始太模糊了......无论如何,我也看不出有任何区别。我正在用(AFAICT)相关部分更新问题。 -
@Harald 我在测试中犯了一个错误,所以诊断结果有点不同。似乎因素的顺序发生了变化。
标签: gcc optimization floating-point fortran gfortran