MPI邻居减少操作

时间:2018-01-30 15:44:21

标签: mpi nearest-neighbor topology cartesian-coordinates

这是我觉得我需要像MPI_Neighbor_allreduce这样的时刻,但我知道it doesn't exist

前言

鉴于描述3D物理域如何在进程间分布的3D MPI笛卡尔拓扑,我写了一个函数probe,它要求一个标量值(应该放在一个简单的REAL :: val中)给出域内一个点的3个坐标。

实际上只有1248个进程可以参与val的计算。< / p>

  • 1如果点内部到流程子域(并且没有涉及邻居),
  • 2如果两个进程的子域之间的点 (并且每个子域都涉及1个邻居),
  • 4如果在4个进程的子域之间一侧(并且每个子域都有2个邻居),
  • 8如果8个进程的子域之间的点是顶点(并且每个子域都涉及3个邻居)。

现在调用probe之后,每个进程都会保留val,这对于所涉及的进程来说是一些价值0NaN(我决定通过( de)为不涉及的过程评论正确的行。并且每个进程都知道它是否涉及(通过LOGICAL :: found变量),但不知道它是否是唯一涉及的,如果不是,则不知道谁是相关的邻居。

在涉及1进程的情况下,只有该进程的值才足够,并且进程可以编写,使用它或任何需要的进程。 在后三种情况下,必须计算所涉及过程的不同标量值的总和(并除以邻居的数量+1,即自我包括。)

问题

完成此通信和计算的最佳策略是什么?

我正在考虑哪些解决方案

我正在考虑以下可能性。

  1. 在调用val = 0之前,每个流程都会执行probe,然后可以使用MPI_(ALL)REDUCE,(参与的流程一般会与val /= 0一起参与,所有其他流程都会val == 0 val 1}}),但这意味着如果MPI_Neighbor_allgather要求更多的点,那么这些点将被连续处理,即使每个点所涉及的过程集与其他集不重叠。
  2. 每个进程调用found以在相邻进程之间共享6,以使每个涉及的进程知道MPI_send个邻居中的哪一个参与该总和然后执行个人MPI_recv(s)和val(s)来传达6。但这仍然涉及每个过程(即使每个过程只与6邻居通信。
  3. 也许最好的选择是每个流程定义一个由自己和MPI_SEND邻居组成的通信器然后使用。
  4. 修改

    对于@JorgeBellón提到的死锁风险,我最初通过在MPI_RECV之前调用who_is_involved来解决这个问题,以便在正方向进行通信,对应于甚至是MPI_SEND中的指数,反之亦然。作为一种特殊情况,这不能处理沿着它只有两个进程的周期性方向(因为两者中的每一个都将在正方向和负方向上看到另一个作为邻居,因此导致两个进程调用{{1}和MPI_RECV以相同的顺序,从而导致死锁);这个特例的解决方案是以下 ad-hoc 编辑who_is_involved(在我的代码中我称之为found_neigh):

    DO id = 1, ndims
        IF (ALL(found_neigh(2*id - 1:2*id))) found_neigh(2*id -1 + mycoords(id)) = .FALSE.
    END DO
    

    作为读者的参考,到目前为止我实施的解决方案(我不满意的解决方案)如下。

    found = ... ! .TRUE. or .FALSE. depending whether the process  is/isn't involved in computation of val
    IF (      found) val = ... ! compute own contribution
    IF (.NOT. found) val = NaN
    
    ! share found among neighbors
    found_neigh(:) = .FALSE.
    CALL MPI_NEIGHBOR_ALLGATHER(found, 1, MPI_LOGICAL, found_neigh, 1, MPI_LOGICAL, procs_grid, ierr)
    found_neigh = found_neigh .AND. found
    
    ! modify found_neigh to deal with special case of TWO processes along PERIODIC direction
    DO id = 1, ndims
        IF (ALL(found_neigh(2*id - 1:2*id))) found_neigh(2*id -1 + mycoords(id)) = .FALSE.
    END DO
    
    ! exchange contribution with neighbors
    val_neigh(:) = NaN
    IF (found) THEN
        DO id = 1, ndims
            IF (found_neigh(2*id))     THEN
                CALL MPI_SEND(val,                 1, MPI_DOUBLE_PRECISION, idp(id), 999, MPI_COMM_WORLD,                    ierr)
                CALL MPI_RECV(val_neigh(2*id),     1, MPI_DOUBLE_PRECISION, idp(id), 666, MPI_COMM_WORLD, MPI_STATUS_IGNORE, ierr)
            END IF
            IF (found_neigh(2*id - 1)) THEN
                CALL MPI_RECV(val_neigh(2*id - 1), 1, MPI_DOUBLE_PRECISION, idm(id), 999, MPI_COMM_WORLD, MPI_STATUS_IGNORE, ierr)
                CALL MPI_SEND(val,                 1, MPI_DOUBLE_PRECISION, idm(id), 666, MPI_COMM_WORLD,                    ierr)
            END IF
        END DO
    END IF
    
    ! combine own contribution with others
    val = somefunc(val, val_neigh)
    

1 个答案:

答案 0 :(得分:1)

如你所说,MPI_Neighbor_allreduce不存在。 您可以创建仅包含相邻进程的派生通信器,然后对它们执行常规MPI_Allreduce。每个进程在3D网格中最多可以有7个通信器。

  • 将特定过程放置在模板中心的通信器。
  • 每个相邻进程的相应通信器。

这可能是一个非常昂贵的过程,但并不意味着它值得(例如,HPLinpack广泛使用派生的通信器)。

如果您已经有笛卡尔拓扑,那么一个好的方法是使用MPI_Neighbor_allgather。通过这种方式,您不仅可以了解有多少邻居,还可以了解它是谁。

int found; // logical: either 1 or 0
int num_neighbors; // how many neighbors i got
int who_is_involved[num_neighbors]; // unknown, to be received
MPI_Neighbor_allgather( &found, ..., who_is_involved, ..., comm );

int actually_involved = 0;
int r = 0;
MPI_Request reqs[2*num_neighbors];
for( int i = 0; i < num_neighbors; i++ ) {
  if( who_is_involved[i] != 0 ) {
    actually_involved++;
    MPI_Isend( &val, ..., reqs[r++]);
    MPI_Irecv( &val, ..., reqs[r++]);
  }
}
MPI_Waitall( r, reqs, MPI_STATUSES_IGNORE );

请注意,我使用非阻塞点对点例程。这在大多数情况下很重要,因为MPI_Send可能会等待接收方呼叫MPI_Recv。在所有进程中无条件地调用MPI_Send然后调用MPI_Recv可能会导致死锁(请参阅MPI 3.1 standard section 3.4)。

另一种可能性是在单个通信中发送实际值和找到的值,以便减少传输次数。由于所有进程都涉及MPI_Neighbor_allgather,因此您可以使用它来完成所有工作(对于传输的数据量的小幅增加,它确实得到了回报)。

INTEGER :: neighbor, num_neighbors, found
REAL :: val
REAL :: sendbuf(2)
REAL :: recvbuf(2,num_neighbors)

sendbuf(1) = found
sendbuf(2) = val
CALL MPI_Neighbor_allgather( sendbuf, 1, MPI_2REAL, recvbuf, num_neighbors, MPI_2REAL, ...)

DO neighbor = 1,num_neighbors
  IF recvbuf(1,neighbor) .EQ. 1 THEN
     ! use neighbor val, placed in recvbuf(2,neighbor)
  END IF
END DO