替代 MPI_Sendrecv_replace

Alternative to MPI_Sendrecv_replace

我正在尝试获取

的替代代码
 if(rank %2==0 && rightNeighbour != MPI_PROC_NULL)
     MPI_Sendrecv_replace(&bufferRight[0], len, MPI_DOUBLE, rightNeighbour, 1, 
                 rightNeighbour, 1, MPI_COMM_WORLD, &status);
 else if(rank%2 ==1 && leftNeighbour != MPI_PROC_NULL)
     MPI_Sendrecv_replace(&bufferLeft[0], len, MPI_DOUBLE, leftNeighbour, 1, 
             leftNeighbour, 1, MPI_COMM_WORLD, &status);

 if (rank % 2 == 1 && rightNeighbour != MPI_PROC_NULL)  
    MPI_Sendrecv_replace(&bufferRight[0], len, MPI_DOUBLE, rightNeighbour, 1,
             rightNeighbour, 1, MPI_COMM_WORLD, &status);
 else if (rank % 2 == 0 && leftNeighbour != MPI_PROC_NULL)   
    MPI_Sendrecv_replace(&bufferLeft[0], len, MPI_DOUBLE, leftNeighbour, 1, 
             leftNeighbour, 1, MPI_COMM_WORLD, &status);

使用 MPI_SendMPI_Recv 但它似乎陷入了僵局。使用 MPI_SendMPI_Recv 做同样的任何简单方法 ?

我试过使用

if(rank %2==0 && rightNeighbour != MPI_PROC_NULL){
    MPI_Recv(&bufferRight[0], len, MPI_DOUBLE, rightNeighbour, 1, 
         MPI_COMM_WORLD, &status);

    MPI_Send(&bufferRight[0], len, MPI_DOUBLE, rightNeighbour, 1, 
         MPI_COMM_WORLD);
}

您最好使用 MPI_IrecvMPI_Isend 而不是阻塞调用(MPI_RecvMPI_Send)。然后,在发出通信例程后,只需等待使用 MPI_Waitall 的请求(或两次调用 MPI_Wait)。但是,要做到这一点,您不能使用相同的缓冲区(即替换)——您需要有两个单独的缓冲区——否则它们会被损坏,因为缓冲区可能会在实际发送之前替换内容。

A 为传入缓冲区,B 为传出缓冲区,您的代码应该类似于

if(rank %2==0 && rightNeighbour != MPI_PROC_NULL){
    MPI_Request req[2];
    MPI_Status status[2];

    MPI_Irecv (&A, len, MPI_DOUBLE, rightNeighbour, 1, 
      MPI_COMM_WORLD, &req[0]);
    MPI_Isend (&B, len, MPI_DOUBLE, rightNeighbour, 1, 
      MPI_COMM_WORLD, &req[1]);

    /* A */

    MPI_Waitall (2, req, status);
}

请注意,在 /* A */ 中,您可以利用通信 飞行 进行一些计算。此外,代码中省略了错误检查——您最好检查 MPI 调用的所有 return 代码。

此代码:

if(rank % 2 == 0 && rightNeighbour != MPI_PROC_NULL)
   MPI_Sendrecv_replace(&bufferRight[0], len, MPI_DOUBLE, rightNeighbour, 1, 
                       rightNeighbour, 1, MPI_COMM_WORLD, &status);
else if(rank % 2 == 1 && leftNeighbour != MPI_PROC_NULL)
   MPI_Sendrecv_replace(&bufferLeft[0], len, MPI_DOUBLE, leftNeighbour, 1, 
                        leftNeighbour, 1, MPI_COMM_WORLD, &status);

相当于:

if(rank % 2 == 0)
{
   MPI_Send(&bufferRight[0], len, MPI_DOUBLE, rightNeighbour, 1,
            MPI_COMM_WORLD);
   MPI_Recv(&bufferRight[0], len, MPI_DOUBLE, rightNeighbour, 1,
            MPI_COMM_WORLD, &status);
}
else if(rank % 2 == 1)
{
   double *temp = malloc(len * sizeof(double));
   MPI_Recv(temp, len, MPI_DOUBLE, leftNeighbour, 1,
            MPI_COMM_WORLD, &status);
   MPI_Send(&bufferLeft[0], len, MPI_DOUBLE, leftNeighbour, 1,
            MPI_COMM_WORLD, &status);
   memcpy(&bufferLeft[0], temp, len * sizeof(double));
   free(temp);
}

请注意,在奇数行列中,发送和接收调用的顺序是相反的。此外,接收使用临时缓冲区来实现 MPI_Sendrecv_replace 的语义,这保证了缓冲区中的数据首先被发送,然后才被接收到的数据覆盖。

请注意,检查排名是否不是 MPI_PROC_NULL 是没有意义的,因为从 MPI_PROC_NULL 发送 to/receive 本质上是空操作,并且总是会成功。 MPI_PROC_NULL 语义的关键思想之一是促进不包含此类 if 的对称代码的编写。

尽管 可能最好的方法是遵循 Harald 的 关于使用 MPI_IsendMPI_Irecv 的建议,但有一种替代方法可能不起作用,具体取决于案.

对于较小的缓冲区大小,一些 MPI 实现遵循所谓的 eager 模式。在这种模式下,无论接收者是否已经在等待消息,消息都会被发送。发送缓冲区数据被复制到临时缓冲区,并且由于复制完成后用户的发送缓冲区可用,MPI_Send returns 在通信实际完成之前.

这有点冒险,因为对于大消息,MPI 通常工作在 rendezvous 模式,实际上 synchronizes 两端,所以以下代码也会产生死锁:

if(rank %2==0 && rightNeighbour != MPI_PROC_NULL){

    MPI_Send(&bufferRight[0], len, MPI_DOUBLE, rightNeighbour, 1, 
         MPI_COMM_WORLD);

    MPI_Recv(&bufferRight[0], len, MPI_DOUBLE, rightNeighbour, 1, 
         MPI_COMM_WORLD, &status);
}

然而,在 MPI_Recv 中,接收缓冲区(显然)在数据已被接收之前不可用。

有关 MPI performance tips 的更多信息。