鉴于下面提供的最小工作示例,您知道为什么在内存分配步骤中不会发生内存分配错误吗?正如我检查的那样,当我使用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
答案 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
$ ( 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 )
针对过度分配引发错误。