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

Too*_*oon 1 memory fortran fortran90

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

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

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

虽然我有 8 GB 的可用内存来运行只有 1 个进程的测试,但我可以分配一个最大为 size 的数组(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
Run Code Online (Sandbox Code Playgroud)

保存test.f90并随后编译并运行

mpif90 test.f90 -o test && mpirun -np 2 ./test
Run Code Online (Sandbox Code Playgroud)

Ant*_*ama 6

当您执行一条allocate语句时,您在虚拟内存空间中保留了一个域。虚拟空间是物理内存+交换+可能由于某些过度使用的可能性而导致的一些额外可能空间的总和,这将假设您不会使用所有保留。

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

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

这种机制在 NUMA 环境中非常好,因为这种“首次接触策略”将分配靠近首先写入的 CPU 的内存。通过这种方式,您可以在 OpenMP 循环中初始化数组,以将内存物理放置在靠近将使用它的 CPU 的位置。