并行计算MPI(入门)——通信模式
我们已经了解了点对点通信的几乎所有重要内容。 有一件事我还没讲到,也许大家有兴趣了解一下 MPI。
如果您做过一些实验,您可能会注意到在进行屏蔽通信时出现的一些尴尬情况。 有时,阻塞通信似乎没有阻塞,而有时,却似乎阻塞了。 真奇怪。
对于那些没有遇到过这种奇怪行为的人,这里有一个小例子可以说服你们:
#include <iostream>
#include <unistd.h>
#include <mpi.h>
int main(int argc, char **argv) {
MPI_Init(&argc, &argv);
int size, rank;
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// We have two buffers, one big and one small
constexpr int small_count = 50;
constexpr int big_count = 100000;
int buff1[small_count];
int buff2[big_count];
// Waiting time, play with this value if you think the difference is due to the quantity of data to send
constexpr int waiting_time = 5; // in seconds
if (rank == 0) {
// We initialize both buffers
for (int i=0; i < big_count; ++i) {
if (i < small_count)
buff1[i] = i;
buff2[i] = i;
}
// Synchronisation 1
MPI_Barrier(MPI_COMM_WORLD);
// We register the current time
double time = -MPI_Wtime();
// We send the buffer immediately
MPI_Send(buff1, small_count, MPI_INT, 1, 0, MPI_COMM_WORLD);
// We print the time it took us to complete this send
std::cout << "Time elapsed to complete blocking send 1 : " << time + MPI_Wtime() << "s" << std::endl;
// Synchronisation 2
MPI_Barrier(MPI_COMM_WORLD);
time = -MPI_Wtime();
// We send the second buffer
MPI_Send(buff2, big_count, MPI_INT, 1, 1, MPI_COMM_WORLD);
// We print the time again
std::cout << "Time elapsed to complete blocking send 2 : " << time + MPI_Wtime() << "s" << std::endl;
}
else {
// Synchronisation 1
MPI_Barrier(MPI_COMM_WORLD);
sleep(waiting_time);
// Receiving buffer 1
MPI_Recv(buff1, small_count, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
std::cout << "Just received buffer 1" << std::endl;
// Synchronisation 2
MPI_Barrier(MPI_COMM_WORLD);
sleep(waiting_time);
// Receiving buffer 2
MPI_Recv(buff2, big_count, MPI_INT, 0, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
std::cout << "Just received buffer 2" << std::endl;
}
MPI_Finalize();
return 0;
}
本例显示了两个阻塞通信。 一个在小缓冲区(50 个元素)上执行,另一个在大缓冲区(100000 个元素)上执行。 进程 0 只发送缓冲区,并打印发送时间和发送完成时间。 进程 1 将等待(演示阻塞特性),然后接收,并在接收完成后向用户发出警告。
您可以很容易地注意到这种奇怪的行为:第一次发送比第一次接收早完成,而对于大缓冲区来说,两个操作似乎是同时进行的。
标准模式(MPI_Send)
标准模式(MPI_Send)实际上是一种 “非模式”,它允许 MPI 实现选择哪种通信模式更可取。 这可能在很大程度上取决于实现。 在 OpenMPI 中,观察到的行为是,对于短消息,发送会自动缓冲,而对于长消息,消息的发送模式在某种程度上接近同步模式。
缓冲模式(MPI_Bsend)
缓冲模式(MPI_Bsend)将所有要发送的数据存储在临时缓冲区中,然后返回执行,就像非阻塞发送模式一样。 这样做的好处是,即使尚未调用相应的阻塞 Recv,也能立即继续执行。 另一方面,缓冲模式会将缓冲区的所有数据复制到内存的另一个区域,从而重复数据。 如果要传输大量数据,这可能会对内存造成危害。\
就绪模式(MPI_Rsend)
只有在相应的接收已被调用的情况下,就绪模式(MPI_Rsend)才能启动。 这样,您的程序就可以从信息初始化的一些额外开销中获得时间。 如果尚未调用相应的 Recv,那么将接收到的消息 //might// 将是定义不明确的,因此在调用就绪模式之前,必须确保接收进程已请求 Recv。
同步模式(MPI_Ssend)
最后,同步模式(MPI_Ssend)将等待相应的 Recv 完成。 数据传输将在这一精确时刻进行,确保两个进程都为传输做好了准备。