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

计算科学 表现 正则
2021-12-07 01:40:06

在这个示例程序中,我以两种不同的方式做同样的事情(至少我是这么认为的)。我在我的 Linux 电脑上运行它并使用 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,但可能会根据局部细化而​​变得更高。

3个回答

我实际上并不知道 fortran 编译器是如何工作的,但根据语言特性,我可以猜测一下。

fortran 中的动态数组带有元数据,可与形状、大小、lbound、ubound 和已分配或关联(可分配与指针)等内在函数一起使用。对于大型数组,元数据的大小可以忽略不计,但对于小型数组,就像您的情况一样,它可以加起来。在您的情况下,大小为 4 的动态数组可能具有比实际数据更多的元数据,这会导致您的内存使用量激增。

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

正如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 类型存储的任何开销,但您不会有内存管理器开销。

哦。这是我遇到的同样的问题。这个问题很老了,但我建议有点不同的代码风格。我的问题是派生数据类型中的可分配语句数组,如下代码。

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。那是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 变量的节点数。我认为,这种代码风格可能不会发生内存泄漏。