用于发送和接收所有mpi流程的省时设计模型:MPI全部2全部通信

时间:2019-05-24 17:55:18

标签: c++ mpi blocking

我正在尝试将消息从某个进程发送到所有MPI进程,并且还试图从某个进程中的所有那些进程接收消息。基本上,这是所有通信的全部,其中每个进程向其他每个进程(除自身之外)发送消息并从其他每个进程接收消息。

以下示例代码段显示了我要实现的目标。现在,MPI_Send的问题是它的行为,对于较小的消息,它充当非阻塞,但对于较大的消息(在我的机器BUFFER_SIZE 16400中),它将阻塞。我知道这就是MPI_Send的行为。解决方法是,我将下面的代码替换为MPI_Sendrecv(send + recv)。示例代码如下:MPI_Sendrecv(intSendPack, BUFFER_SIZE, MPI_INT, processId, MPI_TAG, intReceivePack, BUFFER_SIZE, MPI_INT, processId, MPI_TAG, MPI_COMM_WORLD, MPI_STATUSES_IGNORE)。我对每个级别的循环内的MPI_COMM_WORLD的所有过程进行了上述调用,这种方法给了我我想要实现的目标(所有通信)。但是,此调用需要花费大量时间,因此我想使用一些省时的方法减少调用时间。我尝试使用mpi散点图并聚集以执行所有通信,但是这里的一个问题是,在MPI_all_to_all函数调用的不同迭代中,缓冲区大小(16400)在实际实现中可能有所不同。在这里,我使用MPI_TAG来区分不同迭代中的调用,而我不能在分散和收集函数中使用它。

#define BUFFER_SIZE 16400

void MPI_all_to_all(int MPI_TAG)
{

    int size;
    int rank;
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    int* intSendPack = new int[BUFFER_SIZE]();
    int* intReceivePack = new int[BUFFER_SIZE]();

    for (int prId = 0; prId < size; prId++) {
        if (prId != rank) {
            MPI_Send(intSendPack, BUFFER_SIZE, MPI_INT, prId, MPI_TAG,
            MPI_COMM_WORLD);
          }
    }

    for (int sId = 0; sId < size; sId++) {
        if (sId != rank) {
            MPI_Recv(intReceivePack, BUFFER_SIZE, MPI_INT, sId, MPI_TAG,
            MPI_COMM_WORLD, MPI_STATUSES_IGNORE);
        }
    }
}

我想知道是否有一种方法可以使用任何有效的通信模型来执行所有通信。我不坚持使用MPI_Send,如果还有其他方法可以为我提供我想要实现的目标,那么我对此感到满意。任何帮助或建议,我们将不胜感激。

1 个答案:

答案 0 :(得分:0)

这是一个基准,可以比较所有通信中集体通信与点对点通信的性能,

#include <iostream>
#include <algorithm>
#include <mpi.h>

#define BUFFER_SIZE 16384

void point2point(int*, int*, int, int);

int main(int argc, char *argv[])
{
    MPI_Init(&argc, &argv);

    int rank_id = 0, com_sz = 0;
    double t0 = 0.0, tf = 0.0;
    MPI_Comm_size(MPI_COMM_WORLD, &com_sz);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank_id);

    int* intSendPack = new int[BUFFER_SIZE]();
    int* result = new int[BUFFER_SIZE*com_sz]();
    std::fill(intSendPack, intSendPack + BUFFER_SIZE, rank_id);
    std::fill(result + BUFFER_SIZE*rank_id, result + BUFFER_SIZE*(rank_id+1), rank_id);

    // Send-Receive
    t0 = MPI_Wtime();
    point2point(intSendPack, result, rank_id, com_sz);
    MPI_Barrier(MPI_COMM_WORLD);
    tf = MPI_Wtime();
    if (!rank_id)
        std::cout << "Send-receive time: " << tf - t0 << std::endl;

    // Collective
    std::fill(result, result + BUFFER_SIZE*com_sz, 0);
    std::fill(result + BUFFER_SIZE*rank_id, result + BUFFER_SIZE*(rank_id+1), rank_id);
    t0 = MPI_Wtime();
    MPI_Allgather(intSendPack, BUFFER_SIZE, MPI_INT, result, BUFFER_SIZE, MPI_INT, MPI_COMM_WORLD);
    MPI_Barrier(MPI_COMM_WORLD);
    tf = MPI_Wtime();
    if (!rank_id)
        std::cout << "Allgather time: " << tf - t0 << std::endl;

    MPI_Finalize();
    delete[] intSendPack;
    delete[] result;
    return 0;
}

// Send/receive communication
void point2point(int* send_buf, int* result, int rank_id, int com_sz)
{
    MPI_Status status;
    // Exchange and store the data
    for (int i=0; i<com_sz; i++){
        if (i != rank_id){
            MPI_Sendrecv(send_buf, BUFFER_SIZE, MPI_INT, i, 0, 
                result + i*BUFFER_SIZE, BUFFER_SIZE, MPI_INT, i, 0, MPI_COMM_WORLD, &status);
        }
    }
}

这里,每个等级在所有其他等级上都将自己的数组intSendPack贡献给数组result,而在所有等级上的结局应该相同。 result是平坦的,每个等级从其BUFFER_SIZE开始接受rank_id*BUFFER_SIZE个条目。点对点通信后,将阵列重置为其原始形状。

时间是通过设置MPI_Barrier来衡量的,它将为您提供所有等级中的最大时间。

我使用Nersc Cori KNLslurm的1个节点上运行了基准测试。我为确保每种情况下的值均一致运行了几次,而我并没有查看异常值,但是您应该运行10次左右以收集更适当的统计信息。

这里有一些想法:

  • 对于较少数量的进程(5)和较大的缓冲区大小(16384),集体通信的速度大约是点对点的两倍,但是当移动到更大的等级时,它的速度大约是4-5倍(64) )。
  • 在此基准测试中,该特定计算机上建议的配置设置与默认设置之间的性能差异不大,但在实际的,具有更多通信的较大程序中,有一个非常重要的建议(建议运行的时间少于一分钟)将在默认情况下运行20-30分钟,甚至更多)。重点是检查您的设置,这可能会有所不同。
  • 使用“发送/接收”查看较大的消息实际上是一个僵局。对于此基准测试中显示的消息大小,我也看到了。如果您错过了这些内容,可以在上面写上两个值得的帖子:buffering explanationa word on deadlocking

总而言之,请调整此基准以更紧密地表示您的代码并在系统上运行它,但是由于专用的优化(例如高级算法),在所有情况下或在所有情况下的集体通信应该更快用于通讯安排。 2-5倍的加速速度是相当可观的,因为交流通常对整体时间的贡献最大。