ポインターで派生型の配列を使用する場合のfortranのメモリ使用量


13

このサンプルプログラムでは、2つの異なる方法で同じことを(少なくともそう思います)しています。Linux PCでこれを実行し、topでメモリ使用量を監視しています。gfortranを使用すると、最初の方法(「1」と「2」の間)で使用されるメモリは8.2GBであり、2番目の方法(「2」と「3」の間)でメモリ使用量は3.0GBです。Intelコンパイラーでは、違いはさらに大きくなります:10GB対3GB。これは、ポインターを使用することに対する過度のペナルティのようです。なぜこれが起こるのですか?

program test
implicit none

  type nodesType
    integer:: nnodes
    integer,dimension(:),pointer:: nodes 
  end type nodesType

  type nodesType2
    integer:: nnodes
    integer,dimension(4):: nodes 
  end type nodesType2

  type(nodesType),dimension(:),allocatable:: FaceList
  type(nodesType2),dimension(:),allocatable:: FaceList2

  integer:: n,i

  n = 100000000

  print *, '1'
  read(*,*)
  allocate(FaceList(n))
  do i=1,n
    FaceList(i)%nnodes = 4
    allocate(FaceList(i)%nodes(4))
    FaceList(i)%nodes(1:4) = (/1,2,3,4/)
  end do
  print *, '2'
  read(*,*)

  do i=1,n
    deallocate(FaceList(i)%nodes)
  end do
  deallocate(FaceList)

  allocate(FaceList2(n))
  do i=1,n
    FaceList2(i)%nnodes = 4
    FaceList2(i)%nodes(1:4) = (/1,2,3,4/)
  end do
  print *, '3'
  read(*,*)

end program test

背景はローカルグリッドの改良です。リンクリストを選択して、顔を簡単に追加および削除しました。ノードの数はデフォルトで4ですが、ローカルの改良に応じてより多くなる可能性があります。


1
「最初の方法」は、パフォーマンスの違いに加えて、リークが発生する可能性があるため(アレイは明示的に割り当てを解除する必要があります)、できるだけ避ける必要があります。それを使用する唯一の理由は、Fortran 95を厳守するためです。派生型のallocatableはTR 15581に追加されましたが、すべてのFortranコンパイラ(2003の機能を持たないものも含む)は、F95 + TR15581 + TR15580を永久にサポートしています。
stali

1
これを行う理由は、一部の面に4つ以上のノードがある可能性があるためです。
クリス

それは確かに理にかなっています。4は定数であると仮定しました。
stali

回答:


6

実際、Fortranコンパイラがどのように機能するかはわかりませんが、言語機能に基づいて推測することができます。

fortranの動的配列には、形状、サイズ、lbound、ubound、割り当て済みまたは関連付けられた(割り当て可能vsポインター)などの組み込み関数で動作するメタデータが付属しています。大きな配列の場合、メタデータのサイズはごくわずかですが、ごく小さな配列の場合は、合計することができます。あなたの場合、サイズ4の動的配列には実際のデータよりも多くのメタデータが含まれている可能性が高いため、メモリ使用量の増加につながります。

構造の下部にある動的メモリには強くお勧めします。いくつかの次元の物理システムを扱うコードを書いている場合、マクロとして設定して再コンパイルできます。グラフを扱う場合は、エッジの数などに上限を静的に割り当てることができます。実際にきめ細かい動的メモリ制御を必要とするシステムを扱っている場合は、おそらくCに切り替えるのが最善です。


ええ、メタデータの引数は両方の場合に当てはまりませんか?
stali

@staliいいえ、n最初のメソッドで必要なポインターとは対照的に、2番目のケースでは1つのポインターが必要です。
アロンアフマディア

背景情報を追加しました。静的に上限を割り当てるという提案は、すでに優れた改善です。上限は8ですが、大半は4を持っていますメモリがまだ...無駄になっているので、わずかな割合は、5,6,7または8を持っています
クリス・

@chris:4つのノードと8つのノードの2つのリストを作成できますか?
ペドロ

多分。それは良い妥協のようです。
クリス

5

以下のようmaxhutchが指摘している、問題はおそらく別のメモリ割り当ての膨大な数です。ただし、メタデータの上に、おそらくメモリマネージャーが必要とする追加データとアライメントがあります。つまり、各割り当てを64バイト以上の倍数に切り上げている可能性があります。

各ノードに小さなチャンクが割り当てられないようにするには、事前に割り当てられた配列の一部を各ノードに割り当ててみてください。

integer :: finger
indeger, dimension(8*n) :: theNodes

finger = 1
do i=1,n
    FaceList(i)%nodes => theNodes(finger:finger+FaceList(i)%nnodes-1)
    finger = finger + FaceList(i)%nnodes
end do

私のFortranは少し錆びていますが、原理的にはそうでなければ、上記は動作するはずです。

FortranコンパイラーがPOINTER型に格納する必要があると考えるもののオーバーヘッドはまだありますが、メモリーマネージャーのオーバーヘッドはありません。


これは役立ちますが、ほんの少しです。問題は、単一のポインターではなく、ポインターの動的配列であるということです:FaceList(i)%nodes(1:FaceList(i)%nnodes)=> theNodes(finger:finger + FaceList(i)%nnodes-1)。また、事前に割り当てられた配列のサイズの鋭い見積もりを意味します。
クリス

@クリス:私は完全に理解しているかわからない... "ポインタの動的配列"とはどういう意味ですか?このフィールドnodesType%nodesは、動的配列へのポインターです。
ペドロ

0

ああ。これは私が被った同じ問題です。この質問は非常に古いものですが、少し異なるコードスタイルをお勧めします。私の問題は、次のコードのように、派生データ型の割り当て可能なステートメント配列でした。

type :: node
  real*4,dimension(:),allocatable :: var4
  real*8,dimension(:),allocatable :: var8
end type node

type(node),dimension(:),allocatable :: nds

imax = 5000
allocate(nds(imax))

いくつかのテストから、派生型で割り当て可能なステートメントまたはポインターステートメントを次の4つのケースのコードとして使用すると、メモリリークが非常に大きくなることを確認しました。私の場合、520MBサイズのファイルを赤にします。しかし、Intel fortranコンパイラのリリースモードでのメモリ使用量は4GBでした。それは8倍です!

!(case 1) real*4,dimension(:),allocatable :: var4
!(case 2) real*4,dimension(:),pointer :: var4
!(case 3) real*4,allocatable :: var4(:,:)

!(case 4) 
type :: node(k)
  integer,len :: k = 4
  real*4 :: var4(k)
end type node

派生型なしで割り当て可能またはポインターステートメントを使用すると、メモリリークは発生しません。私の意見では、派生型で割り当て可能またはポインタ型変数を宣言し、派生型で割り当て可能な変数ではなく派生型変数を大きく割り当てると、メモリリークが発生します。この問題を解決するために、派生型を含まないコードを次のコードに変更しました。

real*4,dimension(:,:),allocatable :: var4 
! array index = (Num. of Nodes, Num. of Variables)

このスタイルはどうですか?

integer,dimension(:),allocatable :: NumNodes ! (:)=Num. of Cell or Face or etc.
integer,dimension(:),allocatable :: Node     ! (:)=(Sum(NumNodes))

NumNodes変数は各面のノードの数を意味し、Node変数はNumNodes変数に一致するノード番号です。おそらく、このコードスタイルではメモリリークは発生しないと思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.