Как получить столбцы MPI_Gatherv от процессора, где каждый процесс может отправлять разное количество столбцов

Скажем, участвуют 2 процесса. Процесс 0 (ранг 0) имеет

A = { a d
      b e
      c f
    }

а процесс 1 (ранг 1) имеет

A = { g
      h
      i
    }

Я хочу, чтобы оба процессора отправляли эти столбцы в ранг 0, чтобы ранг 0 имел следующее, скажем, в другом 2D-массиве.

B = { a d g
      b e h
      c f i
    }

Я создаю новый тип данных столбца для MPI_Gatherv и пробую следующий код, который никуда меня не ведет.

Мои конкретные вопросы:

  1. Как мне подойти к этому
  2. Какими должны быть send_type и recv_type.
  3. Как должны быть указаны смещения (должны ли они быть в терминах нового типа данных или MPI_CHAR)

Спасибо.

Это мой код:

#include <stdio.h>
#include <mpi.h>

int main(int argc, char *argv[])
{
  int numprocs, my_rank;
   long int i, j;
   MPI_Status status;
   char **A;
   char **B;
  MPI_Init(&argc, &argv);
  MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
  MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);

  if(my_rank == 0)
  {
    A = (char **)calloc((3), sizeof(char *));
    B = (char **)calloc((3), sizeof(char *));
    for(i=0; i<3; ++i)
    {
       A[i] = (char *)calloc(2, sizeof(char));
       B[i] = (char *)calloc(3, sizeof(char));
    }

    A[0][0] = 'a';
    A[1][0] = 'b';
    A[2][0] = 'c';
    A[0][1] = 'd';
    A[1][1] = 'e';
    A[2][1] = 'f';
  }
  else
  {
    A = (char **)calloc((3), sizeof(char *));
    for(i=0; i<3; ++i)
    {
       A[i] = (char *)calloc(1, sizeof(char));
    }
    A[0][0] = 'g';
    A[1][0] = 'h';
    A[2][0] = 'i';

  }
  MPI_Datatype b_col_type;
  MPI_Type_vector(3, 1, 1, MPI_CHAR, &b_col_type);
  MPI_Type_commit(&b_col_type);
  int displs[2] = {0, 2};
  int recvcounts[2] = {2, 1};
  MPI_Gatherv(&A[0][0], recvcounts[my_rank], b_col_type, &B[0][0], recvcounts, displs,    b_col_type, 0, MPI_COMM_WORLD);
  if(my_rank == 0)
  {
    for(i=0; i<3; ++i)
    {
      for(j=0; j<3; ++j)
        printf("%c ", B[i][j]);
      printf("\n");
    }
  }
  MPI_Finalize();
  return 0;
}

c mpi
person Reep    schedule 20.03.2011    source источник
comment
Что вы имеете в виду, что это вас никуда не приведет? В чем проблема?   -  person suszterpatt    schedule 21.03.2011
comment
Итак, если я попытаюсь запустить приведенный выше код, в B попадет только $ mpirun -np 2 try_gather a d. Таким образом, только ранг 0 может отправить свои 2 элемента. Я попытался настроить значения displs и recvcounts, однако не смог получить то, к чему стремлюсь. Текущий код по крайней мере 2 символа в нужном месте. Я отказываюсь от попыток отформатировать код в разделе комментариев. В основном «а» и «d» идут в нужное место. Остальные все пробелы в массиве B.   -  person Reep    schedule 21.03.2011


Ответы (1)


Итак, во-первых - и это постоянно возникает с массивами MPI и C - вы не можете делать стандартную вещь двумерного массива C. Давайте посмотрим на это:

A = (char **)calloc((3), sizeof(char *));
for(i=0; i<3; ++i)
{
   A[i] = (char *)calloc(2, sizeof(char));
}

Это определенно выделит массив символов 3x2, но вы понятия не имеете, как результирующие данные размещаются в памяти. В частности, нет никакой гарантии вообще, что A[1][0] следует сразу за A[0][1]. Это делает очень сложным создание типов данных MPI, которые охватывают структуру данных! Вам нужно выделить 3x2 смежных байта, а затем сделать так, чтобы массив указывал на него:

char **charalloc2d(int n, int m) {
    char *data = (char *)calloc(n*m,sizeof(char));
    char **array = (char **)calloc(n, sizeof(char *));
    for (int i=0; i<n; i++)
        array[i] = &(data[i*m]);

    return array;
}

void charfree2d(char **array) {
    free(array[0]);
    free(array);
    return;
}

/* ... */
nrows = 3;
ncols = 2;
A = charalloc2d(nrows,ncols);

Теперь мы знаем кое-что о расположении массива и можем полагаться на него при построении типов данных.

Вы на правильном пути с типами данных -

MPI_Datatype b_col_type;
MPI_Type_vector(3, 1, 1, MPI_CHAR, &b_col_type);
MPI_Type_commit(&b_col_type);

сигнатура MPI_Type_vector: (count, blocklen, stride, old_type, *newtype).
Нам нужно nrows символов, которые идут блоками по 1; но они отстоят друг от друга на ncol; так что это шаг.

Обратите внимание, что на самом деле это тип столбца массива A, а не B; тип будет зависеть от количества столбцов в массиве. Таким образом, каждый процесс использует свой тип отправки, и это нормально.

MPI_Datatype a_col_type;
MPI_Type_vector(nrows, 1, ncols, MPI_CHAR, &a_col_type);
MPI_Type_commit(&a_col_type);

Последний шаг — MPI_Gatherv, и здесь вам нужно быть немного милым. Хитрость в том, что мы хотим отправлять (и получать) несколько таких вещей одновременно, то есть несколько последовательных. Но нам нужно, чтобы следующий столбец находился не на расстоянии nrows*ncols символов, а всего на один символ. К счастью, мы можем сделать это, установив верхнюю границу структуры данных на расстоянии всего одного символа от нижней границы, чтобы следующий элемент начинался в нужном месте. Это разрешено стандартом, и фактически одним их примеры в разделе 4.1.4 зависят от этого.

Для этого мы создаем тип с измененным размером, который заканчивается всего через один байт после его начала:

MPI_Type_create_resized(a_col_type, 0, 1*sizeof(char), &new_a_col_type);
MPI_Type_commit(&new_a_col_type); 

и аналогично для B; и теперь мы можем отправлять и получать несколько таких сообщений, как и следовало ожидать. Итак, для меня работает следующее:

#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>

char **charalloc2d(int n, int m) {
    char *data = (char *)calloc(n*m,sizeof(char));
    char **array = (char **)calloc(n, sizeof(char *));
    for (int i=0; i<n; i++)
        array[i] = &(data[i*m]);

    return array;
}

void charfree2d(char **array) {
    free(array[0]);
    free(array);
    return;
}


int main(int argc, char *argv[])
{
    int numprocs, my_rank;
    int nrows, ncols, totncols;
    long int i, j;
    char **A;
    char **B;
    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);

    if(my_rank == 0)
    {
        nrows=3;
        ncols=2;
        totncols = 3;

        A = charalloc2d(nrows, ncols);
        B = charalloc2d(nrows, totncols);

        A[0][0] = 'a';
        A[1][0] = 'b';
        A[2][0] = 'c';
        A[0][1] = 'd';
        A[1][1] = 'e';
        A[2][1] = 'f';
    }
    else
    {
        nrows = 3;
        ncols = 1;
        A = charalloc2d(nrows, ncols);
        B = charalloc2d(1,1); /* just so gatherv survives */
        A[0][0] = 'g';
        A[1][0] = 'h';
        A[2][0] = 'i';

    }
    MPI_Datatype a_col_type, new_a_col_type;
    MPI_Type_vector(nrows, 1, ncols, MPI_CHAR, &a_col_type);
    MPI_Type_commit(&a_col_type);

    /* make the type have extent 1 character -- now the next
     * column starts in the next character of the array 
     */
    MPI_Type_create_resized(a_col_type, 0, 1*sizeof(char), &new_a_col_type);
    MPI_Type_commit(&new_a_col_type);

    MPI_Datatype b_col_type, new_b_col_type;
    if (my_rank == 0) {
        MPI_Type_vector(nrows, 1, totncols, MPI_CHAR, &b_col_type);
        MPI_Type_commit(&b_col_type);

        /* similarly "resize" b columns */
        MPI_Type_create_resized(b_col_type, 0, 1*sizeof(char), &new_b_col_type);
        MPI_Type_commit(&new_b_col_type);
    }

    int displs[2] = {0, 2};
    int recvcounts[2] = {2, 1};
    MPI_Gatherv(A[0], recvcounts[my_rank], new_a_col_type,
                B[0], recvcounts, displs, new_b_col_type,
                0, MPI_COMM_WORLD);
    if(my_rank == 0)
    {
        for(i=0; i<3; ++i)
        {
            for(j=0; j<3; ++j)
                printf("%c ", B[i][j]);
            printf("\n");
        }
    }
    MPI_Finalize();
    return 0;
}
person Jonathan Dursi    schedule 21.03.2011
comment
Спасибо, Джон. Позже я понял, что мои массивы не были смежными в памяти. Так что пока я планирую использовать комбинацию Send/Recv с разными MPI_Datatype для каждого процесса и не использовать MPI_Gatherv. Я так понимаю, вы тоже это имеете в виду? - person Reep; 21.03.2011
comment
Сохраните матрицу в порядке столбцов. Таким образом, один столбец будет иметь простой тип данных MPI_Contiguous, и вы сможете легко отправлять/получать их с помощью Gatherv() (2 столбца из процесса 0 со смещением 0 и 1 столбец из процесса 1 со смещением 2). В любом случае, это лучше подходит для того, как вы разлагаете свои данные. - person suszterpatt; 21.03.2011
comment
@suszterpatt Я не хочу хранить в основном формате столбца, потому что разные процессоры вычисляют и сохраняют данные в своих массивах A[][] в основном порядке строк, а массивы на самом деле довольно большие, поэтому записи, возможно, вызовут много перегрузок памяти. - person Reep; 21.03.2011
comment
@Jonathan, в примере в вопросе, если ранг 1 имеет 2 столбца вместо одного A = { g h i j k l }, тогда, используя MPI_Type_vector(3, 1, 2, MPI_CHAR, &a_col_type), я не могу MPI_Send оба столбца ранжировать 0, если я вызываю MPI_Send вот так MPI_Send(&A[0][0], 2, a_col_type, dest, tag, MPI_COMM_WORLD). Отправляются только первый столбец и последний символ l и выше. Я понимаю, почему после отправки i следующим элементом в памяти будет l. Не должно быть так сложно отправить пару столбцов! - person Reep; 22.03.2011
comment
Вы знаете, можно было бы использовать mpi_type_create_resized, чтобы размер структуры был равен только одному символу, чтобы сразу после этого появился следующий... Однако я сомневаюсь, что это правильно. - person Jonathan Dursi; 22.03.2011
comment
Ах ха! В этом была хитрость. Ответ обновлен; это работает сейчас. - person Jonathan Dursi; 22.03.2011
comment
Спасибо! Раньше я использовал довольно дорогой и неуклюжий метод, когда я сначала копировал элементы в 1D-массив, а затем снова копировал их в нужные места в 2D-массиве. - person Reep; 25.03.2011