Fortran内存分配不会产生错误,但程序在初始化

时间:2015-06-12 13:08:43

标签: memory-management fortran

鉴于下面提供的最小工作示例,您知道为什么在内存分配步骤中不会发生内存分配错误吗?正如我检查的那样,当我使用valgrind来运行代码,或者将参数source = 0.0添加到内存分配语句时,我就像预期的那样,有内存分配错误。

更新:我用最小的工作示例重现了这个问题:

 program memory_test

  implicit none

  double precision, dimension(:,:,:,:), allocatable :: sensitivity
  double precision, allocatable :: sa(:)
  double precision, allocatable :: sa2(:)

  integer :: ierr,nnz
  integer :: nx,ny,nz,ndata

  nx = 50
  ny = 50
  nz = 100
  ndata = 1600

  allocate(sensitivity(nx,ny,nz,ndata),stat=ierr)

  sensitivity = 1.0

  nnz = 100000000

  !allocate(sa(nnz),source=dble(0.0),stat=ierr)
  allocate(sa(nnz),stat=ierr)
  if(ierr /= 0) print*, 'Memory error!'

  !allocate(sa2(nnz),source=dble(0.0),stat=ierr)
  allocate(sa2(nnz),stat=ierr)
  if(ierr /= 0) print*, 'Memory error!'

  print*, 'Start initialization'

  sa = 0.0
  sa2 = 0.0

  print*, 'End initialization'

end program memory_test

当我运行它时,我没有消息'内存错误!'打印,但有消息'开始初始化'然后该程序被操作系统杀死。如果我使用内容分配' source'参数(在代码中注释)只有我有消息'内存错误!'。

对于内存统计,免费提供'命令给我这个输出:

             total       used       free     shared    buffers     cached
Mem:       8169952    3630284    4539668      46240       1684     124888
-/+ buffers/cache:    3503712    4666240
Swap:            0          0          0

3 个答案:

答案 0 :(得分:6)

您正在看到Linux使用的内存分配策略的行为。当您分配内存但尚未写入内存时,它仅包含在虚拟内存中(请注意,这也可能受特定Fortran运行时库的影响,但我不确定)。此内存存在于进程虚拟地址空间中,但不受任何实际物理内存页面的支持。只有当您写入内存时,才会分配物理页面,并且只能满足写入要求。

考虑以下计划:

program test
   implicit none
   real,allocatable :: array(:) 

   allocate(array(1000000000)) !4 gb array

   print *,'Check memory - 4 GB allocated'
   read *

   array(1:1000000) = 1.0

   print *,'Check memory - 4 MB assigned'
   read *

   array(1000000:100000000) = 2.0

   print *,'Check memory - 400 MB assigned'
   read *

   array = 5.0

   print *,'Check memory - 4 GB assigned'
   read *

end program

此程序分配4 GB内存然后写入4 MB阵列部分,396 MB阵列部分(总写入数= 400 MB),最后写入完整阵列(总写入数= 4 GB)。程序在每次写入之间暂停,以便您可以查看内存使用情况。

在分配之后,在第一次写入之前:

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                           
29192 casey     20   0 3921188   1176   1052 S   0.0  0.0   0:00.00 fortranalloc

所有内存都是虚拟内存(VIRT),只有一小部分内存由物理内存(RES)支持。

4 MB写完后:

29192 casey     20   0 3921188   5992   1984 S   0.0  0.0   0:00.00 fortranalloc
在396 MB写完之后

29192 casey     20   0 3921188 392752   1984 S   0.0  1.6   0:00.18 fortranalloc

并在4 GB写完后:

29192 casey     20   0 3921188 3.727g   1984 S  56.6 15.8   0:01.88 fortranalloc 

请注意,每次写入后,驻留内存会增加以满足写入要求。这表明实际的物理内存分配仅在写入时发生,而不仅仅在分配时发生,因此正常allocate()无法检测错误。将source参数添加到allocate时,会发生写入,这会导致内存的完全物理分配,如果此操作失败,则可以检测到错误。

您可能会看到的是当内存耗尽时调用的linux OOM Killer。当发生这种情况时,OOM Killer将使用算法来确定要释放内存的内容,并且代码的行为使其很可能被杀死。当您的写入导致可以满足的物理分配时,您的进程将被内核杀死。你在写时看到它(由赋值引起)但由于上面详述的行为而没有分配。

答案 1 :(得分:4)

扩展评论而不是答案:

在Fortran初始化中有一个特定的含义;它指的是在声明时设置变量的值。所以这个

real :: myvar = 0.0

是初始化。而这些

real :: myvar
....
myvar = 0.0

不是。现在,或许与您报告的问题更相关,这句话

isensit%sa(:) = 0.0

将值0.0分配给数组部分的每个元素 isensit%sa(:)。这是非常(一旦你习惯了)与我认为你打算写的不同,这是:

isensit%sa = 0.0

此版本将值0.0分配给数组 isensit%sa的每个元素。因为数组部分,即使是包含数组的每个元素的数组部分,也不是数组,所以Fortran编译器可以在处理分配时临时为该部分分配空间。当你考虑更通用的数组部分时,这可能是有意义的。

我不确定我理解为什么你认为在allocate语句执行时没有分配空间但我建议你整理分配,然后重新思考。而且我想数组部分的临时空间分配(与数组本身消耗的空间一样多)可能会使程序超出边缘并导致您报告的行为。

顺便说一下,您可以尝试使用语句

allocate(isensit%sa(isensit%nnz),source=0.0,stat=ierr)

如果您的编译器是最新的,那么应该进行分配并在一个语句中设置数组中的值。

哦,这是一个完全无偿的评论:更喜欢use mpi(或use mpi_mod或者您的安装更喜欢include mpif.h。这将预防(许多)错误,这些错误可能是由于调用不匹配而引起的mpi例程及其要求。例程的使用关联意味着编译器可以检查参数匹配,但不包含头文件。

答案 2 :(得分:1)

以下是调用allocate()的三种方法的比较:

program mem_test
    implicit none
    integer, allocatable :: a(:,:,:)
    integer ierr, n1, n2, L, method

    n1 = 250000 ; n2 = 1000    !! 1-GB subarray

    print *, "Input: method, L"
    read *, method, L

    select case ( method )
    case ( 1 )
        allocate( a( n1, n2, L ) )              !! request L-GB virtual mem
    case ( 2 )
        allocate( a( n1, n2, L ), stat=ierr )   !! request L-GB virtual mem
        if ( ierr /= 0 ) stop "Memory error!"
    case ( 3 )
        allocate( a( n1, n2, L ), source=0 )    !! request L-GB resident mem
    endselect

    print *, "allocate() passed (type any key)"
    read *
end

这里使用的机器是Linux(x86_64),具有64 GB物理内存和64 GB交换磁盘。 ulimit -v显示"无限制"。在所有情况下(method = 1,2,3),该程序都会针对L>提出错误。 ~120,即物理和交换存储器的总和。对于method = 1,3,系统引发了错误

Operating system error: Cannot allocate memory
Allocation would exceed memory limit

而对于method = 2,stat=ierr检测到错误。对于L< 120程序继续运行,其中method = 2开始写入大量的0 ...无论如何,在这台机器上,allocate()允许的最大内存量似乎受限于物理+交换大小(合理的结果),尽管ulimit -v显示无限的虚拟内存。

以下是使用allocate()限制ulimit -v允许的最大内存量的另一项测试。该程序分配4GB数组并将值分配给2GB。

program alloc_test
    implicit none
    real, allocatable :: a(:), b(:)
    integer ierr, n

    n = 500000000

    allocate( a( n ), stat=ierr )   !! requests 2GB virtual memory
    if ( ierr /= 0 ) stop "Memory error! (a)"

    allocate( b( n ), stat=ierr )   !! requests 2GB virtual memory
    if ( ierr /= 0 ) stop "Memory error! (b)"

    print *, "before assignment (type any key)"
    call system( "ps aux | grep a.out" )
    read *

    print *, "now writing values..."
    a(:) = 0.0    !! request 2GB resident memory                        

    print *, "after assignment (type any key)"
    call system( "ps aux | grep a.out" )
    read *
end

如果我直接./a.out,则该程序会在allocate()处停止运行。我们现在根据this page

将虚拟内存限制为1GB
$ ( ulimit -v 1000000 ; ./a.out )

然后我们

STOP Memory error! (a)

如果我们将其限制为2.2 GB

STOP Memory error! (b)

最后,如果我们将其设置为> 4GB,则分配开始

before assignment (type any key)
<username>    12380  0.0  0.0 3918048  652 pts/1    S+   07:59   0:00 ./a.out

now writing values...

after assignment (type any key)
<username>    12380 38.0  2.9 3918048 1953788 pts/1 S+   07:59   0:00 ./a.out

因此我们可以限制虚拟内存量(如果需要),以便allocate( ..., stat=ierr )针对过度分配引发错误。