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

Posted

技术标签:

【中文标题】测试或等待之前的非阻塞通信缓冲区操作【英文标题】:Non-blocking communication buffer manipulation before test or wait 【发布时间】:2015-07-27 16:21:02 【问题描述】:

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);

【问题讨论】:

只要senddata[]recvdata[]不重叠,代码就完美了。 【参考方案1】:

我不是 100% 确定我明白你在问什么,所以我会先重申这个问题:

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

答案是肯定的,只要您在接收和发送之间进行同步。请记住,在您完成与MPI_WAIT 的调用之前,来自MPI_IRECV 的数据不会到达,因此在发生这种情况之前您不能将其发送到另一个进程。否则,发送将发送出当时碰巧在缓冲区中的任何垃圾。

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

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);

【讨论】:

谢谢,我已经更正了我的问题,使其更加清晰。【参考方案2】:

在整个 MPI 标准中,使用术语 locations 而不是术语 variables 以防止这种混淆。 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] 属于同一个变量,但 &amp;d[0]&amp;d[2] 指定不同的并且 - 与 ..., 1, MPI_INT, ... 组合 - 不相交的内存位置。

在任何情况下,请确保您不会同时读取和写入同一内​​存位置。

下面是 Wesley Bland 给出的示例的某种更复杂的版本。它通过使用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 单独的数组可能会导致代码更具可读性。

【讨论】:

感谢@hristo-iliev,我从您的评论中了解到,只要不同时读取和写入同一内​​存位置,我就可以接收大数组的不同部分并操作其他部分。 是的,这基本上意味着您只能从当前未写入的位置发送,即如果有未完成的非阻塞接收,您必须先等待它完成才能处理数据并发送。反之亦然。

以上是关于测试或等待之前的非阻塞通信缓冲区操作的主要内容,如果未能解决你的问题,请参考以下文章

java nio学习三:NIO 的非阻塞式网络通信

父进程和外部子进程之间真正的非阻塞双向通信

Java入门系列-25-NIO(实现非阻塞网络通信)

NIO实现TCP的非阻塞通信

阻塞非阻塞同步异步

一个不安全的 MPI 非阻塞通信示例?