这是我觉得我需要像MPI_Neighbor_allreduce
这样的时刻,但我知道it doesn't exist。
前言
鉴于描述3D物理域如何在进程间分布的3D MPI笛卡尔拓扑,我写了一个函数probe
,它要求一个标量值(应该放在一个简单的REAL :: val
中)给出域内一个点的3个坐标。
实际上只有1
,2
,4
或8
个进程可以参与val
的计算。< / p>
1
如果点内部到流程子域(并且没有涉及邻居),2
如果两个进程的子域之间的点 (并且每个子域都涉及1个邻居),4
如果在4个进程的子域之间一侧(并且每个子域都有2个邻居),8
如果8个进程的子域之间的点是顶点(并且每个子域都涉及3个邻居)。现在调用probe
之后,每个进程都会保留val
,这对于所涉及的进程来说是一些价值0
或NaN
(我决定通过( de)为不涉及的过程评论正确的行。并且每个进程都知道它是否涉及(通过LOGICAL :: found
变量),但不知道它是否是唯一涉及的,如果不是,则不知道谁是相关的邻居。
在涉及1
进程的情况下,只有该进程的值才足够,并且进程可以编写,使用它或任何需要的进程。
在后三种情况下,必须计算所涉及过程的不同标量值的总和(并除以邻居的数量+1
,即自我包括。)
问题
完成此通信和计算的最佳策略是什么?
我正在考虑哪些解决方案
我正在考虑以下可能性。
val = 0
之前,每个流程都会执行probe
,然后可以使用MPI_(ALL)REDUCE
,(参与的流程一般会与val /= 0
一起参与,所有其他流程都会val == 0
val
1}}),但这意味着如果MPI_Neighbor_allgather
要求更多的点,那么这些点将被连续处理,即使每个点所涉及的过程集与其他集不重叠。 found
以在相邻进程之间共享6
,以使每个涉及的进程知道MPI_send
个邻居中的哪一个参与该总和然后执行个人MPI_recv
(s)和val
(s)来传达6
。但这仍然涉及每个过程(即使每个过程只与6
邻居通信。MPI_SEND
邻居组成的通信器然后使用。修改
对于@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)
答案 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