测试或等待前的非阻塞通信缓冲区操作



MPI标准规定,一旦给非阻塞通信函数一个缓冲区,应用程序不允许使用它,直到操作完成(即,直到TEST或WAIT函数成功之后)。

这也适用于以下情况吗?

我有一个缓冲区,它的每一部分都将到不同的处理器,例如,它的一部分将从处理器本身可用的数据中复制。

我是否允许在每个处理器上MPI_Irecv从其他处理器的缓冲区的不同部分,复制处理器中可用的部分,然后MPI_Isend应该去其他处理器的数据,做我的其他计算,MPI_Waitall所以我的发送和接收完成?

n=0;
for (i = 0; i < size; i++) {
    if (i != rank) {
        MPI_Irecv(&recvdata[i*100], 100, MPI_INT, i, i, comm, &requests[n]);
        n++;
    }
}
process(&recvdata[rank*100], 100);
for (i = 0; i < size; i++) {
    if (i != rank) { 
        MPI_Isend(&senddata[i*100], 100, MPI_INT, i, rank, comm, &requests[n]);
        n++;
    }
}
MPI_Waitall(n, requests, statuses);

我不是百分之百确定我明白你在问什么,所以我先重申一下问题:

如果我有一个大的数据数组,我可以创建非阻塞调用从数组的子集接收数据,然后将数据发送回其他进程吗?

只要在接收和发送之间同步,答案是肯定的。请记住,来自MPI_IRECV的数据直到您完成对MPI_WAIT的调用才会到达,因此在完成调用之前您不能将其发送到另一个进程。否则,发送者将发送出当时缓冲区中恰好存在的任何垃圾。

所以你的代码可以看起来像这样,并且是安全的:

for (i = 0; i < size; i++)
    MPI_Irecv(&data[i*100], 100, MPI_INT, i, 0, comm, &requests[i]);
/* No touching data in here */
MPI_Waitall(i, requests, statuses);
/* You can touch data here */
for (i = 0; i < size; i++)
    MPI_Isend(&data[i*100], 100, MPI_INT, i+1, 0, comm); /* i+1 is wherever you want to send the data */
/* No touching data in here either */
MPI_Waitall(i, requests, statuses);

在整个MPI标准中使用术语位置,而不是术语变量,以防止这种混淆。MPI库并不关心内存来自哪里,因为只要未完成的MPI操作是在不相交的内存位置上操作的。不同的内存位置可以是不同的变量或大数组的不同元素。实际上,整个进程内存可以看作是一个大的匿名字节数组。

在许多情况下,给定不同的变量声明集可以实现相同的内存布局。例如,对于大多数x86/x64 C/c++编译器,以下两组局部变量声明将导致相同的堆栈布局:

int a, b;             int d[3];
int c;             
|     ....     |      |     ....     |    |
+--------------+      +--------------+    |
|      a       |      |     d[2]     |    |
+--------------+      +--------------+    |  lower addresses
|      b       |      |     d[1]     |    |
+--------------+      +--------------+    |
|      c       |      |     d[0]     |   |/
+--------------+      +--------------+    V

在这种情况下:

int a, b;
int c;
MPI_Irecv(&a, 1, MPI_INT, ..., &req[0]);
MPI_Irecv(&c, 1, MPI_INT, ..., &req[1]);
MPI_Waitall(2, &req, MPI_STATUSES_IGNORE);

等价于:

int d[3];
MPI_Irecv(&d[2], 1, MPI_INT, ..., &req[0]);
MPI_Irecv(&d[0], 1, MPI_INT, ..., &req[1]);
MPI_Waitall(2, &req, MPI_STATUSES_IGNORE);

在第二种情况下,虽然d[0]d[2]属于同一个变量,但&d[0]&d[2]指定的内存位置不同,并且与..., 1, MPI_INT, ...结合在一起,是不相交的。

在任何情况下,请确保您没有同时从同一个内存位置读取和写入。

下面是韦斯利·布兰德给出的一个更复杂的例子。它通过使用MPI_Waitsome来重叠发送和接收操作:

MPI_Request rreqs[size], sreqs[size];
for (i = 0; i < size; i++)
    MPI_Irecv(&data[i*100], 100, MPI_INT, i, 0, comm, &rreqs[i]);
while (1)
{
    int done_idx[size], numdone;
    MPI_Waitsome(size, rreqs, &numdone, done_idx, MPI_STATUSES_IGNORE);
    if (numdone == MPI_UNDEFINED)
        break;
    for (i = 0; i < numdone; i++)
    {
        int id = done_idx[i];
        process(&data[id*100], 100);
        MPI_Isend(&data[id*100], 100, MPI_INT, id, 0, comm, &sreqs[id]);
    }
}
MPI_Waitall(size, sreqs, MPI_STATUSES_IGNORE);

在这种特殊情况下,使用size单独的数组可以产生更可读的代码。

最新更新