MPI在发送大消息时停顿于MPI_Send函数

8

这是一个简单的C++/MPI (MPICH2)程序,它发送一个双精度类型的数组。如果数组大小超过9000,则在调用MPI_Send时程序会挂起。如果数组小于9000(例如8000),程序能正常工作。以下是源代码:

main.cpp

using namespace std;

Cube** cubes;
int cubesLen;

double* InitVector(int N) {
   double* x = new double[N];
   for (int i = 0; i < N; i++) {
       x[i] = i + 1;
   }
   return x;
}

void CreateCubes() {
    cubes = new Cube*[12];
    cubesLen = 12;
    for (int i = 0; i < 12; i++) {
       cubes[i] = new Cube(9000);
    }
}

void SendSimpleData(int size, int rank) {
    Cube* cube = cubes[0];
    int nodeDest = rank + 1;
    if (nodeDest > size - 1) {
        nodeDest = 1;
    }

    double* coefImOut = (double *) malloc(sizeof (double)*cube->coefficentsImLength);
    cout << "Before send" << endl;
    int count = cube->coefficentsImLength;
    MPI_Send(coefImOut, count, MPI_DOUBLE, nodeDest, 0, MPI_COMM_WORLD);
    cout << "After send" << endl;
    free(coefImOut);

    MPI_Status status;
    double *coefIm = (double *) malloc(sizeof(double)*count);

    int nodeFrom = rank - 1;
    if (nodeFrom < 1) {
        nodeFrom = size - 1;
    }

    MPI_Recv(coefIm, count, MPI_DOUBLE, nodeFrom, 0, MPI_COMM_WORLD, &status);
    free(coefIm);
}

int main(int argc, char *argv[]) {
    int size, rank;
    const int root = 0;

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    CreateCubes();

    if (rank != root) {
         SendSimpleData(size, rank);
    }

    MPI_Finalize();
    return 0;
}

类 Cube

 class Cube {
 public:
    Cube(int size);
    Cube(const Cube& orig);
    virtual ~Cube();

    int Id() { return id; } 
    void Id(int id) { this->id = id; }

    int coefficentsImLength;
    double* coefficentsIm;

private:
    int id;
};

Cube::Cube(int size) {
    this->coefficentsImLength = size;

    coefficentsIm = new double[size];
    for (int i = 0; i < size; i++) {
        coefficentsIm[i] = 1;
    }
}

Cube::Cube(const Cube& orig) {
}

Cube::~Cube() {
    delete[] coefficentsIm;
}

该程序运行在4个进程上:

mpiexec -n 4 ./myApp1

有什么想法吗?

1
请发布您的完整代码,包括MPI_Recv。 - Nils_M
好的,虽然我调用MPI_Send时应用程序崩溃了。使用调试器Alinea DDT测试过。 - user2240771
这与“挂起”有些不同。崩溃的性质是什么?但我们仍然需要看到更多的代码,最好是一个简单可重现的例子。 - Jonathan Dursi
我简化了问题,并提供了应用程序的完整代码。 - user2240771
1个回答

19

这里并不涉及 Cube 类的细节:考虑一个简化版本。

#include <mpi.h>
#include <cstdlib>

using namespace std;

int main(int argc, char *argv[]) {
    int size, rank;
    const int root = 0;

    int datasize = atoi(argv[1]);

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    if (rank != root) {
        int nodeDest = rank + 1;
        if (nodeDest > size - 1) {
            nodeDest = 1;
        }
        int nodeFrom = rank - 1;
        if (nodeFrom < 1) {
            nodeFrom = size - 1;
        }

        MPI_Status status;
        int *data = new int[datasize];
        for (int i=0; i<datasize; i++)
            data[i] = rank;

        cout << "Before send" << endl;
        MPI_Send(&data, datasize, MPI_INT, nodeDest, 0, MPI_COMM_WORLD);
        cout << "After send" << endl;
        MPI_Recv(&data, datasize, MPI_INT, nodeFrom, 0, MPI_COMM_WORLD, &status);

        delete [] data;

    }

    MPI_Finalize();
    return 0;
}

奔跑所带来的

$ mpirun -np 4 ./send 1
Before send
After send
Before send
After send
Before send
After send
$ mpirun -np 4 ./send 65000
Before send
Before send
Before send

如果在DDT中查看消息队列窗口, 会发现所有人都在发送, 没有人在接收, 造成了一个典型的死锁(deadlock)

MPI_Send的语义比较奇怪,定义也不太明确, 但它可以阻塞直到"接收被发布(posted)"。相对而言,MPI_Ssend在这方面更清晰;它总是会阻塞直到接收被发布。有关不同发送模式的详细信息,请参见此处

之所以小消息可以正常工作是由于实现的偶然性;对于"足够小"的消息 (对于您的情况,看起来应该是<64kB),您的MPI_Send实现使用"急切发送(eager send)"协议,并且不会在接收上阻塞;对于更大的消息,在内存中保留缓冲副本并不安全,因此发送将等待匹配的接收(无论如何都是允许的)。

有几个方法可以避免这种情况;您需要确保不是每个人都在同时调用阻塞的MPI_Send。你可以(例如)让偶数进程先发送,然后接收, 奇数进程则先接收,再发送。您可以使用非阻塞通信(Isend/Irecv/Waitall)。但是,在这种情况下最简单的解决方案是使用MPI_Sendrecv,它是一个阻塞式(Send+Recv),而不是一个阻塞式的发送加上一个阻塞式的接收。发送和接收将同时执行,并且函数将阻塞直到两者都完成。所以这样就可以了。

#include <mpi.h>
#include <cstdlib>

using namespace std;

int main(int argc, char *argv[]) {
    int size, rank;
    const int root = 0;

    int datasize = atoi(argv[1]);

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    if (rank != root) {
        int nodeDest = rank + 1;
        if (nodeDest > size - 1) {
            nodeDest = 1;
        }
        int nodeFrom = rank - 1;
        if (nodeFrom < 1) {
            nodeFrom = size - 1;
        }

        MPI_Status status;
        int *outdata = new int[datasize];
        int *indata  = new int[datasize];
        for (int i=0; i<datasize; i++)
            outdata[i] = rank;

        cout << "Before sendrecv" << endl;
        MPI_Sendrecv(outdata, datasize, MPI_INT, nodeDest, 0,
                     indata, datasize, MPI_INT, nodeFrom, 0, MPI_COMM_WORLD, &status);
        cout << "After sendrecv" << endl;

        delete [] outdata;
        delete [] indata;
    }

    MPI_Finalize();
    return 0;
}

跑步有益于健康。

$ mpirun -np 4 ./send 65000
Before sendrecv
Before sendrecv
Before sendrecv
After sendrecv
After sendrecv
After sendrecv

非常感谢您提供详细和全面的答案! - user2240771

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接