仅使用非常大的预分配数组的一小部分

时间:2016-02-07 17:44:22

标签: memory-management fortran allocation

当我们在Fortran或C中分配一个数组时,我的理解是内存首先在所谓的虚拟内存中分配,而物理内存只在我们将数据写入数组(某些部分)时分配(例如,基于此page)。这是否意味着,如果我们分配一个非常大的数组(比如10 ^ 9个元素)并且只使用它的一小部分(比如前10 ^ 6个元素),那么我们只需要后者的物理内存吗?如果是这样,使用此功能在非常大的预分配阵列中容纳未知(但不是太大)的数据几乎没有问题吗?

例如,以下Fortran代码首先分配一个大小为10 ^ 9的大型数组,将数据写入前10 ^ 6个元素,然后执行重新分配以调整数组大小。

integer, allocatable :: a(:)
integer :: nlarge, nsmall, i

nlarge = 1000000000  !! 10^9
nsmall =    1000000  !! 10^6 

allocate( a( nlarge ) )   !! allocate in virtual memory

print *, "after allocation"
call system( "ps aux | grep a.out" )

do i = 1, nsmall
    a( i ) = i     !! write actual data (we assume that "nsmall" is not known a priori)
enddo

print *, "after assignment"
call system( "ps aux | grep a.out" )

a = a( 1 : nsmall )    !! adjust the array size by reallocation

print *, "after reallocation"
call system( "ps aux | grep a.out" )

我的机器上的输出(带有gfortran的Linux x86_64)是

after allocation
username    29064  0.0  0.0 3914392  780 pts/3    S+   01:15   0:00 ./a.out
after assignment
username    29064  0.0  0.0 3914392 5188 pts/3    S+   01:15   0:00 ./a.out
after reallocation
username    29064  0.0  0.0  12048  4692 pts/3    S+   01:15   0:00 ./a.out

表示仅使用约5 MB的物理内存。是否可以利用此功能来容纳未知大小的临时数据(但低于物理内存大小)?

修改

更具体地说,我所谓的系统是运行Linux x86_64(例如CentOS)的典型工作站,具有数十GB的RAM,并且该程序是用Fortran编写的。问题的动机是,当我希望将未知大小的数据存储到数组中时,我通常需要以某种方式知道它的大小并适当地分配数组。但是,除非我们有内置的动态数组,否则这种方法有点单调乏味。通常,这种情况在两种情况下发生:(1)当从包含未知大小的数据的外部文件中读取数据时; (2)当一个人收集符合多维循环的特定条件的数据时。在情况1中,我们通常扫描文件两次(一次获得数据大小,然后读取数据),或者预先分配足够大的数组作为缓冲区。因此,我对虚拟内存系统是否通过允许分配非常大的数组(无需过多关注大小)来帮助简化此任务感兴趣。

然而,从更多的实验中我知道这种方法相当有限......例如,如果我按如下方式更改数组的大小,ifort抱怨“虚拟内存不足”高于~80 GB ,这可能与我系统上的物理内存+交换区域的总和相对应。所以,虽然“ulimit -a”表示虚拟内存是“无限制的”,但在实践中似乎并非无限制......

! compiled with: ifort -heap-arrays -assume realloc_lhs 
use iso_fortran_env, only: long => int64
integer, allocatable :: a(:)
integer(long) :: nlarge, nsmall, i

! nlarge = 10_long**9   !! OK: 4 GB                                              
! nlarge = 10_long**10   !! OK: 40 GB                                            
nlarge = 2 * 10_long**10   !! OK: 80 GB                                         
! nlarge = 3 * 10_long**10   !! NG: insufficient virtual memory (120 GB)         
! nlarge = 4 * 10_long**10   !! NG: insufficient virtual memory (160 GB)         
! nlarge = 10_long**11    ! NG: insufficient virtual memory (400 GB)             

nsmall = 10**6   !! 4 MB   

结论:使用传统方法似乎更好(即,分配具有必要大小的数组,或根据需要重复分配可分配数组,或使用用户定义的动态数组)。对于这个微不足道的结论,我感到抱歉......

1 个答案:

答案 0 :(得分:2)

  

当我们在Fortran或C中分配数组时,我的理解是内存首先在所谓的虚拟内存中分配,而物理内存只在我们将数据写入数组(某些部分)时分配。

这是您的操作系统可能选择做的一件事。不能保证它实际上不会为所有保留的内存添加实际的页表条目,映射并因此拥有物理内存。

事实上,C或Fortran并没有告诉你任何关于如何获得内存的信息,这些信息来自于操作系统如何处理这些内存。您会混淆语言指定的内容,标准库如何处理内存需求以及底层操作系统如何实际将物理内存映射到进程地址空间 - 事实上,在没有MMU(内存管理单元)的系统上,您可以完全运行C代码,但所有内存地址实际上都是物理地址。

  

这是否意味着,如果我们分配一个非常大的数组(比如10 ^ 9个元素)并且只使用它的一小部分(比如前10 ^ 6个元素),那么我们只需要后者的物理内存吗?

要小心一点。再次,由操作系统来实现它。操作系统可能(通常会)实现“延迟映射”功能,但仍然不会提供比实际可用内存更多的内存。

另外,请记住,至少对于32位操作系统,存在相关的内存空间限制:32位进程不能超过2GB的内存空间,这意味着您根本不能拥有10 ^ 9 32位整数!

  

如果是这样,使用此功能在非常大的预分配阵列中容纳未知(但不是太大)的数据几乎没有问题吗?

这实际上是一个问题,因为操作系统可能不会给你那么多记忆。此外,除了花费的时间之外,以后获得更多内存并没有真正的缺点(参见realloc standard C function);操作系统必须找到空闲页面并将它们映射到您的进程空间,并可能重新映射以前的页面。