Обмен гало не работает должным образом в MPI - PullRequest
1 голос
/ 15 мая 2011

Я пишу некоторый код, который выполняет вычисления на большой трехмерной сетке и использует процедуру обмена гало, чтобы он мог работать с использованием MPI.Я получаю неправильные результаты из своего кода, и я почти уверен, что это из-за того, что обмен гало не работает должным образом.

В основном у меня большой 3D-массив, часть которого содержится в каждом процессе,У каждого процесса есть массив, который на 2 элемента больше в каждом измерении, чем кусок данных, который он содержит - так что мы можем обменяться гало в каждой грани массива, не затрагивая данные, хранящиеся в остальной части массива.У меня есть следующий код для обмена информацией о гало:

  MPI_Type_vector(g->ny, g->nx, g->nx, MPI_DOUBLE, &face1);
  MPI_Type_commit(&face1);

  MPI_Type_vector(2*g->ny, 1, g->nx, MPI_DOUBLE, &face2);
  MPI_Type_commit(&face2);

  MPI_Type_vector(g->nz, g->nx, g->nx * g->ny, MPI_DOUBLE, &face3);
  MPI_Type_commit(&face3);

  /* Send to WEST receive from EAST */
  MPI_Sendrecv(&(g->data)[current][0][0][0], 1, face1, g->west, tag,
    &(g->data)[current][0][0][0], 1, face1, g->east, tag, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

  /* Send to EAST receive from WEST */
  MPI_Sendrecv(&(g->data)[current][g->nz-1][0][0], 1, face1, g->east, tag,
    &(g->data)[current][g->nz-1][0][0], 1, face1, g->west, tag, MPI_COMM_WORLD, MPI_STATUS_IGNORE);



  /* Send to NORTH receive from SOUTH */
  MPI_Sendrecv(&(g->data)[current][0][0][0], 1, face2, g->north, tag,
    &(g->data)[current][0][0][0], 1, face2, g->south, tag, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

  /* Send to SOUTH receive from NORTH */
  MPI_Sendrecv(&(g->data)[current][0][g->ny-1][0], 1, face2, g->south, tag,
    &(g->data)[current][0][0][0], 1, face2, g->north, tag, MPI_COMM_WORLD, MPI_STATUS_IGNORE);



  /* Send to UP receive from DOWN */
  MPI_Sendrecv(&(g->data)[current][0][0][0], 1, face3, g->up, tag,
    &(g->data)[current][0][0][0], 1, face3, g->down, tag, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

  /* Send to DOWN receive from UP */
  MPI_Sendrecv(&(g->data)[current][0][0][g->nx-1], 1, face3, g->down, tag,
    &(g->data)[current][0][0][g->nx-1], 1, face3, g->up, tag, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

g->nx, g->ny и g->nz - размеры фрагмента массива, который удерживает этот процесс, и g->west,g->east, g->north, g->south, g->up и g->down - ранги смежных процессов в каждом направлении, найденные с использованием следующего кода:

/* Who are my neighbours in each direction? */
  MPI_Cart_shift( cart_comm, 2, 1, &g->north, &g->south);
  MPI_Cart_shift( cart_comm, 1, 1, &g->west, &g->east);
  MPI_Cart_shift( cart_comm, 0, 1, &g->up, &g->down);

Массив в каждом процессеопределяется как:

array[2][g->nz][g->ny][g->nx]

(у него две копии, потому что мне нужно по одному обновляться каждый раз с помощью процедуры обновления, как только я произвожу обмен гало).

Может кто-нибудьскажите, правильно ли я общаюсь?В частности, определение векторных типов.Будут ли векторные типы, определенные в коде, извлекать каждую грань трехмерного массива?И выглядят ли вызовы MPI_Sendrecv правильными?

Я совершенно не понимаю, почему мой код не работает, но я почти уверен, что он связан с коммуникациями.

1 Ответ

3 голосов
/ 16 мая 2011

Так что я большой поклонник использования MPI_Type_create_subarray для извлечения кусочков массивов; проще держать прямо, чем векторные типы. В общем, вы не можете использовать один векторный тип для описания нескольких сторожевых ячеек (потому что есть несколько шагов, вам нужно создавать векторы векторов), но я думаю, потому что вы используете только 1 сторожевую ячейку в каждом направлении, что ты в порядке.

Итак, давайте рассмотрим X-Face GC; здесь вы отправляете всю плоскость y-z вашему x-соседу. В памяти это выглядит так с учетом вашего массива:

 +---------+
 |        @|
 |        @|
 |        @|
 |        @|   z=2
 |        @|
 +---------+
 |        @|
 |        @|
 |        @|   z=1
 |        @|
 |        @|
 +---------+
 |        @|
^|        @|
||        @|   z=0
y|        @|
 |        @|
 +---------+
    x->

, так что вы хотите отправить count = (ny * nz) блоков по 1 значению, каждый с шагом nx. Я предполагаю, что nx, ny и nz включают в себя guardcells, и что вы отправляете значения углов. Если вы не отправляете значения углов, лучше использовать subarray. Я также предполагаю, что важно, чтобы данные g-> представляли собой непрерывный блок из nx * ny * nz * 2 (или 2 смежных блоков из nx * ny * nz), удваивающийся, иначе все потеряно.

Так что ваш тип создания должен выглядеть как

MPI_Type_vector((g->ny*g->nz), 1, g->nx, MPI_DOUBLE, &face1);
MPI_Type_commit(&face1);

Обратите внимание, что мы посылаем итоговое значение count * blocksize = ny * nz, что правильно, и мы шагаем по памяти count * stride = nx * ny * nz в процессе, что тоже верно.

Хорошо, значит, у выглядит так:

 +---------+
 |@@@@@@@@@|
 |         |
 |         |
 |         |   z=2
 |         |
 +---------+
 |@@@@@@@@@|
 |         |
 |         |   z=1
 |         |
 |         |
 +---------+
 |@@@@@@@@@|
^|         |
||         |   z=0
y|         |
 |         |
 +---------+
    x->

Таким образом, у вас есть nz блоков значений nx, каждый из которых разделен шагом nx * ny. Так что ваш тип создания должен выглядеть как

MPI_Type_vector(g->nz, g->nx, (g->nx)*(g->ny), MPI_DOUBLE, &face2);
MPI_Type_commit(&face2);

И снова двойная проверка, вы отправляете счетчик * blocksize = nz * nx значений, шаговый счетчик * stride = nx * ny * nz памяти. Проверьте.

Наконец, отправка данных z-face включает отправку всей плоскости x-y:

 +---------+
 |@@@@@@@@@|
 |@@@@@@@@@|
 |@@@@@@@@@|   z=2
 |@@@@@@@@@|
 |@@@@@@@@@|
 +---------+
 |         |
 |         |
 |         |   z=1
 |         |
 |         |
 +---------+
 |         |
^|         |
||         |   z=0
y|         |
 |         |
 +---------+
    x->

MPI_Type_vector(1, (g->nx)*(g->ny), 1, MPI_DOUBLE, &face3);
MPI_Type_commit(&face3);

И снова двойная проверка, вы отправляете счетчик * blocksize = nx * ny значений, шаговый счетчик * stride = nx * ny памяти. Проверьте.

Обновление

Я не смотрел на ваши Sendrecvs, но там тоже может быть что-то. Обратите внимание, что вам нужно использовать указатель на первый фрагмент данных, который вы отправляете с векторным типом данных.

Прежде всего, если у вас есть размер массива nx в направлении x, и у вас есть две защитные ячейки (по одной с каждой стороны), ваша левая защитная ячейка равна 0, правая равна nx-1, а ваши «реальные» данные расширяются от 1 ..nx-2. Таким образом, чтобы отправить самые западные данные своему западному соседу и получить в свою самую восточную охранную ячейку от своего восточного соседа, вам понадобится

 /* Send to WEST receive from EAST */
 MPI_Sendrecv(&(g->data)[current][0][0][g->nx-2], 1, face1, g->west, westtag,
              &(g->data)[current][0][0][0], 1, face1, g->east, westtag, 
              MPI_COMM_WORLD, MPI_STATUS_IGNORE);

 /* Send to EAST receive from WEST */
 MPI_Sendrecv(&(g->data)[current][0][0][1], 1, face1, g->east, easttag,
              &(g->data)[current][0][0][g->nx-1], 1, face1, g->west, easttag, 
              MPI_COMM_WORLD, MPI_STATUS_IGNORE);

(мне нравится использовать разные теги для каждого этапа общения, помогает сортировать вещи.)

Аналогично для других направлений.

...