【问题标题】:Using MPI_Irecv and MPI_Isend in a for loop在 for 循环中使用 MPI_Irecv 和 MPI_Isend
【发布时间】:2018-09-13 12:26:11
【问题描述】:

我对@9​​87654322@ 和MPI_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 应该将MLocal[0][0]=1MLocal[0][1]=1 发送到P0 其中P0 分别以M_j0M_j1 接收它们。

P0 应该将MLocal[0][0]=0MLocal[0][1]=1 发送到P1 其中P1 分别以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 的建议,我将 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);

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

更新:

我更新了代码,部分问题是因为处理器排名和匹配标签。我修复了那个部分,但它仍然容易出现死锁,我想我知道问题出在哪里。而且可能无法解决。如果我有足够数量的处理器,将每条线分配给一个处理器,即 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 向/从 P1 发送和接收 (0,1):"20" 的信息并等待(完成)

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

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

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

第二次迭代:

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

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

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

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

第三次迭代:

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

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

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

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

第四次迭代:

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

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

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

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

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

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

【问题讨论】:

  • 可以使用4个请求的数组,然后MPI_Waitall(4, ...);
  • 你也可以使用两个MPI_Sendrecv()
  • @GillesGouaillardet 我照你说的做了。仍然有同样的问题。
  • 然后请上传minimal reproducible example
  • @GillesGouaillardet 我更新了它。任何帮助表示赞赏。

标签: c++ matrix mpi distributed-computing distributed-system


【解决方案1】:

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

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

挑战在于您需要匹配您的 send 和 recv 调用。一种方法(不一定是性能最高的)是通过 MPI_Isend 在一个循环中发布所有发送,然后使用 MPI_Probe、MPI_Recv 来处理每个等级的接收(因为接收的数量是发送的数量,你确切知道如何许多)。伪代码示例:

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--;
} 

【讨论】:

  • 谢谢,你是对的,排名和标签出了问题,我只是修复它。不过,我遇到了另一个导致容易出现死锁的问题。我更新了我的问题并提供了更多详细信息。有什么想法吗?
  • 您仍在组合发送/接收操作,而不是发布所有发送,然后继续处理接收。您编写它的方式本质上是同步/阻塞,因为您在发送后立即调用等待。你可以这样做,但是你需要付出更多的努力来组织你的发送/接收,以便它们兼容。
【解决方案2】:

一些小建议:

你总是要记住每个进程都是独立的。进程之间没有同步(如果你放了一个 MPI_Barrier)。

我真的不明白你对行的循环(行 = 6 吗?)

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

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

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

【讨论】:

    猜你喜欢
    • 2020-03-09
    • 2015-05-27
    • 1970-01-01
    • 2017-08-20
    • 2011-08-01
    • 1970-01-01
    • 2015-12-09
    • 2017-10-01
    • 2016-01-06
    相关资源
    最近更新 更多