为什么Fortran可以分配这么大的数组呢?

时间:2015-09-21 17:15:23

标签: memory fortran fortran90

我编写了一个科学代码,并且像往常一样,这可以用来计算代数特征值方程中的系数:计算这些系数需要在多维数组上进行积分,这会迅速膨胀内存使用量。一旦计算出矩阵系数,就可以解除原始的预积分多维数组,并且智能求解器可以接管,因此内存使用不再是一个大问题。正如你所看到的那样,有一个瓶颈,在我的64位,4核,8线程,8GB ram笔记本电脑上,由于内存不足,程序崩溃了。

因此,我正在实现一个系统,通过限制MPI进程在计算某些特征值矩阵元素时可以采取的任务的大小来控制内存使用。完成后,他们将寻找剩余的工作,以便矩阵仍然被填充,但是以更顺序和更不平行的方式。

因此,我正在检查我可以分配多少内存,这是混乱开始的地方:我分配大小为8字节的双精度数(使用sizeof(1)检查)并查看分配状态。

虽然我有8 GB的ram可用,只运行1个进程的测试,但我可以分配一个最大为(40000,40000)的数组,相当于大约13GB的内存!我的第一个问题是:这怎么可能?有那么多虚拟内存吗?

其次,我意识到我也可以为多个流程做同样的事情: 16 流程可以,同时分配这些大规模数组!

这不可能是对的吗?

有人理解为什么会这样吗?我是否做错了什么?

修改

这是一个产生上述奇迹的代码,至少在我的机器上。但是,当我将数组的元素设置为某个值时,它确实表现得应该然后崩溃 - 或者至少开始慢慢地表现非常,我猜这是因为虚拟内存很慢是用吗?

program test_miracle
    use ISO_FORTRAN_ENV
    use MPI

    implicit none

    ! global variables
    integer, parameter :: dp = REAL64                                           ! double precision
    integer, parameter :: max_str_ln = 120                                      ! maximum length of filenames
    integer :: ierr                                                             ! error variable
    integer :: n_procs                                                          ! MPI nr. of procs

    ! start MPI
    call MPI_init(ierr)                                                         ! initialize MPI
    call MPI_Comm_size(MPI_Comm_world,n_procs,ierr)                             ! nr. MPI processes
    write(*,*) 'RUNNING MPI WITH', n_procs, 'processes'

    ! call asking for 6 GB
    call test_max_memory(6000._dp)
    call MPI_Barrier(MPI_Comm_world,ierr)

    ! call asking for 13 GB
    call test_max_memory(13000._dp)
    call MPI_Barrier(MPI_Comm_world,ierr)

    ! call asking for 14 GB
    call test_max_memory(14000._dp)
    call MPI_Barrier(MPI_Comm_world,ierr)

    ! stop MPI
    call MPI_finalize(ierr)

contains
    ! test whether maximum memory feasible
    subroutine test_max_memory(max_mem_per_proc)
        ! input/output
        real(dp), intent(in) :: max_mem_per_proc                                ! maximum memory per process

        ! local variables
        character(len=max_str_ln) :: err_msg                                    ! error message
        integer :: n_max                                                        ! maximum size of array
        real(dp), allocatable :: max_mem_arr(:,:)                               ! array with maximum size
        integer :: ierr                                                         ! error variable

        write(*,*) ' > Testing whether maximum memory per process of ',&
            &max_mem_per_proc/1000, 'GB is possible'

        n_max = ceiling(sqrt(max_mem_per_proc/(sizeof(1._dp)*1.E-6)))

        write(*,*) '   * Allocating doubles array of size', n_max

        allocate(max_mem_arr(n_max,n_max),STAT=ierr)
        err_msg = '   * cannot allocate this much memory. Try setting &
            &"max_mem_per_proc" lower'
        if (ierr.ne.0) then
            write(*,*) err_msg
            stop
        end if

        !max_mem_arr = 0._dp                                                     ! UNCOMMENT TO MAKE MIRACLE DISSAPEAR


        deallocate(max_mem_arr)

        write(*,*) '   * Maximum memory allocatable'
    end subroutine test_max_memory
end program test_miracle

保存在test.f90中,随后编译并与

一起运行
mpif90 test.f90 -o test && mpirun -np 2 ./test

1 个答案:

答案 0 :(得分:4)

执行allocate语句时,在虚拟内存空间中保留域。虚拟空间是物理内存+交换的总和+可能是一些额外的可能空间,因为某些 overcommit 可能性,假设您不会使用所有预留。

但是在你写入内容之前,内存还没有实际保留。当您在内存中写入内容时,系统将为您物理分配相应的页面。 如果你没有初始化你的数组,并且你的数组非常稀疏,那么很可能有许多页面从未写过,因此内存永远不会被物理上完全使用。

当您看到系统速度变慢时,可能是系统正在将页面交换到磁盘,因为物理内存已满。如果你在磁盘上有8GB RAM和8GB交换,你的计算可以运行(非常慢......)

这种机制在NUMA环境中相当不错,因为这种“第一次触摸策略”会将内存分配到首先写入其中的CPU附近。 通过这种方式,您可以在OpenMP循环中初始化数组,以将内存物理放置在将使用它的CPU附近。