MPI点到点通信
本文参考了链接Discussion: MPI Basic Point to Point Communication I (mtsu.edu)
总述
在MPI中,点到点通信分为阻塞通信和非阻塞通信。
无论是阻塞通信还是非阻塞通信,消息的发送都有以下四种通信模式(Communication Mode):
- 标准模式:MPI_Send,MPI_Isend
- 缓存(Buffer)模式:MPI_Bsend,MPI_Ibsend
- 就绪(Ready)模式:MPI_Rsend,MPI_Irsend
- 同步(Synchronous)模式:MPI_Ssend,MPI_Issend
消息的接受没有通信模式之分,只有阻塞和非阻塞之分。
通信模式 | 阻塞通信 | 非阻塞通信 |
---|---|---|
同步 | MPI_Ssend | MPI_Issend |
就绪 | MPI_Rsend | MPI_Irsend |
缓存 | MPI_Bsend | MPI_Ibsend |
标准 | MPI_Send | MPI_Isend |
MPI_Recv | MPI_Irecv | |
MPI_Sendrecv | ||
MPI_Sendrecv_replace |
阻塞通信
阻塞发送意味着,在从函数参数列表中指定的位置复制数据之前,发送函数不会返回。因此,可以在发送函数调用后更改数据,而不会影响原始消息。
int send_data = 1;
MPI_Send(&send_data, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
// 阻塞发送意味着下面修改send_data的操作是安全的
// 对于非阻塞发送,下面这条语句可能会导致接收方收到的值为2而不是1,这显然不符合预期
send_data = 2;
阻塞接受意味着,在所有接收到的数据都存储在函数参数列表中指定的变量中之前,接受函数不会返回。因此,可以在调用后使用数据,并确保所有数据都在那里。
int recv_data = 1;
MPI_Recv(&recv_data, 1, MPI_INT, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
// 阻塞接受意味着下面访问recv_data的操作是安全的
// 对于非阻塞发送,下面这条语句可能会导致输出的值为1而不是实际接收到的值,这显然不符合预期
printf("%d", recv_data);
通信模式
阻塞同步发送
基本原理
在下图中,时间从左到右增加。标记为 S 的粗水平线表示发送方的执行时间,标记为 R 的粗虚线表示接收方的执行时间。
MPI_Ssend 执行时,发送方向接收方发送“准备发送”的消息。当接收者执行 MPI_recv 时,它发送一个“准备接收”的消息,然后开始传输数据。
不足
消息传递有两个开销来源:
- 系统开销来自将消息数据从发送方的消息缓冲区复制到网络上,以及将消息数据从网络复制到接收方的消息缓冲区中。
- 同步开销是等待另一个任务发生事件所花费的时间。在上图中,发送方必须等待接收方执行 MPI_Recv 和握手到达才能传输消息。接收方在等待握手完成时也会产生一些同步开销。在同步模式下,同步开销可能很大。上图显示的是 MPI_Ssend 比 MPI_Recv 先到达,因此发送方需要等待更长的时间;不过也有可能是MPI_Recv 比 MPI_Ssend 先到达,此时接受方需要等待更长的时间
阻塞就绪发送
基本原理
MPI_Rsend 只是通过网络将消息发送出去。它要求“准备接收”的通知已经到达,即接收方已经准备开始接受。
不足
-
如果“准备接收”的消息尚未到达,则将会将引发错误。默认情况下,代码将退出。程序员可以将不同的错误处理程序与通信器(communicator)相关联以覆盖此默认行为。
-
就绪模式目的在于最小化发送方的系统开销和同步开销。发送方唯一需要等待的就是直到所有数据都已从发送方的消息缓冲区传输出去。接收方仍然会产生大量的同步开销,这取决于它比相应的发送早多少执行。 除非用户确定相应的接收已准备好,否则不应使用此模式。
阻塞缓冲发送
基本原理
MPI_Bsend 将数据从消息缓冲区复制到用户提供的缓冲区,然后返回。发送方可以继续执行之后的操作,可以修改原始消息缓冲区,因为这些修改不会反映在实际发送的数据中。一旦“准备接收”的通知到达,数据将从用户提供的缓冲区通过网络复制。
不足
-
由于需要将数据从消息缓冲区拷贝到用户提供的缓冲区,这会产生额外的系统开销。
-
在发送方上消除了同步开销,接收方仍然可能产生同步开销。
-
一个好处是用户可以为程序需要发送的消息提供缓冲区。但是,用户负责管理该缓冲区。如果该缓冲区大小不足以存储消息缓冲区中待发送的数据,这将会导致错误,默认情况下程序将退出。
在阻塞缓冲发送下,用户必须提供缓冲区:可以是静态分配的数组,也可以使用 malloc 动态分配缓冲区。用户提供的缓冲区应大于消息数据的总和,因为还必须存储消息头。
此外,必须通过调用 MPI_Buffer_attach 将此空间标识为用户提供的缓冲区。当不再需要它时,应使用 MPI_Buffer_detach 将其分离。一次只能有一个用户提供的消息缓冲区处于活动状态。它将存储多条消息。系统会跟踪消息最终何时离开缓冲区,并将重用缓冲区空间。
阻塞标准发送
消息大小小于设定的阈值
基本原理
在这种情况下,MPI_Send 通过网络将消息复制到接收方的系统缓冲区中,然后返回,发送方可以继续运行。系统缓冲区是在程序启动时附加的,用户不需要管理它。每个进程有一个系统缓冲区可以保存多条消息。执行接收调用时,消息将从系统缓冲区复制到接收任务。
不足
- 缓冲区的使用降低了发送方的同步开销,但复制到缓冲区导致系统开销增加。如果接收方可能产生同步开销
- 如果超出缓冲区大小不够,发送方不会产生错误,而是被阻塞,直到接收方从系统缓冲区中接受数据。因此,发送方仍然可能产生同步开销。
消息大小大于设定的阈值
此时和同步模式是一样的
MPI_Sendrecv 和 MPI_Sendrecv_replace
发送和接收操作可以合并为一个调用。 MPI_Sendrecv 执行阻塞发送和接收,其中发送和接收的缓冲区必须不相交。
MPI_Sendrecv_replace 也做了一个阻塞发送和接收,但只有一个缓冲区,因为接收到的消息会覆盖发送的消息
非阻塞通信
非阻塞调用在发起通信后立即返回。程序员此时并不知道要发送的数据是否已经从发送缓冲区复制出来,或者要接收的数据是否已经到达。因此,在使用消息缓冲区之前,程序员必须检查其状态。程序员可以通过调用 MPI_Wait 及其变体来选择阻塞直到消息缓冲区可以安全使用,或者仅使用 MPI_Test 及其变体返回通信的当前状态。
以下以非阻塞标准发送为例