MPI 乘以 Isends、Irecvs 和 waitany 是一个好方法吗?

MPI multiples Isends, Irecvs with waitany is a good approach?

在MPI项目中使用下面的代码有什么问题吗?它可能会产生死锁或缓冲区重叠吗?

//send a data to all nodes on receiver list
for (auto &e : receivers_list) {
        MPI_Request request;
        MPI_Isend( &data, 1, MPI_INT, e.remot_id , 1234 , MPI_COMM_WORLD, &request);
}

//read data from senders
MPI_Request request_arr[senders_list.size()];
for (auto &e : senders_list) {
        MPI_Irecv  (&data, 1, MPI_INT, e.remot_id, MPI_ANY_TAG, MPI_COMM_WORLD, &request_arr[request_index++]);
}

//wait for all receives complete 
for (int count_recv = 0; count_recv < senders_list.size(); ++count_recv) {
        MPI_Waitany(senders_list.size(), request_arr, &request_index, MPI_STATUS_IGNORE);

     //do some code here ....
}

您 post 编辑的代码片段中存在一些问题,我将在此处详细说明:

  • 非阻塞发送和接收调用使用相同的 data 缓冲区。这是非常非常错误的,这在两个层面上:
    1. 由于调用是在循环内进行的,因此每次新调用都会在同一内存地址上创建另一次读取或写入操作。然后,关于这些完成的顺序,您将有很多竞争条件,导致未定义的行为。
    2. 即使上述几点本身不是问题,MPI 标准也明确指出,在证明完成之前访问用于非阻塞 MPI 通信的通信缓冲区(MPI_Wait()MPI_Test() 等)是被禁止的。实际上,这几乎是一种防止我第一点中描述的竞争条件的方法,但这意味着代码不仅具有未定义的行为,而且从 MPI 标准的角度来看也是错误的。
  • 您在第一个循环中对 MPI_Isend() 的调用始终重复使用相同的 request,并在每次迭代时覆盖其值。这意味着您将丢失上一次调用的任何句柄,从而阻止您强制完成它们,并可能泄漏 MPI 库中的内存。您应该改用请求数组,就像您在接收循环中所做的那样。
  • 为了处理请求,你还应该包括发送部分,以确保通信的发送和接收双方平等地进行......
  • 最后,这是一个有争议的细节,在发送请求之前 post 接收请求通常是更明智的性能,让 MPI 库准备好接收传入消息而不是处理意外消息。因此,交换发送和接收循环可能是个好主意。

因此代码可能如下所示:

MPI_Request request_arr[receivers_list.size() + senders_list.size()];
int request_index = 0;

//read data from senders
int dataRecv[senders_list.size()];
for ( auto &e : senders_list ) {
    MPI_Irecv( &dataRecv[request_index], 1, MPI_INT, e.remot_id, MPI_ANY_TAG, MPI_COMM_WORLD, &request_arr[request_index++] );
}

//send a data to all nodes on receiver list
for ( auto &e : receivers_list ) {
    //I assume that you want to send the same value to all processes here, so like doing a MPI_Bcast()
    MPI_Isend( &data, 1, MPI_INT, e.remot_id, 1234 , MPI_COMM_WORLD, &request_arr[request_index++] );
}

//wait for all receives complete 
for ( int count_req = 0; count_req < request_index; ++count_req ) {
    int curr_idx;
    MPI_Waitany( request_index, request_arr, &curr_idx, MPI_STATUS_IGNORE );

    if ( curr_idx < senders_list.size() ) {
        //the completed request is a receiving one
        //we can therefore freely access dataRecv[curr_idx]
        //do some code here ...
    }
}