81

我无法理解 MPI 中阻塞通信和非阻塞通信的概念。两者有什么区别?有什么优点和缺点?

4

5 回答 5

107

阻止通信是使用MPI_Send()和完成的MPI_Recv()。这些函数在通信完成之前不会返回(即它们阻塞)。稍微简化一下,这意味着传递给的缓冲区MPI_Send()可以被重用,要么是因为 MPI 将它保存在某个地方,要么是因为它已被目标接收。同样,MPI_Recv()当接收缓冲区已填满有效数据时返回。

相反,非阻塞通信是使用MPI_Isend()和完成的MPI_Irecv()。即使通信尚未完成,这些函数也会立即返回(即它们不会阻塞)。您必须致电MPI_Wait()MPI_Test()查看通信是否已完成。

在足够的情况下使用阻塞通信,因为它更容易使用。必要时使用非阻塞通信,例如,您可以调用MPI_Isend(),进行一些计算,然后执行MPI_Wait()。这允许计算和通信重叠,这通常会提高性能。

请注意,集体通信(例如,all-reduce)仅在最高 MPIv2 的阻塞版本中可用。IIRC,MPIv3 引入了非阻塞集体通信。

可以在这里查看MPI 发送模式的快速概览。

于 2012-04-04T18:49:24.083 回答
73

这篇文章虽然有点旧,但我认为可以接受的答案。声明“这些函数在通信完成之前不会返回”有点误导,因为阻塞通信并不能保证任何握手 b/w 发送和接收操作。

首先需要知道,send 有四种通信模式:Standard、Buffered、SynchronousReady,每一种都可以是阻塞非阻塞的

与发送不同,接收只有一种模式,可以是阻塞非阻塞

在继续之前,还必须清楚我明确提到哪个是MPI_Send\Recv 缓冲区,哪个是系统缓冲区(这是 MPI 库拥有的每个处理器中的本地缓冲区,用于在通信的队列之间移动数据团体)

阻塞通信:阻塞并不意味着消息已传递到接收方/目的地。它只是意味着(发送或接收)缓冲区可供重用。要重用缓冲区,将信息复制到另一个内存区域就足够了,即库可以将缓冲区数据复制到库中自己的内存位置,然后,例如,MPI_Send 可以返回。

MPI 标准非常清楚地将消息缓冲与发送和接收操作分离。一旦消息被缓冲,阻塞发送就可以完成,即使没有发布匹配的接收。但在某些情况下,消息缓冲可能很昂贵,因此直接从发送缓冲区复制到接收缓冲区可能是有效的。因此,MPI Standard 提供了四种不同的发送模式,让用户可以自由选择适合其应用程序的发送模式。让我们看一下每种通信方式会发生什么:

1.标准模式

标准模式下,是否缓冲传出消息取决于 MPI 库。在库决定缓冲传出消息的情况下,发送甚至可以在匹配的接收被调用之前完成。在库决定不缓冲的情况下(出于性能原因,或由于缓冲区空间不可用),发送将不会返回,直到匹配的接收已发布并且发送缓冲区中的数据已移动到接收缓冲区。

因此,标准模式下的 MPI_Send 是非本地的,因为无论是否已发布匹配的接收,都可以启动标准模式下的发送,并且其成功完成可能取决于匹配接收的发生(因为它是实现取决于消息是否会被缓冲)。

标准发送的语法如下:

int MPI_Send(const void *buf, int count, MPI_Datatype datatype, 
             int dest, int tag, MPI_Comm comm)

2.缓冲模式

与标准模式一样,可以在缓冲模式下启动发送,而不管匹配的接收是否已发布,并且发送可能在匹配的接收已发布之前完成。然而,主要区别在于如果发送被盯着并且没有匹配的接收被发布,则必须缓冲传出消息。请注意,如果发布了匹配的接收,则缓冲发送可以愉快地与开始接收的处理器会合,但如果没有接收,则缓冲模式下的发送必须缓冲传出消息以允许发送完成。总的来说,缓冲发送是本地的。在这种情况下,缓冲区分配是用户定义的,如果缓冲区空间不足,则会发生错误。

缓冲区发送的语法:

int MPI_Bsend(const void *buf, int count, MPI_Datatype datatype,
             int dest, int tag, MPI_Comm comm)

3.同步模式

在同步发送模式下,无论是否发布了匹配的接收,都可以开始发送。但是,只有在发布了匹配的接收并且接收方已开始接收同步发送发送的消息时,发送才会成功完成。同步发送的完成不仅表示发送中的缓冲区可以被复用,同时也表示接收进程已经开始接收数据。如果发送和接收都阻塞,则在通信处理器会合之前,通信在任一端都没有完成。

同步发送的语法:

int MPI_Ssend(const void *buf, int count, MPI_Datatype datatype, int dest,
              int tag, MPI_Comm comm)

4. 就绪模式

与前三种模式不同,只有在匹配的接收已经过帐时,才可以在就绪模式下启动发送。发送的完成并不表示任何关于匹配接收的信息,而只是告诉发送缓冲区可以被重用。使用就绪模式的发送具有与标准模式或同步模式相同的语义,并带有有关匹配接收的附加信息。可以用同步发送或标准发送替换具有就绪通信模式的正确程序,除了性能差异外,对结果没有影响。

准备发送的语法:

int MPI_Rsend(const void *buf, int count, MPI_Datatype datatype, int dest, 
              int tag, MPI_Comm comm)

在经历了所有 4 个阻塞发送之后,它们可能看起来在原则上有所不同,但根据实现,一种模式的语义可能与另一种相似。

例如 MPI_Send 通常是一种阻塞模式,但取决于实现,如果消息大小不是太大,MPI_Send 会将传出消息从发送缓冲区复制到系统缓冲区(现代系统中大多数情况下)并立即返回。让我们看一个下面的例子:

//assume there are 4 processors numbered from 0 to 3
if(rank==0){
    tag=2;
    MPI_Send(&send_buff1, 1, MPI_DOUBLE, 1, tag, MPI_COMM_WORLD);
    MPI_Send(&send_buff2, 1, MPI_DOUBLE, 2, tag, MPI_COMM_WORLD);
    MPI_Recv(&recv_buff1, MPI_FLOAT, 3, 5, MPI_COMM_WORLD);
    MPI_Recv(&recv_buff2, MPI_INT, 1, 10, MPI_COMM_WORLD);
}

else if(rank==1){
     tag = 10;
    //receive statement missing, nothing received from proc 0
    MPI_Send(&send_buff3, 1, MPI_INT, 0, tag, MPI_COMM_WORLD);
    MPI_Send(&send_buff3, 1, MPI_INT, 3, tag, MPI_COMM_WORLD);
}

else if(rank==2){
    MPI_Recv(&recv_buff, 1, MPI_DOUBLE, 0, 2, MPI_COMM_WORLD);
    //do something with receive buffer
}

else{ //if rank == 3
    MPI_Send(send_buff, 1, MPI_FLOAT, 0, 5, MPI_COMM_WORLD);
    MPI_Recv(recv_buff, 1, MPI_INT, 1, 10, MPI_COMM_WORLD);
}

让我们看看上面例子中每个等级发生了什么

Rank 0 尝试发送到 rank 1 和 rank 2,并从 rank 1 和 3 接收。

Rank 1正在尝试发送到 rank 0 和 rank 3 并且不接收来自任何其他 rank 的任何内容

Rank 2正在尝试从 rank 0 接收,然后对 recv_buff 中接收到的数据进行一些操作。

Rank 3正在尝试发送到 rank 0 并从 rank 1 接收

初学者感到困惑的是 rank 0 正在发送到 rank 1 但 rank 1 没有开始任何接收操作,因此通信应该阻塞或停止,并且 rank 0 中的第二个发送语句根本不应该执行(这就是 MPI文档强调它是实现定义的传出消息是否将被缓冲)。在大多数现代系统中,这些小尺寸的消息(这里大小为 1)很容易被缓冲,MPI_Send 将返回并执行其下一个 MPI_Send 语句。因此在上面的例子中,即使 rank 1 中的接收没有开始,rank 0 中的第一个 MPI_Send 也会返回并执行它的下一条语句。

在 rank 3 在 rank 0 之前开始执行的假设情况下,它会将第一个 send 语句中的传出消息从发送缓冲区复制到系统缓冲区(在现代系统中;)),然后开始执行其接收语句。一旦 rank 0 完成它的两个发送语句并开始执行它的接收语句,rank 3 在系统中缓冲的数据被复制到 rank 0 的接收缓冲区中。

如果在处理器中启动了接收操作并且没有发布匹配的发送,则该进程将阻塞,直到接收缓冲区充满它所期望的数据。在这种情况下,除非 MPI_Recv 返回,否则计算或其他 MPI 通信将被阻止/停止。

了解了缓冲现象之后,应该回过头来更多地考虑MPI_Ssend,它具有阻塞通信的真正语义。即使 MPI_Ssend 将传出消息从发送缓冲区复制到系统缓冲区(这也是实现定义的),必须注意 MPI_Ssend 不会返回,除非发送处理器收到来自接收进程的一些确认(低级格式)。

幸运的是,MPI 决定让用户在接收方面更轻松,阻塞通信中只有一个接收: MPI_Recv,并且可以与上述四种发送模式中的任何一种一起使用。对于 MPI_Recv,阻塞意味着接收仅在其缓冲区中包含数据后才返回。这意味着接收只能在匹配的发送开始后才能完成,但并不意味着它是否可以在匹配的发送完成之前完成。

在这种阻塞调用期间发生的情况是计算被暂停,直到阻塞的缓冲区被释放。这通常会导致计算资源的浪费,因为 Send/Recv 通常将数据从一个内存位置复制到另一个内存位置,而 cpu 中的寄存器保持空闲。

非阻塞通信:对于非阻塞通信,应用程序为发送和/或接收创建通信请求并取回句柄,然后终止。这就是保证流程被执行所需要的一切。即通知 MPI 库必须执行该操作。

对于发送方,这允许与通信重叠计算。

对于接收方,这允许重叠一部分通信开销,即将消息直接复制到应用程序中接收方的地址空间中。

于 2017-10-31T17:36:27.330 回答
10

在使用阻塞通信时,您必须关心发送和接收呼叫,例如查看此代码

 if(rank==0)
 {
     MPI_Send(x to process 1)
     MPI_Recv(y from process 1)
 }
 if(rank==1)
 {
     MPI_Send(y to process 0);
     MPI_Recv(x from process 0);
 }

在这种情况下会发生什么?

  1. 进程 0 将 x 发送到进程 1 并阻塞,直到进程 1 收到 x。
  2. 进程 1 将 y 发送给进程 0 并阻塞,直到进程 0 收到 y,但是
  3. 进程 0 被阻塞,使得进程 1 无限阻塞,直到两个进程被杀死。
于 2012-04-04T19:33:55.947 回答
4

这很容易。

非阻塞意味着单个进程的计算和传输数据可以同时发生。

阻塞意味着,嘿,伙计,你必须确保你已经完成了数据传输然后返回完成下一个命令,这意味着如果有一个传输后有一个计算,那么计算必须在传输成功之后。

于 2015-06-23T06:12:37.593 回答
3

公认的答案和另一个很长的答案都提到计算和通信的重叠是一个优势。那是 1. 不是主要动机,以及 2. 很难实现。非阻塞通信的主要优点(也是最初的动机)是您可以表达复杂的通信模式而不会出现死锁,也不会不必要地序列化自己的进程。

示例: 死锁:每个人都进行接收,然后每个人都进行发送,例如沿着环。这会挂起。

序列化:沿着线性顺序,除了最后一个之外的每个人都向右发送,然后除了第一个之外的每个人都从左边接收。这将使所有进程按顺序而不是并行执行。

于 2021-09-09T13:22:20.557 回答