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

Why can Fortran allocate such large arrays?

我写了一个科学代码,和往常一样,这归结为计算代数特征值方程中的系数:计算这些系数需要对多维数组进行积分,这会迅速大幅增加内存使用量。一旦计算出矩阵系数,原始的、预积分的多维数组就可以被释放,智能求解器接管,因此内存使用不再是大问题。如您所见,存在瓶颈,在我的 64 位、4 核、8 线程、8GB 内存笔记本电脑上,程序因内存不足而崩溃。

因此,我正在实施一个系统,该系统通过限制 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

当您执行 allocate 语句时,您会在虚拟内存 space 中保留一个域。虚拟 space 是物理内存的总和 + 交换 + 也许一些额外的可能 space 由于某些 overcommit 的可能性,这将假设你不会使用所有预约。

但是在您向其中写入内容之前,内存尚未物理保留。当你往内存里写东西的时候,系统会在物理上为你分配相应的页。 如果你不初始化你的数组,并且你的数组非常稀疏,可能有很多页面从未被写入,因此内存永远不会被完全使用。

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

这个机制在 NUMA 环境中非常好,因为这个 "first touch policy" 将分配内存接近第一个写入它的 CPU。 通过这种方式,您可以在 OpenMP 循环中初始化一个数组,以物理方式将内存放置在靠近将使用它的 CPU 的位置。