在 for 循环中使用 MPI_Irecv 和MPI_Isend

Using MPI_Irecv and MPI_Isend in a for loop

本文关键字:MPI Irecv Isend for 循环      更新时间:2023-10-16

我对MPI_IsendMPI_Irecv有问题。我正在研究一个图的邻接矩阵,该矩阵是按行分布的。我们可以假设每个处理器包含一行。对于每对索引(i,j)我需要发送和接收 2 个整数。基本上,我需要从其他行接收一些其他信息才能进行计算。我是 MPI 的新手,在这里它进入无限循环,我不确定它是否是在 for 循环中使用MPI_IsendMPI_Irecv的正确方法,也是放置等待的地方。

举个例子,假设我们有一个有 6 个顶点的图,所以邻接矩阵 (adjMatrix) 将是一个 6*6 矩阵,我们还有一个 6*2 矩阵用于其他一些信息,最后,我们将数据分布在 6 个处理器之间。因此:

|0  20 16 0  6  0 |      |0  1|
|20 0  0  19 0  6 |      |1  1|
addMatrix=|16 0  0  0  12 0 |    M=|2  1|
|0  19 0  0  0  12|      |3  1|
|6  0  12 0  0  9 |      |0  0|
|0  6  0  12 9  0 |      |1  0|

我们按如下方式分配矩阵:

P0:       |0  20 16 0  6  0 |      |0  1|
P1:       |20 0  0  19 0  6 |      |1  1|
P2:       |16 0  0  0  12 0 |      |2  1|
P3:       |0  19 0  0  0  12|      |3  1|
P4:       |6  0  12 0  0  9 |      |0  0|
P5:       |0  6  0  12 9  0 |      |1  0|

现在,每个处理器都需要 更新其adjMatrix部分 。为此,它们需要来自矩阵M的某些部分的信息,该部分位于其他处理器中。例如,为了P0更新20的索引(0,1),它需要有权访问矩阵M的行1,这是{1,1}。因此:

P1应将MLocal[0][0]=1MLocal[0][1]=1发送到P0P0分别以M_j0M_j1接收它们。

P0应将MLocal[0][0]=0MLocal[0][1]=1发送到P1P1分别以M_j0M_j1接收它们。

for(int i=0;i<rows;i++){
for (int j=0; j<n; j++)
{
int M_j0,M_j1;
MPI_Isend(&MLocal[i][0], 1, MPI_INT, j, my_rank+i*n+j+0, MPI_COMM_WORLD, &send_request0);
MPI_Isend(&MLocal[i][1], 1, MPI_INT, j, my_rank+i*n+j+1, MPI_COMM_WORLD, &send_request1);
MPI_Irecv(&M_j0, 1, MPI_INT, j, my_rank+i*n+j+0, MPI_COMM_WORLD, &recv_request0);
MPI_Irecv(&M_j1, 1, MPI_INT, j, my_rank+i*n+j+1, MPI_COMM_WORLD, &recv_request1);
//MPI_Wait(&send_request0, &status);
//MPI_Wait(&send_request1, &status);
MPI_Wait(&recv_request0, &status);
MPI_Wait(&recv_request1, &status);
// Do something ...
}
}

然后根据GillesGouaillardet的建议,我将4MPI_IsendMPI_Irecv改为:

MPI_Sendrecv(&MoatsLocal[i][0], 1, MPI_INT, j, my_rank+i*n+j+0, &M_j0,1, MPI_INT, my_rank, my_rank+i*n+j+0, MPI_COMM_WORLD, &status);
MPI_Sendrecv(&MoatsLocal[i][1], 1, MPI_INT, j, my_rank+i*n+j+1, &M_j1,1, MPI_INT, my_rank, my_rank+i*n+j+1, MPI_COMM_WORLD, &status);

但是,它仍然进入了一个无限循环。

更新:

我更新了代码,问题的某些部分是由于处理器排名和匹配标签。我修复了该部分,但仍然容易死锁,我想我知道问题出在哪里。并且可能无法解决它。如果我有足够的处理器数量,将每行分配给处理器,即 n=p,那就没有任何问题了。但是问题是 处理器数量少于n,那么流不是很好地通过主对角线 我通过示例进行解释,让我们假设我们有 4 个处理器和n=6.假设这里是分布:

P0:       |0  20 16 0  6  0 |      |0  1|
P1:       |20 0  0  19 0  6 |      |1  1|
|16 0  0  0  12 0 |      |2  1|
P2:       |0  19 0  0  0  12|      |3  1|
P3:       |6  0  12 0  0  9 |      |0  0|
|0  6  0  12 9  0 |      |1  0|

这就是循环中的内容。

第一次迭代:

P0

发送和接收 (0,1):"20"和等待(完成)的 P1 信息。

P1 发送和接收 (1,0):"20"和等待(完成)的 P0 信息

P2 发送和接收 (3,1):"19" 的 P1 信息并等待

P3 发送和接收 (4,1):"6" 的 P0 信息并等待

第二次迭代:

P0 发送和接收 (0,2):"16"的 P1 信息并等待

P1 发送和接收 (1,3) 的 P2 信息:"19"并等待(完成)

P2 正在等待 P1 (3,1):"19"然后只是收到它并完成!

P3 正在等待 (4,1):"6"的 P0 并等待

第三次迭代:

P0 正在等待 (0,2):"16" 的 P1

P1 发送和接收 (1,5):"19"的 P3 信息并等待

P2发送和接收 (3,5):"12"的 P3 信息并等待

P3 正在等待 (4,1):"6" 的 P0

第四次迭代:

P0 正在等待 (0,2):"16" 的 P1

P1 正在等待 P3 的 (1,5):"19">

P2 正在等待 P3 的 (3,5):"12">

P3 正在等待 (4,1):"6" 的 P0

现在,所有人都在互相等待,我认为没有任何办法可以解决它。ptb 建议的解决方案可能有效,我会尝试那个。

不过,任何其他想法都是值得赞赏的!

您发布的代码存在一些问题

  1. 每个处理器将循环遍历rows。但是,在您的描述中,行分布在处理器之间,因此这可能是一个错误。
  2. 发送和接收目标和源是相同的。 因此,如果您考虑j=0的情况,MPI_Isend(...,j,...) 表示每个等级都会向根进程发送一些东西。 接下来是调用 MPI_IRecv(...,j,...),MPI_Wait这意味着每个进程都将等待来自永远不会到来的根进程的发送。
  3. MPI_SendRecv调用具有相同的基本问题

挑战在于您需要发送和接收呼叫才能匹配。 一种方法(不一定是性能最高的)是通过循环中通过MPI_Isend发布所有发送,然后使用MPI_Probe,MPI_Recv处理每个排名的 recvs(因为 recvs 的数量是你确切知道的发送数量)。 伪代码示例:

int send_count = 0;
for (int j=0; j<n; j++) {
if (matrix_entry[j] != 0) {
call MPI_Isend(M_local, 2, MPI_INT, j, 0, ...)
send_count++;
}
}
while (send_count) {
MPI_Probe(MPI_ANY_SOURCE, MPI_ANY_TAG, comm, status)
/* get source from status and then call recv */
MPI_Recv(M_j01, 2, MPI_INTEGER, status(MPI_SOURCE), ...)
/* Do something with M_j01 */
send_count--;
} 

一些小建议:

你必须始终记住,每个过程都是独立的。进程之间没有同步(如果您放置MPI_Barrier,则期望)。

我真的不明白你在行上的循环(行 = 6 吗?

然后所有进程都执行代码。 这意味着: P0,1,2,3,4,5,6 调用您的 sendrecv,他们都这样做了 6 次,因为这些调用处于循环中......

最后:矩阵的通常大小是多少? 发送大量非常小的消息是一个非常糟糕的主意。

您应该按如下方式设计算法: 1) 找出进程 PX 需要哪些数据来更新其所有列。 2) 执行通信,为所有进程收集此数据 3) 执行更新。