MPI错误:内存不足 - 有哪些解决方案选项

时间:2011-06-06 20:49:43

标签: c++ multicore mpi parallel-processing shared-memory

我正在尝试解析Fatal Error in MPI_Irecv: Aborting Job并收到对该查询的混合(有用但不完整)响应。

错误消息如下:

aborting job:
> Fatal error in MPI_Irecv: Other MPI
> error, error stack: MPI_Irecv(143):
> MPI_Irecv(buf=0x8294a60, count=48,
> MPI_DOUBLE, src=2, tag=-1, 
> MPI_COMM_WORLD, request=0xffffd6ac)
> failed MPID_Irecv(64): Out of
> memory

我正在寻求某人的帮助来回答这些问题(我需要指导以帮助调试和解决此死锁)

  1. 在" MPI非阻塞发送和接收"结束时,发送/接收完成后自身释放的内存是否必须被强制释放?

  2. 问题是"内存不足"如果我使用"多核心"而不是单一的?我们目前有4个处理器到1个核心,我使用以下命令提交我的工作:mpirun -np 4。我尝试使用mpirun n -4但它仍在同一核心上运行了4个线程。

  3. 我如何计算出多少"共享内存"我的程序需要吗?

  4. MPI_ISend / MPI_IRecv在我的代码中的递归循环内部,因此如果错误源位于那里则不太清楚(如果我只使用一次或两次发送/接收命令,系统计算就好了没有&#34 ;内存不足问题"如果是这样,如何检查和减轻此类信息?

     #include <mpi.h>  
    
     #define Rows 48 
    
    double *A = new double[Rows];
    double *AA = new double[Rows];
    ....
    ....
    
     int main (int argc, char *argv[])
     {
      MPI_Status status[8]; 
      MPI_Request request[8];
      MPI_Init (&argc, &argv);
      MPI_Comm_size(MPI_COMM_WORLD, &p);   
      MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    
     while (time < final time){
    ...
    ...
    
    for (i=0; i<Columns; i++) 
    {
    for (y=0; y<Rows; y++) 
    {
    if ((my_rank) == 0)
    {
        MPI_Isend(A, Rows, MPI_DOUBLE, my_rank+1, 0, MPI_COMM_WORLD, &request[1]);
        MPI_Irecv(AA, Rows, MPI_DOUBLE, my_rank+1, MPI_ANY_TAG, MPI_COMM_WORLD, &request[3]);
        MPI_Wait(&request[3], &status[3]);  
    
        MPI_Isend(B, Rows, MPI_DOUBLE, my_rank+2, 0, MPI_COMM_WORLD, &request[5]);
        MPI_Irecv(BB, Rows, MPI_DOUBLE, my_rank+2, MPI_ANY_TAG, MPI_COMM_WORLD, &request[7]);
        MPI_Wait(&request[7], &status[7]);
     }
    
    if ((my_rank) == 1)
    {
        MPI_Irecv(CC, Rows, MPI_DOUBLE, my_rank-1, MPI_ANY_TAG, MPI_COMM_WORLD, &request[1]);
        MPI_Wait(&request[1], &status[1]); 
        MPI_Isend(Cmpi, Rows, MPI_DOUBLE, my_rank-1, 0, MPI_COMM_WORLD, &request[3]);
    
        MPI_Isend(D, Rows, MPI_DOUBLE, my_rank+2, 0, MPI_COMM_WORLD, &request[6]); 
        MPI_Irecv(DD, Rows, MPI_DOUBLE, my_rank+2, MPI_ANY_TAG, MPI_COMM_WORLD, &request[8]);
        MPI_Wait(&request[8], &status[8]);
    }
    
    if ((my_rank) == 2)
    {
        MPI_Isend(E, Rows, MPI_DOUBLE, my_rank+1, 0, MPI_COMM_WORLD, &request[2]);
        MPI_Irecv(EE, Rows, MPI_DOUBLE, my_rank+1, MPI_ANY_TAG, MPI_COMM_WORLD, &request[4]);
        MPI_Wait(&request[4], &status[4]);
    
        MPI_Irecv(FF, Rows, MPI_DOUBLE, my_rank-2, MPI_ANY_TAG, MPI_COMM_WORLD, &request[5]);
        MPI_Wait(&request[5], &status[5]);
        MPI_Isend(Fmpi, Rows, MPI_DOUBLE, my_rank-2, 0, MPI_COMM_WORLD, &request[7]);
    }
    
    if ((my_rank) == 3)
    {
        MPI_Irecv(GG, Rows, MPI_DOUBLE, my_rank-1, MPI_ANY_TAG, MPI_COMM_WORLD, &request[2]);
        MPI_Wait(&request[2], &status[2]);
            MPI_Isend(G, Rows, MPI_DOUBLE, my_rank-1, 0, MPI_COMM_WORLD, &request[4]);
    
        MPI_Irecv(HH, Rows, MPI_DOUBLE, my_rank-2, MPI_ANY_TAG, MPI_COMM_WORLD, &request[6]);
            MPI_Wait(&request[6], &status[6]); 
            MPI_Isend(H, Rows, MPI_DOUBLE, my_rank-2, 0, MPI_COMM_WORLD, &request[8]);
     }
    

    谢谢!

2 个答案:

答案 0 :(得分:6)

您的程序中存在内存泄漏;这样:

MPI_Isend(A, Rows, MPI_DOUBLE, my_rank+1, 0, MPI_COMM_WORLD, &request[1]);
MPI_Irecv(AA, Rows, MPI_DOUBLE, my_rank+1, MPI_ANY_TAG, MPI_COMM_WORLD, &request[3]);
MPI_Wait(&request[3], &status[3])

泄漏与MPI_Isend请求相关联的资源。你每次迭代调用Rows*Columns次,大概是多次迭代;但你只是在等待其中一个请求。你可能需要为这两个请求做MPI_Waitall()

但除此之外,你的程序非常混乱。没有合理的MPI程序应该有这样一系列if (rank == ...)语句。既然你没有在非阻塞发送/接收和等待之间做任何实际的工作,我不明白为什么你不只是使用MPI_Sendrecv或其他东西。你的计划试图完成什么?

<强>更新

好的,所以看起来你正在做标准的光环填充物。一些事情:

  1. 每个任务需要它自己的数组 - 等级为A / AA,等级为1的B / BB等。内存是分布式的,不是共享的;没有排名可以看到其他阵列,所以没有必要担心覆盖它们。 (如果有,则不需要发送消息)。此外,想想在不同数量的进程上运行会变得多么困难 - 每次更改使用的处理器数量时,都必须在代码中添加新数组。

  2. 您可以直接读取/写入V阵列而不是使用副本,尽管最初可能最容易理解副本。

  3. 我在这里使用您的变量名称(TmyoNmyoV,标记i和{{1}在这里写了一个光环填充代码的小版本等等)。每个任务只有更宽V阵列的一部分,并且仅与其邻居交换其边缘数据。它使用字符,因此您可以看到正在发生的事情。它用V等级填充V阵列的一部分,然后将其边缘数据与其邻居进行交换。

    强烈鼓励您坐下来阅读MPI书并完成其示例。我喜欢Using MPI,但还有很多其他人。还有很多很好的MPI教程。我认为可以毫不夸张地说,95%的MPI书籍和教程(例如,我们的here - 见第5和第6部分)将完全按照这个程序作为他们的第一个大型工作示例之一。他们称之为光环填充或保护单元填充或边界交换或其他东西,但这一切都归结为传递边缘数据。

    y

    上述程序可以使用#include <stdio.h> #include <stdlib.h> #include <mpi.h> char **alloc_2d_char(const int rows, const int cols) { char *data = (char *)malloc(rows*cols*sizeof(char)); char **array= (char **)malloc(rows*sizeof(char*)); for (int i=0; i<rows; i++) array[i] = &(data[cols*i]); return array; } void edgeDataFill(char **locV, const int locNmyo, const int locTmyo, const int ncols, const int myrow, const int mycol, const int size, const int rank) { MPI_Datatype leftright, updown; int left, right, up, down; int lefttag = 1, righttag = 2; int uptag = 3, downtag = 4; MPI_Status status; /* figure out our neighbours */ left = rank-1; if (mycol == 0) left = MPI_PROC_NULL; right = rank+1; if (mycol == ncols-1) right = MPI_PROC_NULL; up = rank - ncols; if (myrow == 0) up = MPI_PROC_NULL; down = rank + ncols; if (down >= size) down = MPI_PROC_NULL; /* create data type for sending/receiving data left/right */ MPI_Type_vector(locNmyo, 1, locTmyo+2, MPI_CHAR, &leftright); MPI_Type_commit(&leftright); /* create data type for sending/receiving data up/down */ MPI_Type_contiguous(locTmyo, MPI_CHAR, &updown); MPI_Type_commit(&updown); /* Send edge data to our right neighbour, receive from left. We are sending the edge (locV[1][locTmyo]..locV[locNmyo][locTmyo]), and receiving into edge (locV[0][1]..locV[locNmyo][locTmyo]) */ MPI_Sendrecv(&(locV[1][locTmyo]), 1, leftright, right, righttag, &(locV[1][0]), 1, leftright, left, righttag, MPI_COMM_WORLD, &status); /* Send edge data to our left neighbour, receive from right. We are sending the edge (locV[1][1]..locV[locNmyo][1]), and receiving into edge (locV[1][locTmyo+1]..locV[locNmyo][locTmyo+1]) */ MPI_Sendrecv(&(locV[1][1]), 1, leftright, left, lefttag, &(locV[1][locTmyo+1]), 1, leftright, right, lefttag, MPI_COMM_WORLD, &status); /* Send edge data to our up neighbour, receive from down. We are sending the edge (locV[1][1]..locV[1][locTmyo]), and receiving into edge (locV[locNmyo+1][1]..locV[locNmyo+1][locTmyo]) */ MPI_Sendrecv(&(locV[1][1]), 1, updown, up, uptag, &(locV[locNmyo+1][1]), 1, updown, down, uptag, MPI_COMM_WORLD, &status); /* Send edge data to our down neighbour, receive from up. We are sending the edge (locV[locNmyo][1]..locV[locNmyo][locTmyo]), and receiving into edge (locV[0][1]..locV[0][locTmyo]) */ MPI_Sendrecv(&(locV[locNmyo][1]),1, updown, down, downtag, &(locV[0][1]), 1, updown, up, downtag, MPI_COMM_WORLD, &status); /* Release the resources associated with the Type_create() calls. */ MPI_Type_free(&updown); MPI_Type_free(&leftright); } void printArrays(char **locV, const int locNmyo, const int locTmyo, const int size, const int rank) { /* all these barriers are a terrible idea, but it's just for controlling output to the screen as a demo. You'd really do something smarter here... */ for (int task=0; task<size; task++) { if (rank == task) { printf("\nTask %d's local array:\n", rank); for (int i=0; i<locNmyo+2; i++) { putc('[', stdout); for (int y=0; y<locTmyo+2; y++) { putc(locV[i][y], stdout); } printf("]\n"); } } fflush(stdout); MPI_Barrier(MPI_COMM_WORLD); } } int main(int argc, char **argv) { int ierr, size, rank; char **locV; const int Nmyo=12; /* horizontal */ const int Tmyo=12; /* vertical */ const int ncols=2; /* n procs in horizontal direction */ int nrows; int myrow, mycol; int locNmyo, locTmyo; ierr = MPI_Init(&argc, &argv); ierr|= MPI_Comm_size(MPI_COMM_WORLD, &size); ierr|= MPI_Comm_rank(MPI_COMM_WORLD, &rank); nrows = size/ncols; if (nrows*ncols != size) { fprintf(stderr,"Size %d does not divide number of columns %d!\n", size, ncols); MPI_Abort(MPI_COMM_WORLD,-1); } /* where are we? */ mycol = rank % ncols; myrow = rank / ncols; /* figure out how many Tmyo we have */ locTmyo = (Tmyo / ncols); /* in case it doesn't divide evenly... */ if (mycol == ncols-1) locTmyo = Tmyo - (ncols-1)*locTmyo; /* figure out how many Tmyo we have */ locNmyo = (Nmyo / nrows); /* in case it doesn't divide evenly... */ if (myrow == nrows-1) locNmyo = Nmyo - (ncols-1)*locNmyo; /* allocate our local array, with space for edge data */ locV = alloc_2d_char(locNmyo+2, locTmyo+2); /* fill in our local data - first spaces everywhere */ for (int i=0; i<locNmyo+2; i++) for (int y=0; y<locTmyo+2; y++) locV[i][y] = ' '; /* then the inner regions have our rank # */ for (int i=1; i<locNmyo+1; i++) for (int y=1; y<locTmyo+1; y++) locV[i][y] = '0' + rank; /* The "before" picture: */ if (rank==0) printf("###BEFORE###\n"); printArrays(locV, locNmyo, locTmyo, size, rank); /* Now do edge filling. Ignore corners for now; the right way to do that depends on your algorithm */ edgeDataFill(locV, locNmyo, locTmyo, ncols, myrow, mycol, size, rank); /* The "after" picture: */ if (rank==0) printf("###AFTER###\n"); printArrays(locV, locNmyo, locTmyo, size, rank); MPI_Finalize(); } 进一步简化以创建多维域并自动为您计算邻居,但我想向您展示逻辑,以便了解正在发生的事情。

    另外,如果你可以从长期完成这项工作的人那里得到一些建议:

    任何时候你都有一行一行的重复代码:如60(!!)行:

    MPI_Cart_create

    这表明您没有使用正确的数据结构。在这里,你几乎肯定想要一个3d数组的状态变量,(可能)第三个索引是物种或本地状态变量或任何你想要调用i2,i1f,i1s等等。然后所有这些行都可以被替换使用循环,并添加一个新的本地状态变量变得很多更简单。

    同样,基本上所有的状态都被定义为全局变量,这将使你的生活在更新和维护代码方面变得更加艰难。同样,这可能部分与以数不清的独立状态变量为主,而不是将所有相关数据组合在一起的结构或更高维数组。

答案 1 :(得分:1)

我对图书馆并不熟悉,但...... 1)读取后不应删除缓冲区。您已在程序启动时(动态)分配缓冲区。只要你在终止时删除(一次),你应该没事。实际上,即使你不删除它,它也应该在程序退出时清理(但这很邋))。

2)多核应该对内存问题没有影响。

3)不确定。 MPI应该有一些文档来帮助你。