最终编辑:
silverfrost 论坛上的一些用户非常有帮助地指导我简化代码和解决方案。
可以使用以下代码复制该问题:
PROGRAM ML14ERROR
INTEGER :: origzn, destzn
INTEGER,PARAMETER :: MXZMA = 1713, LXTZN = 1714, MXAV = 182
INTEGER,PARAMETER :: JTMPREL = 1003, av = 1
REAL(KIND=2) :: RANDOM@
REAL,dimension (1:mxav,lxtzn,lxtzn,JTMPREL:JTMPREL):: znzndaav
DO origzn=1,lxtzn
DO destzn=1,lxtzn
znzndaav(av,origzn,destzn,JTMPREL) = RANDOM@()
END DO
END DO
DO origzn=1,mxzma
DO destzn=1,mxzma
! This is where the error occurs
znzndaav(av,origzn,lxtzn,JTMPREL)=
$ znzndaav(av,origzn,lxtzn,JTMPREL)+
$ znzndaav(av,origzn,destzn,JTMPREL)
ENDDO
ENDDO
WRITE(6,*)'No errors'
END PROGRAM
该问题仅在MXAV
>182 时出现,这表明存在内存问题。实际上,将尺寸相乘:183 * 1714 * 1714 * 4 会产生 >2GB,超过堆栈大小。
一种解决方案是按如下方式使用堆(Fortan 95):
PROGRAM ML14ERROR
INTEGER :: origzn, destzn
INTEGER,PARAMETER :: MXZMA = 1713, LXTZN = 1714, MXAV = 191
INTEGER,PARAMETER :: JTMPREL = 1003, av = 1
REAL(KIND=2) :: RANDOM@
REAL,allocatable :: znzndaav(:,:,:,:)
ALLOCATE( znzndaav(1:mxav,lxtzn,lxtzn,JTMPREL:JTMPREL) )
DO origzn=1,lxtzn
DO destzn=1,lxtzn
znzndaav(av,origzn,destzn,JTMPREL) = RANDOM@()
END DO
END DO
DO origzn=1,mxzma
DO destzn=1,mxzma
! This is where the error occurs
znzndaav(av,origzn,lxtzn,JTMPREL)= &
& znzndaav(av,origzn,lxtzn,JTMPREL)+ &
& znzndaav(av,origzn,destzn,JTMPREL)
ENDDO
ENDDO
DEALLOCATE(znzndaav)
WRITE(6,*)'No errors'
END PROGRAM
完成此操作后,我们可以分配超过 2GB 的空间,并且数组可以正常工作。这段代码源自的程序已经有几年历史了,我们现在才遇到这个问题,因为我们构建的模型比以前大很多倍。由于 Fortran 77 不允许 ALLOCATABLE 数组,我们必须要么减少堆栈使用,要么移植代码 - 或寻求另一种优化。
编辑添加:
我现在整理了一个包含可重现代码的 git repo。
概述
我有一个程序在编译为 32 位时运行良好,但在编译并以 64 位运行时出现访问冲突错误。
我正在使用 Silverfrost Fortran 编译器 FTN95 v8.51,尽管使用 v8.40 和 v8.50 会出现此问题。
示例代码
! .\relocmon.inc
INTEGER JTMPREL
PARAMETER(JTMPREL=1003)
REAL znda(lxtzn,JTMPREL:JTMPREL)
REAL zndaav(1:mxav,lxtzn,JTMPREL:JTMPREL)
REAL,dimension (lxtzn,lxtzn,JTMPREL:JTMPREL) :: znznda
REAL mlrlsum(lxtzn,lxtzn)
REAL,dimension (1:mxav,lxtzn,lxtzn,JTMPREL:JTMPREL):: znzndaav
COMMON /DDMON/ znda, znznda, mlrlsum,znzndaav, zndaav
! EOF .\relocmon.inc
! .\relocmon.inc with values
INTEGER JTMPREL
PARAMETER(JTMPREL=1003)
REAL znda(1714,JTMPREL:JTMPREL)
REAL zndaav(1:191,1714,JTMPREL:JTMPREL)
REAL,dimension (1714,1714,JTMPREL:JTMPREL) :: znznda
REAL mlrlsum(1714,1714)
REAL,dimension (1:191,1714,1714,JTMPREL:JTMPREL):: znzndaav
COMMON /DDMON/ znda, znznda, mlrlsum,znzndaav, zndaav
! EOF .\relocmon.inc
! .\main.for
INCLUDE 'relocmon.inc'
REAL,save,dimension(lxtzn,lxtzn,mxav) :: ddfuncval
DO origzn=1,mxzma
IF( zonedef(origzn,JZUSE) )THEN
DO destzn=1,mxzma
IF (zonedef(destzn,JZUSE)) THEN
znznda(origzn,destzn,JTMPREL)=znda(destzn,JTMPREL)*
$ ddfuncval(origzn,destzn,av)
znznda(origzn,lxtzn,JTMPREL)=znznda(origzn,lxtzn,JTMPREL)
$ +znznda(origzn,destzn,JTMPREL)
znzndaav(av,origzn,destzn,JTMPREL)=zndaav(av,destzn,JTMPREL)*
$ ddfuncval(origzn,destzn,av)
! LINE 309 -- where error occurs
znzndaav(av,origzn,lxtzn,JTMPREL)=
$ znzndaav(av,origzn,lxtzn,JTMPREL)
$ +znzndaav(av,origzn,destzn,JTMPREL)
ENDIF
ENDDO
ENDIF
ENDDO
! EOF .\main.for
注意该函数zonedef
只是检查一个区域对于我们想要进行的计算是否有效。此函数返回一个logical
.
调试
正如我最初提到的,这个程序的 32 位编译版本运行良好。尝试运行 64 位版本时,第一个循环的输出如下:
来自 sdbg64.exe:
Error: Access Violation reading address
0x00000002071E05A0
main.for: 309
将异常写入文件:
Access violation (c0000005) at address 43a1f4
Within file ml14.exe
in main in line 309, at address 2b84
RAX = 0000000000000001 RBX = 000000027fff704c RCX = 000000000285e6b8 RDX = 00000002802296cc
RBP = 0000000000400000 RSI = 000000029ba3ad6c RDI = 0000000307695374 RSP = 000000000285be70
R8 = 0000000307695374 R9 = 00000002ffff5040 R10 = 000000029ba3ad6c R11 = 000000030731f0dc
R12 = 000000027fff5584 R13 = 00000002802296cc R14 = 000000028169f3ec R15 = 0000000281660928
43a1f4) addss XMM11,[85b401b4++R14]
剩下的……请多多包涵。无论如何,我都不是受过训练的软件工程师或 fortran 开发人员,所以我在黑暗中刺了一下以进行故障排除。
的值为ZNZNDAAV(1,337,337,1003)
2.241640,并且正在添加到ZNZNDAAV(1,337,1714,1003)
. 这与异常输出中详述的寄存器 XMM11 相符。该值位于 address 000000029BA3BD60
。另一个值在 address 00000003071E05A0
。
IIUC,在 relocmon.inc 中,我们设置COMMON /DDMON/
为包含维度数组znzndaav
,因此如果软件名义上工作,则相关值的地址将在/DDMON/
块内。的地址范围/DDMON/
是z'000000027FFF6040' - z'0000000307421150'
。如果我的逻辑是正确的,则违规发生在此块之外。
在我看来,该程序正在尝试写入00000002071E05A0
它应该使用的时间00000003071E05A0
。
谁能帮我确定为什么会这样?似乎有一些系统性的东西——这仅仅是巧合吗?