Ошибка MPI: недостаточно памяти - каковы некоторые варианты решения - PullRequest
2 голосов
/ 07 июня 2011

Я пытаюсь разрешить Неустранимая ошибка в MPI_Irecv: прерывание задания и получены смешанные (полезные, но неполные) ответы на этот запрос.

Сообщение об ошибке выглядит следующим образом:

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 процессора на одно ядро, и я отправляю свою работу с помощью следующей команды: mpirun -np 4. Я попытался использовать mpirun n -4, но он по-прежнему запускал 4 потока на одном ядре.

  3. Как определить, сколько "общей памяти" требуется для моей программы?

MPI_ISend / MPI_IRecv находится в рекурсивном цикле в моем коде и, следовательно, не очень ясно, лежит ли там источник ошибки (если я использую команды Send / Recv. Один или два раза, система просто отлично вычисляет без Проблемы с памятью "Если да, то как можно проверить и извлечь такую ​​информацию?

 #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 ]

6 голосов
/ 07 июня 2011

В вашей программе есть утечка памяти; это:

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 или что-то в этом роде. Что ваша программа пытается достичь?

UPDATE

Хорошо, похоже, вы делаете стандартную вещь, заполняющую гало. Несколько вещей:

  1. Каждому заданию не нужны свои собственные массивы - A / AA для ранга 0, B / BB для ранга 1 и т. Д. Память распределена, а не разделена; никакие ранги не могут видеть другие массивы, поэтому нет необходимости беспокоиться о перезаписи их. (Если бы было, вам не нужно отправлять сообщения). Кроме того, подумайте, насколько сложнее это работает на разных количествах процессов - вам придется добавлять новые массивы в код каждый раз, когда вы меняете количество используемых процессоров.

  2. Вы можете читать / записывать непосредственно в массив V вместо использования копий, хотя изначально копии могут быть проще для понимания.

Я написал здесь небольшую версию кода, заполняющего гало, используя ваши имена переменных (Tmyo, Nmyo, V, знаки i и y и т. Д.). У каждой задачи есть только часть более широкого массива V, и она обменивается данными своего края только со своими соседями. Он использует символы, чтобы вы могли видеть, что происходит. Он заполняет свою часть массива V своим номером #, а затем обменивается данными своего края со своими соседями.

Я бы СИЛЬНО рекомендовал бы вам сесть с книгой MPI и разобраться с ее примерами. Мне нравится Использование MPI , но есть много других. Есть также много хороших учебников MPI там. Я думаю, что не будет преувеличением сказать, что 95% книг и учебных пособий по MPI (например, наши здесь - см. Части 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);

это признак того, что вы не используете правильные структуры данных. Здесь вы почти наверняка захотите иметь трехмерный массив переменных состояния, при этом (вероятно) 3-й индекс - это видовая или локальная переменная состояния или как вы хотите назвать i2, i1f, i1s и т. Д. Затем все эти строки можно заменить с циклом, и добавление новой локальной переменной состояния становится намного проще.

Точно так же, когда все ваше состояние будет определено как глобальные переменные, это сделает вашу жизнь намного сложнее, когда дело доходит до обновления и обслуживания кода. Опять же, это, вероятно, отчасти связано с наличием вещей в миллионах независимых переменных состояния вместо того, чтобы иметь структуры или массивы более высокой размерности, объединяющие все соответствующие данные.

1 голос
/ 07 июня 2011

Я не знаком с библиотекой, но ... 1) Не следует удалять буфер после чтения. Вы выделили буфер (динамически) при запуске программы. Пока вы удаляете его (один раз) по окончании, с вами все будет в порядке. На самом деле, даже если вы не удалите его, он должен быть очищен при выходе из программы (но это неаккуратно).

2) Несколько ядер не должны влиять на проблемы с памятью.

3) Не уверен. MPI должен иметь некоторую документацию, чтобы помочь вам.

...