在 for 循环中使用 MPI_Irecv 和 MPI_Isend

Using MPI_Irecv and MPI_Isend in a for loop

我对 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 更新索引 (0,1)20,它需要访问矩阵 M 的行 1{1,1}。因此:

P1 should send MLocal[0][0]=1 and MLocal[0][1]=1 to P0 in which P0 receives them as M_j0 and M_j1, respectively.

And

P0 should send MLocal[0][0]=0 and MLocal[0][1]=1 to P1 in which P1 receives them as M_j0 and M_j1, respectively.

    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 的建议,我将 4 MPI_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);

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

UPDATE:

我更新了代码,部分问题是因为处理器排名和匹配标签。我修复了那部分,但它仍然容易死锁,我想我知道问题出在哪里。而且可能无法解决。如果我有足够数量的处理器,将每一行分配给一个处理器,即 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 send and receive to/from P1 information for (0,1):"20" and wait(done)

P1 send and receive to/from P0 information for (1,0):"20" and wait(done)

P2 send and receive to/from P1 information for (3,1):"19" and wait

P3 send and receive to/from P0 information for (4,1):"6" and wait

第二次迭代:

P0 send and receive to/from P1 information for (0,2):"16" and wait

P1 send and receive to/from P2 information for (1,3):"19" and wait(done)

P2 was wainting for P1 (3,1):"19" then just recieved it and done!

P3 is waiting for P0 for (4,1):"6" and wait

第三次迭代:

P0 is waiting for P1 for (0,2):"16"

P1 send and receive to/from P3 information for (1,5):"19" and wait

P2 send and receive to/from P3 information for (3,5):"12" and wait

P3 is waiting for P0 for (4,1):"6"

第四次迭代:

P0 is waiting for P1 for (0,2):"16"

P1 is waiting for P3 for (1,5):"19"

P2 is waiting for P3 for (3,5):"12"

P3 is waiting for P0 for (4,1):"6"

现在都在互相等待,我觉得没有什么办法可以解决。 ptb 建议的解决方案可能有效,我会尝试那个。

不过,如果有任何其他想法,我们将不胜感激!

您post编辑

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

挑战在于您需要发送和接收调用匹配。一种方法(不一定是最高性能的)是 post 循环中通过 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) 执行更新。