有效地影响 MPI 进程之外的部分阵列

计算科学 并行计算 正则 mpi
2021-12-27 20:23:43

这最初是在 Stackoverflow 上提出的,但建议我在此处重新发布并删除它,而不是迁移它。我试图根据那里的评论进行澄清。

我是 MPI 的新手,但我正在尝试使用它来加速我拥有的一些代码。该代码的最小版本如下:

program main
    !! takes a list, then for each element randomly generates an index and adds
    !! the element to that location
    !! while this program is useless the basic features are the same as a Monte
    !! Carlo program I am writing

    integer, parameter :: N=5
    integer, parameter :: niter=10
    integer :: arr(N), arr_new(N) ! will want dp real

    ! dummy
    integer :: i,j,step

    do i=1,N
        arr(i) = i
    enddo
    do step=1,niter
        arr_new = 0 ! initialise to zero

        do i=1,N
            j = randint_exc(1,N,i)
            arr_new(j) = arr_new(j) + i
        enddo

        arr = arr + arr_new

        print*, "newarr", arr_new
        print*, "uptarr", arr
    enddo

    contains

    function randint_exc(a, b, exclude) result(retval)
        !! get random integer between a and b, but exclude arg exclude
        implicit none
        integer, parameter :: dp = kind(1.d0)
        integer, intent(in) :: a, b
        integer, intent(in) :: exclude
        integer :: retval

        real(dp) :: u

        call random_number(u)
        retval = a + floor((b-a)*u) ! randint between a and b-1
        if (retval >= exclude) then
            retval = retval + 1
        endif

    end function randint_exc

end program main

(FWIW 我正在并行化我自己的 FCIQMC 实现,只是为了好玩;我知道那里有很好的程序。我想我会在这里简化它,这样你就不必担心细节)

基本上,我有一些值数组(我知道它的初始值),对于这个数组的每个元素,我想随机选择数组中的另一个元素,并将当前元素添加到其中。然后我对一些固定数量的迭代执行此操作。如您所见,我这样做的方法是将一个新数组初始化为零并向其中添加值,然后将该新数组添加到原始数组中。冲洗并重复。

我尝试将它与 MPI 并行化是让每个进程生成自己的数组,但我被困在它可能在自己的块之外生成元素的部分。我想我必须检查进程索引j属于哪个等级,然后将索引连同值一起发送到进程并接收(任意计数)。我一直在努力使用MPI_SendandMPI_Recv为此(我的尝试甚至还没有编译)。我将如何做到这一点,有没有更优雅/更简单的方法呢?(还有关于阻塞到部分;是否有内置的 MPI 函数?)这是我的尝试,! TODO ???我在评论中卡住了......否则,有没有办法让所有 MPI 进程共享内存到新数组,这样我就可以随时发送到任意索引?

program main
    use mpi
    implicit none

    ! MPI variables
    integer :: ierr, nproc, rank

    integer :: N=5
    integer, parameter :: niter=10

    ! variables introduced because I'm trying to move to MPI
    integer :: Nlocal, r
    integer, allocatable :: arrlocal(:), arr_newlocal(:)

    ! dummy
    integer :: i,j,step

    call MPI_Init(ierr)
    call MPI_Comm_size(MPI_COMM_WORLD, nproc, ierr)
    call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr)

    Nlocal = N/nproc
    if (rank == nproc-1) then
        ! add remaining elements to last processor's list
        r = modulo(N,nproc)
    else
        r = 0
    endif
    allocate(arrlocal(Nlocal+r), arr_newlocal(Nlocal+r))
    do i=1,Nlocal+r
        arrlocal(i) = Nlocal*rank+i
    enddo
    print*, rank, Nlocal, "array", arrlocal
    do step=1,niter
        ! NOTE you can only start the next step when all the other processes are
        ! done (I think), since it will depend on the new full array
        ! so force all the processes to reach this point
        call MPI_Barrier(MPI_COMM_WORLD, ierr)
        arr_newlocal = 0 ! initialise to zero

        do i=1,N
            ! this is the part I am most confused about parallelising
            j = randint_exc(1,N,i) ! NOTE N, *not* Nlocal
            ! TODO ???
            ! j might be outside the scope of this process
            ! arr_newlocal(j) = arr_newlocal(j) + i
        enddo

        arrlocal = arrlocal + arr_newlocal

    !     print*, step, rank, "newarr", arr_newlocal
    !     print*, step, rank, "uptarr", arrlocal
    enddo

    call MPI_Finalize(ierr)

    contains

    function randint_exc(a, b, exclude) result(retval)
        !! get random integer between a and b, but exclude arg exclude
        implicit none
        integer, parameter :: dp = kind(1.d0)
        integer, intent(in) :: a, b
        integer, intent(in) :: exclude
        integer :: retval

        real(dp) :: u

        call random_number(u)
        retval = a + floor((b-a)*u) ! randint between a and b-1
        if (retval >= exclude) then
            retval = retval + 1
        endif

    end function randint_exc

end program main

一些评论听起来好像这比我意识到的更难并行化,所以我很乐意接受一个基本上只是“使用 X 模式和 Y 方法”的参考的答案。我也对中间步骤感兴趣,我想去非常大的数组(所以它是内存密集型的),这更像是一个编程练习而不是其他任何东西,所以我想并行化算法本身而不是解决相同的算法多次并行并从那里获取统计信息。

2个回答

MPI 的基本模型是“双向沟通”:你有一个知道从哪里发送的发送者和一个知道从哪里期待某事的接收者。在您的描述中,情况并非如此:发送者发送给随机生成的接收者。您可以通过 MPI 中的片面沟通来做到这一点,这将是一个学习曲线。发送者(更准确地说:“来源”)可以选择任何进程作为接收者(“目标”)并将数据放在那里。所以你会使用MPI_Put而不是MPI_Send.

其他可能性:进程执行通配符接收。这有一个问题,他们不知道他们必须发出多少次这样的问题。但是你可以使用非阻塞屏障来解决这个问题。如果你能做到这一点,你可以拍拍自己的后背,因为你完全是最先进的。

或者您可以使用完全不同的范例,使您的分布式内存看起来像是共享的。不幸的是,这些通常远非有效。

编辑另一个答案建议Isend/Irecv我不确定这是否可行:收件人取决于该随机数。如果只有一个点发送,你就有大麻烦了,因为每个人都必须听那个消息,但只有一个人真正得到它。如果每个人都发送,你可以通过累积发送和接收来更聪明一点。如果您的应用程序有“超级步骤”,这原则上可以工作;如果发送时间也是随机的,它就不起作用。但从统计上看,一个进程仍然有可能获得零数据,因此它将有一个Irecv永远不会满足的优秀。(另一个问题是您不知道要创建多少缓冲区空间。)因此,更好的解决方案是使用 aReduce_scatter找出您将获得多少数据然后接收它。

单方面的沟通当然是一种途径。您可以用来解决此问题的另一种机制是使用非阻塞双向通信,例如MPI_ISendMPI_IRecv如果你修复了一些 MPI 进程,你可以将它设置为有两个主要任务:

  1. 来自其他进程的进程请求,请求该进程本地的数组数据
  2. 当此进程获得对其数据请求之一的响应时,它会更新相应的本地数组元素

您可能希望使您的阵列有效分布并使用一些全局 IDgid索引整个分布式数组中的元素。您可以为全局 ID 做的一件事是构造它,以便您可以在恒定时间内使用它来计算排名r该元素继续存在并计算本地 IDid该元素在位于排名上的数组块中的位置r过程。例如,您可能会使用类似的东西

gid=rN+id

在哪里,说,N是任何等级的元素数量(假设所有等级都相同)。然后明确给出gid我们可以计算rid. 当你随机选择一个元素时,你可以通过计算一个随机的全局 ID 来做到这一点gid之间0和你最大的全局索引,我假设你在初始化代码时得到它,并使用它来请求与元素关联的数据gid从父级。

只要每个进程都有适当的机制来响应请求并在收到响应时进行处理,这似乎相对容易完成。请注意,由于随机性,您将需要使用类似的东西MPI_Iprobe来帮助您确定消息队列中是否有任何您应该处理的消息(无论是请求还是响应)。