使用带指针的派生类型数组时,fortran中的内存使用情况


13

在此示例程序中,我以两种不同的方式做同样的事情(至少我是这样认为的)。我在Linux PC上运行此程序,并通过top监视内存使用情况。使用gfortran,我发现第一种方式(“ 1”和“ 2”之间)使用的内存为8.2GB,而第二种方式(“ 2”和“ 3”之间)使用的内存为3.0GB。使用英特尔编译器,两者之间的差异更大: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
除了看到的性能差异外,还应尽可能避免“第一种方法”,因为它容易发生泄漏(必须像您那样显式地释放数组)。使用它的唯一原因是严格遵守Fortran95。TR15581中添加了可派生类型的派生类型,但是所有Fortran编译器(即使是没有2003功能的编译器)也一直支持它们,即F95 + TR15581 + TR15580至今。 。
stali 2012年

1
这样做的原因是某些面可能具有4个以上的节点。
克里斯

那当然是有道理的。我认为4是常数。
stali 2012年

Answers:


6

我实际上并不知道fortran编译器如何工作,但是基于语言功能,我可以猜测一下。

fortran中的动态数组带有元数据,可与诸如形状,大小,lbound,ubound,已分配或关联(可分配与指针)之类的内在函数一起使用。对于大型阵列,元数据的大小可以忽略不计,但是对于小型阵列(如您的情况),它可以累加。在您的情况下,大小为4的动态数组可能具有比真实数据更多的元数据,这导致您的内存使用迅速增加。

我强烈建议您不要在结构的底部使用动态内存。如果您要编写处理一定数量维度的物理系统的代码,则可以将其设置为宏并重新编译。如果要处理图形,则可以静态分配边数等的上限。如果您要处理的系统实际上需要细粒度的动态内存控制,那么最好切换到C。


是的,但是元数据参数在两种情况下都不成立吗?
stali 2012年

@stali否,请注意,第二种情况需要一个指针,而不是n第一种方法需要的指针。
阿隆·艾玛迪亚

我添加了一些背景信息。您关于静态分配上限的建议已经是一个很好的改进。上限是8,但是大多数将有4,只有一小部分将有5、6、7或8。因此,内存仍然被浪费了……
chris 2012年

@chris:能否列出两个列表,一个包含4个节点,一个包含8个节点?
2012年

大概。这似乎是一个很好的妥协。
克里斯,2012年

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)。它还暗示着对预分配阵列大小的精确估计。
克里斯,2012年

@chris:我不确定我是否完全理解...“动态指针数组”是什么意思?该字段nodesType%nodes是指向动态数组的指针。
2012年

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))

通过一些测试,我确认如果我在派生类型中使用可分配语句或指针语句作为以下四种情况的跟踪代码,则会发生很大的内存泄漏。就我而言,我将文件大小设为520MB。但是在intel fortran编译器上,释放模式下的内存使用量为4GB。那是大八倍!

!(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变量匹配的节点号。我认为这种代码风格可能不会发生内存泄漏。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.