MPI将阻止转换为非阻止问题

时间:2015-06-03 12:40:56

标签: fortran mpi nonblocking

我正在处理的代码使用MPI将一个大的三维数组(一个立方体)沿着所有三个轴分割成子域,以形成更小的立方体。我之前曾经研究过一个更简单的二维等价物而没有任何问题。

现在,由于MPI有一种令人烦恼的习惯(或者令人满意的习惯,取决于你如何看待它)将MPI_SEND和MPI_RECV视为小块数据的非阻塞调用,因此从2D迁移到3D带来了很多困难。由于必须在进程之间传递的数据现在是3D数组,因此所有对2D工作完美的调用在3D中最轻微的挑衅开始死锁。

经过一周的战斗和拔出大量头发后,构建了一组复杂的MPI_SEND和MPI_RECV调用,设法在域中的每个多维数据集的面,边和角上传递数据(周期性和非周期性设置适当在不同的边界)顺利。幸福不会持久。在添加了一个新的边界条件后,该条件需要在域的一侧的单元之间进行额外的通信路径,代码陷入了另一个恶性的死锁之中。

受够了,我决定采用非阻塞电话。现在有了这么多背景,我希望我对以下代码的意图非常清楚。我不包括我用于在子域的边缘和角落传输数据的代码。如果我可以理清立方体面之间的沟通,我就能把其他所有东西整齐地放在适当位置。

代码使用五个数组来简化数据传输:

  1. rArr =相邻单元格的排名数组

  2. tsArr =用于向每个邻居发送数据的标签数组

  3. trArr =用于从每个邻居接收数据的标签数组

  4. lsArr =描述要发送的数据块的限制(索引)数组

  5. lrArr =描述要接收的数据块的限制(索引)数组

  6. 现在,由于每个立方体有6个邻居共享一个面,rArr,tsArr和trArr都是长度为6的整数数组。另一方面,limits数组是一个二维数组,如下所述:

    lsArr = [[xStart, xEnd, yStart, yEnd, zStart, zEnd, dSize], !for face 1 sending
             [xStart, xEnd, yStart, yEnd, zStart, zEnd, dSize], !for face 2 sending
             .
             .
             [xStart, xEnd, yStart, yEnd, zStart, zEnd, dSize]] !for face 6 sending
    

    因此调用将变量dCube的值发送到单元格(进程)的第i个面上将发生如下:

    call MPI_SEND(dCube(lsArr(i, 1):lsArr(i, 2), lsArr(i, 3):lsArr(i, 4), lsArr(i, 5):lsArr(i, 6)), lsArr(i, 7), MPI_DOUBLE_PRECISION, rArr(i), tsArr(i), MPI_COMM_WORLD, ierr)
    

    另一个具有匹配目标排名和标记的进程将收到与下面相同的块:

    call MPI_RECV(dCube(lrArr(i, 1):lrArr(i, 2), lrArr(i, 3):lrArr(i, 4), lrArr(i, 5):lrArr(i, 6)), lrArr(i, 7), MPI_DOUBLE_PRECISION, rArr(i), trArr(i), MPI_COMM_WORLD, stVal, ierr)
    

    测试源和目标进程的lsArr和lrArr以显示匹配的大小(但不同的限制)。还检查了标签阵列以查看它们是否匹配。

    现在我的早期版本的代码与阻塞调用完美配合,因此我对上述数组中值的正确性有99%的信心。如果有理由怀疑它们的准确性,我可以添加这些细节,但随后帖子将非常长。

    以下是我的代码的阻止版本,它完美地运行。如果它有点棘手,我道歉。如果有必要进一步阐明问题,我会这样做。

    subroutine exchangeData(dCube)
    use someModule
    
    implicit none
    integer :: i, j
    double precision, intent(inout), dimension(xS:xE, yS:yE, zS:zE) :: dCube
    
    do j = 1, 3
        if (mod(edArr(j), 2) == 0) then    !!edArr = [xRank, yRank, zRank]
            i = 2*j - 1
            call MPI_SEND(dCube(lsArr(1, i):lsArr(2, i), lsArr(3, i):lsArr(4, i), lsArr(5, i):lsArr(6, i)), &
                          lsArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), tsArr(i), MPI_COMM_WORLD, ierr)
    
            i = 2*j
            call MPI_RECV(dCube(lrArr(1, i):lrArr(2, i), lrArr(3, i):lrArr(4, i), lrArr(5, i):lrArr(6, i)), &
                          lrArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), trArr(i), MPI_COMM_WORLD, stVal, ierr)
        else
            i = 2*j
            call MPI_RECV(dCube(lrArr(1, i):lrArr(2, i), lrArr(3, i):lrArr(4, i), lrArr(5, i):lrArr(6, i)), &
                          lrArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), trArr(i), MPI_COMM_WORLD, stVal, ierr)
    
            i = 2*j - 1
            call MPI_SEND(dCube(lsArr(1, i):lsArr(2, i), lsArr(3, i):lsArr(4, i), lsArr(5, i):lsArr(6, i)), &
                          lsArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), tsArr(i), MPI_COMM_WORLD, ierr)
        end if
    
        if (mod(edArr(j), 2) == 0) then
            i = 2*j
            call MPI_SEND(dCube(lsArr(1, i):lsArr(2, i), lsArr(3, i):lsArr(4, i), lsArr(5, i):lsArr(6, i)), &
                          lsArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), tsArr(i), MPI_COMM_WORLD, ierr)
    
            i = 2*j - 1
            call MPI_RECV(dCube(lrArr(1, i):lrArr(2, i), lrArr(3, i):lrArr(4, i), lrArr(5, i):lrArr(6, i)), &
                          lrArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), trArr(i), MPI_COMM_WORLD, stVal, ierr)
        else
            i = 2*j - 1
            call MPI_RECV(dCube(lrArr(1, i):lrArr(2, i), lrArr(3, i):lrArr(4, i), lrArr(5, i):lrArr(6, i)), &
                          lrArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), trArr(i), MPI_COMM_WORLD, stVal, ierr)
    
            i = 2*j
            call MPI_SEND(dCube(lsArr(1, i):lsArr(2, i), lsArr(3, i):lsArr(4, i), lsArr(5, i):lsArr(6, i)), &
                          lsArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), tsArr(i), MPI_COMM_WORLD, ierr)
        end if
    end do
    end subroutine exchangeData
    

    基本上它沿着每个方向x,y和z,首先从奇数面和偶数面发送数据。我不知道是否有更简单的方法可以做到这一点。这是在无数的死锁代码之后得出的,几乎让我疯狂。跨边和角发送数据的代码甚至更长。

    现在出现了我现在遇到的实际问题。我用以下代码替换了上面的代码(有点天真,也许?)

    subroutine exchangeData(dCube)
    use someModule
    
    implicit none
    integer :: i, j
    integer, dimension(6) :: fRqLst
    integer :: stLst(MPI_STATUS_SIZE, 6)
    double precision, intent(inout), dimension(xS:xE, yS:yE, zS:zE) :: dCube
    
    fRqLst = MPI_REQUEST_NULL
    do i = 1, 6
        call MPI_IRECV(dCube(lrArr(1, i):lrArr(2, i), lrArr(3, i):lrArr(4, i), lrArr(5, i):lrArr(6, i)), &
                            lrArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), trArr(i), MPI_COMM_WORLD, fRqLst(i), ierr)
    end do
    
    do i = 1, 6
        call MPI_SEND(dCube(lsArr(1, i):lsArr(2, i), lsArr(3, i):lsArr(4, i), lsArr(5, i):lsArr(6, i)), &
                           lsArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), tsArr(i), MPI_COMM_WORLD, ierr)
    end do
    call MPI_WAITALL(6, fRqLst, stLst, ierr)
    call MPI_BARRIER(MPI_COMM_WORLD, ierr)
    end subroutine exchangeData
    

    someModule是一个包含所有变量的占位符模块。实际上它们是分布在一系列模块中的,但我现在仍然会对此有所了解。主要思想是使用非阻塞MPI_IRECV调用来强制每个进程接收数据,然后使用一系列阻塞MPI_SEND调用发送数据。但是,我怀疑如果事情变得如此简单,那么并行编程本来就是一块蛋糕。

    此代码提供SIGABRT并以双重自由错误退出。此外,它似乎是一个Heisenbug并且有时会消失。

    错误讯息:

    *** Error in `./a.out': double free or corruption (!prev): 0x00000000010315c0 ***
    *** Error in `./a.out': double free or corruption (!prev): 0x00000000023075c0 ***
    *** Error in `./a.out': double free or corruption (!prev): 0x0000000001d465c0 ***
    
    Program received signal SIGABRT: Process abort signal.
    
    Program received signal SIGABRT: Process abort signal.
    
    Backtrace for this error:
    
    Program received signal SIGABRT: Process abort signal.
    
    Backtrace for this error:
    
    Program received signal SIGABRT: Process abort signal.
    
    Backtrace for this error:
    
    Backtrace for this error:
    #0  0x7F5807D3C7D7
    #1  0x7F5807D3CDDE
    #2  0x7F580768ED3F
    #3  0x7F580768ECC9
    #4  0x7F58076920D7
    #5  0x7F58076CB393
    #6  0x7F58076D766D
    #0  0x7F4D387D27D7
    #1  0x7F4D387D2DDE
    #2  0x7F4D38124D3F
    #3  0x7F4D38124CC9
    #4  0x7F4D381280D7
    #5  0x7F4D38161393
    #0  #6  0x7F4D3816D66D
    0x7F265643B7D7
    #1  0x7F265643BDDE
    #2  0x7F2655D8DD3F
    #3  0x7F2655D8DCC9
    #4  0x7F2655D910D7
    #5  0x7F2655DCA393
    #6  0x7F2655DD666D
    #7  0x42F659 in exchangedata_ at solver.f90:1542 (discriminator 1)
    #7  0x42F659 in exchangedata_ at solver.f90:1542 (discriminator 1)
    #8  0x42EFFB in processgrid_ at solver.f90:431
    #9  0x436CF0 in MAIN__ at solver.f90:97
    #8  0x42EFFB in processgrid_ at solver.f90:431
    #9  0x436CF0 in MAIN__ at solver.f90:97
    #0  0x7FC9DA96B7D7
    #1  0x7FC9DA96BDDE
    #2  0x7FC9DA2BDD3F
    #3  0x7FC9DA2BDCC9
    #4  0x7FC9DA2C10D7
    #5  0x7FC9DA2FA393
    #6  0x7FC9DA30666D
    #7  0x42F659 in exchangedata_ at solver.f90:1542 (discriminator 1)
    #8  0x42EFFB in processgrid_ at solver.f90:431
    #9  0x436CF0 in MAIN__ at solver.f90:97
    #7  0x42F659 in exchangedata_ at solver.f90:1542 (discriminator 1)
    #8  0x42EFFB in processgrid_ at solver.f90:431
    #9  0x436CF0 in MAIN__ at solver.f90:97
    

    我尝试使用'(鉴别器1)'在本网站上搜索类似的错误。部分,但无法找到任何。我还搜索了MPI产生双重内存损坏错误并再次无效的情况。

    我还必须指出错误消息中的第1542行对应于我的代码中的阻止MPI_SEND调用。

    当我使用gfortran 4.8.2和ompi 1.6.5时出现上述错误。但是,我也尝试使用英特尔fortran编译器运行上述代码,并收到一条奇怪的错误消息:

    [21] trying to free memory block that is currently involved to uncompleted data transfer operation
    

    我在网上搜索了上述错误,几乎什么都没有。 :(所以这也是一个死胡同。完整的错误信息有点太长了,但下面是它的一部分:

    *** glibc detected *** ./a.out: munmap_chunk(): invalid pointer: 0x0000000001c400a0 ***
    *** glibc detected *** ./a.out: malloc(): memory corruption: 0x0000000001c40410 ***
    *** glibc detected *** ./a.out: malloc(): memory corruption: 0x0000000000a67790 ***
    *** glibc detected *** ./a.out: malloc(): memory corruption: 0x0000000000a67790 ***
    *** glibc detected *** ./a.out: free(): invalid next size (normal): 0x0000000000d28c80 ***
    *** glibc detected *** ./a.out: malloc(): memory corruption: 0x00000000015354b0 ***
    *** glibc detected *** ./a.out: malloc(): memory corruption: 0x00000000015354b0 ***
    *** glibc detected *** ./a.out: free(): invalid next size (normal): 0x0000000000f51520 ***
    [20] trying to free memory block that is currently involved to uncompleted data transfer operation
     free mem  - addr=0x26bd800 len=3966637480
     RTC entry - addr=0x26a9e70 len=148800 cnt=1
    Assertion failed in file ../../i_rtc_cache.c at line 1397: 0
    internal ABORT - process 20
    [21] trying to free memory block that is currently involved to uncompleted data transfer operation
     free mem  - addr=0x951e90 len=2282431520
     RTC entry - addr=0x93e160 len=148752 cnt=1
    Assertion failed in file ../../i_rtc_cache.c at line 1397: 0
    internal ABORT - process 21
    

    如果我的错误是一些粗心的错误,或者由于知识不足,上述细节可能就足够了。如果这是一个更深层次的问题,那么我很乐意进一步阐述。

    提前致谢!

1 个答案:

答案 0 :(得分:0)

虽然这个问题引起了有用的评论,但我相信,就这个问题如何帮助我解决问题发表答案可能会对那些可能在未来遇到同样问题的人有用。

正如所指出的,在Fortran中使用非连续数组的非阻塞MPI调用 - 糟糕的主意

我使用了将非连续数组复制到连续数组并使用它的想法。但是,对于阻塞调用,非连续数组似乎表现良好。由于我使用阻塞MPI_SEND和非阻塞MPI_IRECV,因此代码只生成一个副本 - 仅用于接收,并继续像以前一样非连续地发送数据。这似乎现在有效,但如果以后可能会导致任何打嗝,请在评论中警告我。

它确实增加了许多重复的代码行(破坏了美学:P)。这主要是因为所有6个面的发送/接收限制都不相同。因此,临时存储要接收的数据的数组必须为六个面中的每一个单独分配(和复制)。

subroutine exchangeData(dCube)
use someModule

implicit none
integer :: i
integer, dimension(6) :: fRqLst
integer :: stLst(MPI_STATUS_SIZE, 6)
double precision, intent(inout), dimension(xS:xE, yS:yE, zS:zE) :: dCube
double precision, allocatable, dimension(:,:,:) :: fx0, fx1, fy0, fy1, fz0, fz1

allocate(fx0(lrArr(1, 1):lrArr(2, 1), lrArr(3, 1):lrArr(4, 1), lrArr(5, 1):lrArr(6, 1)))
allocate(fx1(lrArr(1, 2):lrArr(2, 2), lrArr(3, 2):lrArr(4, 2), lrArr(5, 2):lrArr(6, 2)))
allocate(fy0(lrArr(1, 3):lrArr(2, 3), lrArr(3, 3):lrArr(4, 3), lrArr(5, 3):lrArr(6, 3)))
allocate(fy1(lrArr(1, 4):lrArr(2, 4), lrArr(3, 4):lrArr(4, 4), lrArr(5, 4):lrArr(6, 4)))
allocate(fz0(lrArr(1, 5):lrArr(2, 5), lrArr(3, 5):lrArr(4, 5), lrArr(5, 5):lrArr(6, 5)))
allocate(fz1(lrArr(1, 6):lrArr(2, 6), lrArr(3, 6):lrArr(4, 6), lrArr(5, 6):lrArr(6, 6)))

fRqLst = MPI_REQUEST_NULL
call MPI_IRECV(fx0, lrArr(7, 1), MPI_DOUBLE_PRECISION, rArr(1), trArr(1), MPI_COMM_WORLD, fRqLst(1), ierr)
call MPI_IRECV(fx1, lrArr(7, 2), MPI_DOUBLE_PRECISION, rArr(2), trArr(2), MPI_COMM_WORLD, fRqLst(2), ierr)
call MPI_IRECV(fy0, lrArr(7, 3), MPI_DOUBLE_PRECISION, rArr(3), trArr(3), MPI_COMM_WORLD, fRqLst(3), ierr)
call MPI_IRECV(fy1, lrArr(7, 4), MPI_DOUBLE_PRECISION, rArr(4), trArr(4), MPI_COMM_WORLD, fRqLst(4), ierr)
call MPI_IRECV(fz0, lrArr(7, 5), MPI_DOUBLE_PRECISION, rArr(5), trArr(5), MPI_COMM_WORLD, fRqLst(5), ierr)
call MPI_IRECV(fz1, lrArr(7, 6), MPI_DOUBLE_PRECISION, rArr(6), trArr(6), MPI_COMM_WORLD, fRqLst(6), ierr)

do i = 1, 6
    call MPI_SEND(dCube(lsArr(1, i):lsArr(2, i), lsArr(3, i):lsArr(4, i), lsArr(5, i):lsArr(6, i)), &
                       lsArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), tsArr(i), MPI_COMM_WORLD, ierr)
end do

call MPI_WAITALL(6, fRqLst, stLst, ierr)
dCube(lrArr(1, 1):lrArr(2, 1), lrArr(3, 1):lrArr(4, 1), lrArr(5, 1):lrArr(6, 1)) = fx0
dCube(lrArr(1, 2):lrArr(2, 2), lrArr(3, 2):lrArr(4, 2), lrArr(5, 2):lrArr(6, 2)) = fx1
dCube(lrArr(1, 3):lrArr(2, 3), lrArr(3, 3):lrArr(4, 3), lrArr(5, 3):lrArr(6, 3)) = fy0
dCube(lrArr(1, 4):lrArr(2, 4), lrArr(3, 4):lrArr(4, 4), lrArr(5, 4):lrArr(6, 4)) = fy1
dCube(lrArr(1, 5):lrArr(2, 5), lrArr(3, 5):lrArr(4, 5), lrArr(5, 5):lrArr(6, 5)) = fz0
dCube(lrArr(1, 6):lrArr(2, 6), lrArr(3, 6):lrArr(4, 6), lrArr(5, 6):lrArr(6, 6)) = fz1
deallocate(fx0, fx1, fy0, fy1, fz0, fz1)
end subroutine exchangeData

这部分地消除了我通过在数组中存储排名和标签而获得的优势。我这样做主要是为了让发送和接收呼叫循环。使用此修复程序,只能将发送调用置于循环中。

由于在子程序的每次调用中分配和取消分配都会浪费时间,因此可以将数组放入模块中并在代码的开头分配。每次通话都不会改变限制。

当同样的方法应用于角落和边缘时,它会使代码膨胀一些,但它似乎正在起作用。 :)

感谢您的评论。