简单的你好世界非阻塞 MPI

Simple hello world non blocking MPI

我正在使用 this website 练习一个简单的非阻塞“Hello world”程序。

#include <iostream>
#include <mpi.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    MPI_Init(&argc, &argv);
    MPI_Request request;
    MPI_Status  status;

    int size, rank, data;

    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    MPI_Comm_size(MPI_COMM_WORLD, &size);

    if (rank>0) {
    MPI_Irecv(&data, 1, MPI_INT, rank - 1, 0,  MPI_COMM_WORLD,&request);

      std::cout << "Rank " << rank << " has received message with data " << data<< " from rank " << rank - 1
              << std::endl;
    }

    std::cout << "Hello from rank " <<rank << " out of " << size<< std::endl;

    data=rank;

    MPI_Isend(&data, 1, MPI_INT, (rank + 1) % size, 0, MPI_COMM_WORLD, &request);

    MPI_Finalize();
    return 0;
}

我有几个问题:第一个是 (rank + 1) % size 对我来说没有意义。我希望这只是 rank+1 而不是 (rank + 1) % size。但是,当我删除 %size 时,代码不会 运行。我的第二个歧义是这个特定代码的结果:

#PTP job_id=12493
Rank 3 has received message with data 21848 from rank 2
Hello from rank 3 out of 4
Hello from rank 0 out of 4
Rank 2 has received message with data 22065 from rank 1
Hello from rank 2 out of 4
Rank 1 has received message with data 22043 from rank 0
Hello from rank 1 out of 4

我已将数据定义为等于等级,但它似乎随机抛出一些东西。这是为什么?

TL;DR 您代码中的主要问题(很可能)是数据显示随机值的原因是 MPI_IrecvMPI_Isend 的使用没有MPI_Wait(或MPI_Test)的调用。

MPI_Irecv and MPI_Isend are nonblocking communication routines, therefore one needs to use the MPI_Wait (or use MPI_Test测试请求是否完成)确保消息完成,send/receive缓冲区中的数据可以再次安全 操纵。

让我们想象一下,您使用 MPI_Isend 发送一个整数数组而不调用 MPI_Wait;在这种情况下,您不确定何时可以安全地修改(或取消分配)该数组的内存。 MPI_Irecv也是如此。尽管如此,调用 MPI_Wait 可确保从那一点开始 read/write (或释放内存)缓冲区没有 undefined behavior 或不一致数据的风险。

MPI_Isend期间必须读取和发送缓冲区的内容(例如整数数组);同样,在 MPI_Irecv 期间,接收缓冲区的内容必须到达。同时,可以与正在进行的过程重叠一些计算,但是这个计算不能改变(或读取)send/recv 缓冲区的内容。 然后调用 MPI_Wait 以确保从那时起数据 send/recv 可以安全地 read/modified 没有任何问题。

在您的代码中,无论您如何调用:

MPI_Irecv(&data, 1, MPI_INT, rank - 1, 0,  MPI_COMM_WORLD,&request);

之后不调用 MPI_Wait。此外,您更改缓冲区的内容 ,即 data=rank;。如前所述,这可能导致 未定义的行为

您可以通过使用 MPI_Recv and MPI_Send 或调用 MPI_IrecvMPI_Isend 然后调用 MPI_Wait 来解决这个问题。从语义上讲,调用 MPI_Isend() 后调用 MPI_Wait() 或调用 MPI_Recv 后调用 MPI_Wait() 与调用 MPI_Send() 和 [=37= 相同], 分别.

I have a couple of problems: the first one is (rank + 1) % size does not make sense to me. I expect this to be just rank+1 rahther than (rank + 1) % size.

为了向您解释该表达式背后的原因,让我们考虑一下您的代码有 4 个进程,等级从 0 到 3。

进程 1、2 和 3 将调用:

MPI_Irecv(&data, 1, MPI_INT, rank - 1, 0,  MPI_COMM_WORLD,&request);
  • 进程 1 需要来自进程 0 的消息;
  • 进程 2 需要来自进程 1 的消息;
  • 进程 3 需要进程 2 的消息;

然后所有四个进程调用(让我们相应地替换公式 (rank + 1) % size):

MPI_Isend(&data, 1, MPI_INT, (rank + 1) % size, 0, MPI_COMM_WORLD, &request);
  • 进程 0 从进程 (0 + 1) % 4 -> 1 发送消息;
  • 进程 1 从进程 (1 + 1) % 4 -> 2 发送消息;
  • 进程 2 从进程 (2 + 1) % 4 -> 3 发送消息;
  • 进程 3 从进程 (3 + 1) % 4 -> 0 发送消息;

所以 (rank + 1) % size 被用作一个技巧,这样当你达到最后一个排名时,它 returns 回到第一名的排名。

所有这些都是为了构建 recv/send 消息的以下模式 0 -> 1 -> 2 -> 3 -> 0.

您可能已经注意到,MPI_RecvMPI_Send 分别被调用了 3 次和 4 次。即使您没有遇到任何与此相关的问题,通常,不执行相同数量的 MPI_Recv/MPI_Send 调用也可能会导致死锁。如果不使用 (rank + 1) % size 而只使用 rank + 1 并过滤掉调用 MPI_Isend 的最后进程,则可以避免这种情况,如下所示:

if(rank + 1 < size)
  MPI_Isend(&data, 1, MPI_INT, (rank + 1) 0, MPI_COMM_WORLD, &request);

这意味着进程 3 不会向进程 0 发送消息,但这没关系,因为进程 0 无论如何都不希望收到任何消息。

A 运行 示例:

#include <iostream>
#include <mpi.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    MPI_Init(&argc, &argv);
    MPI_Request request;
    MPI_Status  status;

    int size, rank, data;

    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    if (rank>0) {
       MPI_Irecv(&data, 1, MPI_INT, rank - 1, 0,  MPI_COMM_WORLD,&request);
       MPI_Wait(&request, &status);
       std::cout << "Rank " << rank << " has received message with data " << data<< " from rank " << rank - 1
              << std::endl;
    }

    std::cout << "Hello from rank " <<rank << " out of " << size<< std::endl;
    data=rank;

   if(rank + 1 < size){
       MPI_Isend(&data, 1, MPI_INT, (rank + 1) 0, MPI_COMM_WORLD, &request);
    }
    MPI_Wait(&request, &status);
    MPI_Finalize();

    return 0;
}

输出: 使用 4 个进程可能的输出:

Hello from rank 0 out of 4
Rank 1 has received message with data 0 from rank 0
Hello from rank 1 out of 4
Rank 2 has received message with data 1 from rank 1
Hello from rank 2 out of 4
Rank 3 has received message with data 2 from rank 2
Hello from rank 3 out of 4