最終編集:
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 リポジトリをまとめました。
概要
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
。もう 1 つの値は address にあります00000003071E05A0
。
IIUC では、relocmon.inc でCOMMON /DDMON/
次元配列を含むように設定しているznzndaav
ため、ソフトウェアが正常に機能していた場合、問題の値のアドレスは/DDMON/
ブロック内にあります。のアドレス範囲は/DDMON/
ですz'000000027FFF6040' - z'0000000307421150'
。私のロジックが正しい場合、違反はこのブロックの外で発生します。
00000002071E05A0
プログラムがを使用すべきときにに書き込もうとしているように私には思え00000003071E05A0
ます。
なぜこれが当てはまるのかを判断するのを手伝ってくれる人はいますか? それには何か体系的なものがあるように見えます - それは単なる偶然でしょうか?