问题描述
我正在使用 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,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,(rank + 1) % size,&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_Irecv
和 MPI_Isend
而没有调用 {{ 1}}(或 MPI_Wait
)。
MPI_Irecv 和 MPI_Isend 是非阻塞通信例程,因此需要使用 MPI_Wait(或使用 MPI_Test 来测试请求是否完成)以确保消息完成,发送/接收缓冲区中的数据可以再次安全操作。
让我们假设您使用 MPI_Test
发送一个整数数组而不调用 MPI_Isend
;在这种情况下,您不确定何时可以安全地修改(或取消分配)该数组的内存。这同样适用于 MPI_Wait
。尽管如此,调用 MPI_Irecv
可确保从那时起一种情况下读/写(或取消分配内存)缓冲区,而不会出现 undefined behavior 或不一致数据的风险。
在 MPI_Wait
期间,必须读取和发送缓冲区的内容(例如 int 数组);同样,在 MPI_Isend
期间,接收缓冲区的内容必须到达。同时,可以将一些计算与正在进行的过程重叠,但是这种计算不能改变(或读取)发送/接收缓冲区的竞争。然后调用 MPI_Irecv
以确保从那时起,可以安全地读取/修改数据发送/接收,而不会出现任何问题。
在您的代码中,无论您如何调用:
MPI_Wait
之后没有调用 MPI_Irecv(&data,1,MPI_INT,rank - 1,MPI_COMM_WORLD,&request);
。此外,您更改缓冲区的内容即 MPI_Wait
;。如前所述,这会导致未定义的行为。
您可以通过使用 MPI_Recv 和 MPI_Send 或通过调用 data=rank
和 MPI_Irecv
后跟 MPI_Isend
来解决此问题。从语义上讲,调用 MPI_Wait
后调用 MPI_Isend()
或调用 MPI_Wait()
后接 MPI_Recv
与调用 MPI_Wait()
和 {{1} 相同},分别。
我有几个问题:第一个是 (rank + 1) % size 对我来说没有意义。我希望这只是排名 + 1 而不是 (等级 + 1)% 大小。
为了向您解释该表达式背后的原因,让我们考虑一下您的代码,其中包含 4 个进程,等级范围从 0 到 3。
进程 1、2 和 3 将调用:
MPI_Send()
- 进程 1 期待来自进程 0 的消息;
- 进程 2 期待来自进程 1 的消息;
- 进程 3 需要一个消息表单进程 2;
然后所有四个进程都调用(让我们相应地替换公式 (rank + 1) % size):
MPI_Recv()
- 进程 0 从进程 (0 + 1) % 4 -> 1 发送消息;
- 进程 1 从进程 (1 + 1) % 4 -> 2 发送消息;
- 进程 2 从进程 (2 + 1) % 4 -> 3 发送消息;
- 进程 3 从进程 (3 + 1) % 4 -> 0 发送消息;
所以 MPI_Irecv(&data,&request);
被用作一个技巧,这样当你到达最后一个等级时,它会返回第一个的等级。
所有这些都构建了以下接收/发送消息模式 MPI_Isend(&data,(rank + 1) % size,&request);
您可能已经注意到,(rank + 1) % size
和 0 -> 1 -> 2 -> 3 -> 0.
分别被调用了 3 次和 4 次。即使您没有遇到任何与此相关的问题,但通常情况下,不执行相同数量的 MPI_Recv
/MPI_Send
调用可能会导致死锁。如果不使用 MPI_Recv
而只使用 MPI_Send
并过滤掉调用 (rank + 1) % size
的最后一个进程,则可以避免这种情况,如下所示:
rank + 1
这意味着进程 3 不会向进程 0 发送消息,但这没关系,因为进程 0 无论如何都不希望收到任何消息。
运行示例:
MPI_Isend
输出: 使用 4 个进程可能的输出:
if(rank + 1 < size)
MPI_Isend(&data,(rank + 1) 0,&request);