【问题标题】:MPI Error: Out of Memory - What are some solution optionsMPI 错误:内存不足 - 有哪些解决方案选项
【发布时间】:2011-09-09 14:46:15
【问题描述】:

我正在尝试解决 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 Non Blocking Send and Receive”结束时,内存是在发送/接收完成后自行释放还是必须强制释放?

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

  3. 如何确定我的程序需要多少“共享内存”?

MPI_ISend/MPI_IRecv 在我的代码中的递归循环内,因此不太清楚错误的来源是否在那里(如果我只使用一次或两次发送/接收命令,系统计算就好了没有“内存不足问题”)。如果是这样,如何检查和缓解这些信息?

#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]);
            }
        }
    }
}

谢谢!

【问题讨论】:

  • 使用多核而不是单核并不能解决内存不足的问题,它仍然是您计算机中的同一块内存,无论使用一核还是四核!
  • @Gajet:这是在集群上运行的......所以我想,每个核心都有自己的共享内存。因此,我假设内存问题也许可以解决!?但是,我不确定,因此这个想法。
  • 1.阅读代码不是问题。调试它。 2. 如果错误消息比“内存不足”或“其他 MPI 错误”更具体,那么该假设可能成立;在这种情况下,信息越多越好。 3. 编辑这篇文章,例如对于那些对调试感兴趣的人来说,一个保管箱链接会很有用;另一个新线程绝对没有保证。

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


【解决方案1】:

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

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 次,大概是多次迭代;但是您只是在调用 Wait 来等待其中一个请求。您可能需要为这两个请求执行MPI_Waitall()

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

更新

好的,看起来您正在做标准的光环填充操作。几件事:

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

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

我在这里使用您的变量名(TmyoNmyoV、indicies iy 等)编写了一个小版本的晕圈填充代码。每个任务只有它在更宽的 V 数组中的一部分,并且只与它的邻居交换它的边缘数据。它使用字符,所以你可以看到发生了什么。它用它的秩 # 填充 V 数组的一部分,然后与它的邻居交换它的边缘数据。

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

#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();
}

上面的程序可以进一步简化,使用MPI_Cart_create 来创建您的多维域并自动为您计算邻居,但我想向您展示逻辑以便您了解发生了什么。

另外,如果您可以从长期从事此工作的人那里获得一些建议:

任何时候你有一行又一行的重复代码:就像 60 (!!) 行这样的:

Vmax =V[i][y]-Vold; updateMaxStateChange(Vmax / dt);

mmax=m[i][y]-mold; updateMaxStateChange(mmax / dt);
hmax=h[i][y]-hold; updateMaxStateChange(hmax / dt);
jmax=j[i][y]-jold; updateMaxStateChange(jmax / dt);

mLmax=mL[i][y]-mLold; updateMaxStateChange(mLmax / dt);
hLmax=hL[i][y]-hLold; updateMaxStateChange(hLmax / dt);
hLBmax=hLB[i][y]-hLBold; updateMaxStateChange(hLBmax / dt);
hLSmax=hLS[i][y]-hLSold; updateMaxStateChange(hLSmax / dt);

amax=a[i][y]-aold; updateMaxStateChange(amax / dt);
i1fmax=i1f[i][y]-i1fold; updateMaxStateChange(i1fmax / dt);
i1smax=i1s[i][y]-i1sold; updateMaxStateChange(i1smax / dt);

Xrmax=Xr[i][y]-Xrold; updateMaxStateChange(Xrmax / dt);

i2max=i2[i][y]-i2old; updateMaxStateChange(i2max / dt);

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

同样,基本上将所有状态定义为全局变量将使您在更新和维护代码时变得更加艰难。同样,这可能部分与拥有数以万计的独立状态变量中的事物有关,而不是拥有将所有相关数据组合在一起的结构或高维数组。

【讨论】:

  • 这样的硬编码意味着如果你想使用不同数量的处理器,你必须完全重写程序。看起来您正在进行最近邻通信以进行光环交换。明天某个时候,我会尝试发布一个更简单的版本。
  • 更新了您想要做的基本结构。请注意,上面的程序将与 any 多个 ncols 处理器一起使用,并且它足够简单(通过避免硬编码 ncols)可以与任意数量的处理器一起使用。还要注意,一旦我们有了 ncols,并且一旦我们定义了 Nmyo 和 Tmyo 等的常量,我们就永远不会再次使用那些硬编码的数字。这对于可维护性必不可少
  • 在 main() 中,ncols 设置为 2;因此,如果您的 size=4,您将拥有 2 x 2 网格 (nrows = size/ncols;),并且左/右/上/下 = -1/+1/-2/+2。但是,当然,您可以进行任何您想要的分解。如果您有size 任务,您可以自己将其分解为行/列,或者您可以调用MPI_Dims_create(size, 2, dims) 来创建尽可能平方的分解,然后调用rows=dims[0]; cols=dims[1];
  • 内存泄漏来自于每个isend/irecv 创建一个需要一些资源(例如,内存)的请求。您可以通过(例如)为 each 请求执行 Wait() 或 Waitall() 来释放请求,从而释放该内存。通过生成越来越多的请求,但只为其中的一些调用Wait(),你只释放了一些内存,因此你有内存泄漏。跨度>
  • Ashoman:通过发布非阻塞请求而不等待它们来不泄漏资​​源,从而解决了内存泄漏问题。您可以通过 (a) 等待您在某个时间发布的 每个 请求来解决此问题,无论如何您都需要这样做以确保正确性,和/或 (b) 不使用非阻塞通信并使用 sendrecv上面的例子。上面的代码是一个完整且正确的保护单元填充过程,根本不使用临时数组(例如,A/AA)。
【解决方案2】:

我不熟悉图书馆,但是... 1)您不应该在读取后删除缓冲区。您已在程序启动时(动态地)分配了缓冲区。只要您在终止时将其删除(一次),就可以了。其实就算不删,程序退出的时候也应该清理干净(不过这样很草率)。

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

3) 不确定。 MPI 应该有一些文档可以帮助您。

【讨论】:

  • 谢谢约翰!您能否澄清“终止时删除缓冲区”的含义。
  • John,我已经尝试过您推荐的方法,但没有成功?还有什么我可以尝试的吗?
猜你喜欢
  • 2014-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-26
  • 1970-01-01
相关资源
最近更新 更多