Fortran内存分配不报错,但程序在初始化时被OS杀死
Fortran memory allocation does not give an error, but the program is killed by OS at initialization
鉴于下面提供的最小工作示例,您知道为什么在内存分配步骤不会发生内存分配错误吗?正如我检查的那样,当我使用 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
当我 运行 它时,我没有打印消息 'Memory error!',但有消息 'Start initialization',然后程序被 OS 终止。如果我将内存分配与 'source' 参数一起使用(如代码中所注释),那么我只会收到消息 'Memory error!'.
对于内存统计,'free' 命令给我这个输出:
total used free shared buffers cached
Mem: 8169952 3630284 4539668 46240 1684 124888
-/+ buffers/cache: 3503712 4666240
Swap: 0 0 0
扩展评论而不是答案:
在 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 编译器在处理赋值时可能会临时为该部分分配 space。当您考虑更通用的数组部分时,这可能是有意义的。
我不确定我是否理解为什么您认为 allocate
语句执行时未分配 space 但我建议您整理一下分配,然后再考虑一下。而且我猜想为数组部分临时分配 space,这将与数组本身消耗的 space 一样多,可能会使您的程序超出边缘并导致您报告的行为。
顺便说一下,你可以试试这个语句
allocate(isensit%sa(isensit%nnz),source=0.0,stat=ierr)
如果您的编译器是最新的,应该在一条语句中进行分配并设置数组中的值。
哦,还有一个完全没有必要的评论:更喜欢 use mpi
(或 use mpi_mod
或任何您的安装更喜欢 include mpif.h
。这将防止(许多)错误可能由不匹配引起根据要求调用 mpi 例程。例程的使用关联意味着编译器可以检查参数匹配,包含头文件则不会。
您正在查看 linux 使用的内存分配策略的行为。当您分配内存但尚未写入时,它仅包含在虚拟内存中(请注意,这也可能受到特定 Fortran 运行时库的影响,但我不确定)。此内存存在于您的进程虚拟地址 space 中,但它不受任何实际物理内存页面的支持。只有当你写入内存时,才会分配物理页,并且只够满足写入。
考虑以下程序:
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 将使用一种算法来确定要杀死什么以释放内存,并且您的代码的行为使其很可能成为被杀死的候选者。当你的写入导致可以满足的物理分配时,你的进程正在被内核杀死。由于上面详述的行为,您会在写入时看到它(由赋值引起)但不会在分配时看到。
下面是三种调用方式的比较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),物理内存为64GB,交换磁盘为64GB。 ulimit -v
显示 "unlimited"。在所有情况下 (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
显示虚拟内存不受限制。
下面是使用 ulimit -v
限制 allocate()
允许的最大内存量的另一个测试。本程序分配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 )
会引发针对过度分配的错误。
鉴于下面提供的最小工作示例,您知道为什么在内存分配步骤不会发生内存分配错误吗?正如我检查的那样,当我使用 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
当我 运行 它时,我没有打印消息 'Memory error!',但有消息 'Start initialization',然后程序被 OS 终止。如果我将内存分配与 'source' 参数一起使用(如代码中所注释),那么我只会收到消息 'Memory error!'.
对于内存统计,'free' 命令给我这个输出:
total used free shared buffers cached
Mem: 8169952 3630284 4539668 46240 1684 124888
-/+ buffers/cache: 3503712 4666240
Swap: 0 0 0
扩展评论而不是答案:
在 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 编译器在处理赋值时可能会临时为该部分分配 space。当您考虑更通用的数组部分时,这可能是有意义的。
我不确定我是否理解为什么您认为 allocate
语句执行时未分配 space 但我建议您整理一下分配,然后再考虑一下。而且我猜想为数组部分临时分配 space,这将与数组本身消耗的 space 一样多,可能会使您的程序超出边缘并导致您报告的行为。
顺便说一下,你可以试试这个语句
allocate(isensit%sa(isensit%nnz),source=0.0,stat=ierr)
如果您的编译器是最新的,应该在一条语句中进行分配并设置数组中的值。
哦,还有一个完全没有必要的评论:更喜欢 use mpi
(或 use mpi_mod
或任何您的安装更喜欢 include mpif.h
。这将防止(许多)错误可能由不匹配引起根据要求调用 mpi 例程。例程的使用关联意味着编译器可以检查参数匹配,包含头文件则不会。
您正在查看 linux 使用的内存分配策略的行为。当您分配内存但尚未写入时,它仅包含在虚拟内存中(请注意,这也可能受到特定 Fortran 运行时库的影响,但我不确定)。此内存存在于您的进程虚拟地址 space 中,但它不受任何实际物理内存页面的支持。只有当你写入内存时,才会分配物理页,并且只够满足写入。
考虑以下程序:
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 将使用一种算法来确定要杀死什么以释放内存,并且您的代码的行为使其很可能成为被杀死的候选者。当你的写入导致可以满足的物理分配时,你的进程正在被内核杀死。由于上面详述的行为,您会在写入时看到它(由赋值引起)但不会在分配时看到。
下面是三种调用方式的比较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),物理内存为64GB,交换磁盘为64GB。 ulimit -v
显示 "unlimited"。在所有情况下 (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
显示虚拟内存不受限制。
下面是使用 ulimit -v
限制 allocate()
允许的最大内存量的另一个测试。本程序分配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 )
会引发针对过度分配的错误。