对MPI非阻塞通信的初印象。

非阻塞通信与阻塞通信的区别

对于非阻塞通信的理解还是要理解他和阻塞通信的区别。这个区别书上扯了一大堆好像也没有用简单通俗的语言总结这两者之间的区别让人看着后面的例子一开始有点头晕。那其实区别很简单:

阻塞通信

阻塞通信就是使用前面几篇博客里面的那些发送和接收函数,例如MPI_Send()MPI_Recv(),这些函数有一个特点就是这些函数在通信完成之前是不会返回的,也就是阻塞

那什么时候是通信完成呢?

  • 对于发送端,所谓完成就是指发送缓冲区可以被安全的在使用,也就意味着之前发送缓冲区的数据已经被成功的转移走了,可能被MPI保存在其他地方,或者放到了接收端的缓冲区中。
  • 对于接收端,所谓的完成就是指数据已经成功的复制到了接受缓冲区中,接收进程可以放心使用缓冲区。

非阻塞通信

相比之下,非阻塞通信就是使用MPI_Isend()MPI_Irecv()等函数完成通信的过程。这些函数没有阻塞,会立即返回,尽管通信还没有完成,也可以返回。但是必须在后面调用MPI_Wait()来查看通信是否完成。
这样我们就可以在告诉让MPI进行消息传送或者接受之后去做其他的事情,这样计算和通信并发进行,会提升效率。

非阻塞通信业遵循一定的执行顺序

在这里我写了个例子来看看非阻塞通信的执行顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include "mpi.h"
#include <stdio.h>

int main(int argc, char ** argv)
{
int sba[1] = {-1}, sbb[1] = {1};
int rba[1], rbb[1];
int rank, nproc;

int flag1, flag2;
MPI_Comm comm = MPI_COMM_WORLD;
MPI_Request r1, r2;
MPI_Status status1, status2;

MPI_Init(&argc, &argv);
MPI_Comm_rank(comm, &rank);
MPI_Comm_size(comm, &nproc);

if (rank == 0)
{
fprintf(stderr, "proc %d: sba = [%d]\n", rank, sba[0]);
fprintf(stderr, "proc %d: sbb = [%d]\n", rank, sbb[0]);
fflush(stderr);

MPI_Isend(sba, 1, MPI_INT, 1, 0, comm, &r1);
MPI_Isend(sbb, 1, MPI_INT, 1, 0, comm, &r2);

MPI_Wait(&r1, &status1);
MPI_Wait(&r2, &status2);
}
else if (rank == 1)
{
MPI_Irecv(rba, 1, MPI_INT, 0, MPI_ANY_TAG, comm, &r1);
MPI_Irecv(rbb, 1, MPI_INT, 0, 0, comm, &r2);

MPI_Wait(&r1, &status1);
MPI_Wait(&r2, &status2);

fprintf(stderr, "proc %d: rba = [%d]\n", rank, rba[0]);
fprintf(stderr, "proc %d: rbb = [%d]\n", rank, rbb[0]);
fflush(stderr);
}

MPI_Finalize();

return 0;
}

编译执行可以看到:
1
2
3
4
5
6
[zjshao@master 2-2]$ mpicc nonblocking_order.c -o nonblocking_order.x
[zjshao@master 2-2]$ mpiexec -n 2 -host node01 nonblocking_order.x
proc 0: sba = [-1]
proc 0: sbb = [1]
proc 1: rba = [-1]
proc 1: rbb = [1]

进程0的第一个发送会与进程1的第一个接收配对,第二个也是这样。即使我交换了接受端的接受顺序,也是第一个配对第一个,第二个配对第二个这样。

通信结束测试

通信对象

非阻塞通信使用了等待函数,该函数使用了非阻塞通信对象来管理通信动作完成与否的信息。
例如上面的

1
2
int flag1, flag2;
MPI_Request r1, r2;

这些都是通信对象,主要用于识别通信操作,并在启动和结束通信的动作之间建立关联。

通信测试函数

  • MPI_Wait()

    1
    int MPI_Wait(MPI_Request *request, MPI_Status *status)

    此函数是非本地操作,他的返回意味着相应通信对象所代表的通信过程已经完成了。

  • MPI_Test()

    1
    int MPI_Test(MPI_Request *request, int *flag, MPI_Status *status)

    此函数是一个本地函数,它仅仅进行测试,不保证通信过程完成。
    下面我在上面的代码基础上进行测试。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    ...
    else if (rank == 1)
    {
    MPI_Irecv(rba, 1, MPI_INT, 0, MPI_ANY_TAG, comm, &r1);
    MPI_Irecv(rbb, 1, MPI_INT, 0, 0, comm, &r2);

    MPI_Test(&r1, &flag1, &status1);
    MPI_Test(&r2, &flag2, &status2);

    fprintf(stderr, "proc %d: rba = [%d], t = %f, flag1 = %d\n", rank, rba[0], MPI_Wtime(), flag1);
    fprintf(stderr, "proc %d: rbb = [%d], t = %f, flag2 = %d\n", rank, rbb[0], MPI_Wtime(), flag2);

    MPI_Wait(&r1, &status1);
    MPI_Wait(&r2, &status2);

    MPI_Test(&r1, &flag1, &status1);
    MPI_Test(&r2, &flag2, &status2);

    fprintf(stderr, "proc %d: rba = [%d], t = %f, flag1 = %d\n", rank, rba[0], MPI_Wtime(), flag1);
    fprintf(stderr, "proc %d: rbb = [%d], t = %f, flag2 = %d\n", rank, rbb[0], MPI_Wtime(), flag2);

    fflush(stderr);
    }
    ...

    编译执行可以看到:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [zjshao@master 2-2]$ mpicc nonblocking_order.c -o nonblocking_order.x
    [zjshao@master 2-2]$ mpiexec -n 2 -host node01 nonblocking_order.x
    proc 0: sba = [-1]
    proc 0: sbb = [1]
    proc 1: rba = [1], t = 1468052801.197011, flag1 = 0
    proc 1: rbb = [4197776], t = 1468052801.197031, flag2 = 0
    proc 1: rba = [-1], t = 1468052801.197051, flag1 = 1
    proc 1: rbb = [1], t = 1468052801.197057, flag2 = 1
    [zjshao@master 2-2]$

    第一次调用MPI_Test()的时候通信并没有完成,数据也是随机的值,flag仍未false
    在调用MPI_Wait()之后表示通信完成,再用MPI_Test()进行测试,这时候flag就设置成了true

Comments