Отправка столбцов матрицы с использованием MPI_Scatter

Я пытаюсь написать программу умножения матрицы-вектора с использованием MPI. Я пытаюсь отправить столбцы матрицы для разделения процессов и локально вычислить результат. В конце я выполняю операцию MPI_Reduce с помощью операции MPI_SUM.

Отправка строк матрицы легко, так как C хранит массивы в строчном порядке, но столбцы не являются (если вы не отправляете их один за другим). Я прочитал здесь вопрос:

MPI_Scatter - отправка столбцов 2D-массива

Джонатан Дурси предложил использовать новые типы данных MPI, и вот что я сделал, адаптировав его код к моим собственным потребностям:

  double matrix[10][10];
  double mytype[10][10];
  int part_size; // stores how many cols a process needs to work on
  MPI_Datatype col, coltype;
  // ...
  MPI_Type_vector(N, 1, N, MPI_DOUBLE, &col);
  MPI_Type_commit(&col);
  MPI_Type_create_resized(col, 0, 1*sizeof(double), &coltype);
  MPI_Type_commit(&coltype);
  // ...
  MPI_Scatter(matrix, part_size, coltype,
              mypart, part_size, coltype,
              0, MPI_COMM_WORLD);

  // calculations...
  MPI_Reduce(local_result, global_result,
             N, MPI_DOUBLE,
             MPI_SUM,
             0, MPI_COMM_WORLD);

Это работает отлично, но я не могу сказать, что я действительно понимаю, как это работает.

  • Как хранится MPI_Type_vector в памяти?
  • Как работает MPI_Type_create_resized() и что он делает?

Пожалуйста, имейте в виду, что я новичок в MPI. Спасибо заранее.

Ответ 1

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

Важно знать, какой макет памяти описывает тип данных MPI. Вызывающая последовательность до MPI_Type_vector:

int MPI_Type_vector(int count,
                   int blocklength,
                   int stride, 
                   MPI_Datatype old_type,
                   MPI_Datatype *newtype_p)

Создает новый тип, который описывает макет памяти, где находится каждый элемент stride, есть блок из blocklength выведенных элементов и всего count этих блоков. Элементы здесь находятся в единицах того, что было old_type. Так, например, если вы позвонили (назвав здесь параметры, которые вы не можете сделать на C, но:)

 MPI_Type_vector(count=3, blocklength=2, stride=5, old_type=MPI_INT, &newtype);

Тогда newtype будет описывать макет в памяти следующим образом:

   |<----->|  block length

   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   | X | X |   |   |   | X | X |   |   |   | X | X |   |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   |<---- stride ----->|

   count = 3

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

Хорошо, поэтому в вашем случае вы вызвали

  MPI_Type_vector(N, 1, N, MPI_DOUBLE, &col);

который будет принимать блоки count = N, каждый из которых имеет размер blocklength=1 MPI_DOUBLE s, с пробелом между началом каждого блока stride=N MPI_DOUBLE s. Другими словами, это займет каждый N'th double, всего N раз; идеально подходит для извлечения одного столбца из (смежно сохраненного) массива NxN двойников. Удобная проверка заключается в том, чтобы увидеть, сколько данных перечеркивается (count*stride = N*N, который является полным размером матрицы, проверка) и сколько данных фактически включено (count*blocksize = N, который является размером столбца, проверьте. )

Если все, что вам нужно было сделать, это вызвать MPI_Send и MPI_Recv для обмена отдельными столбцами, вы бы сделали это; вы можете использовать этот тип, чтобы описать расположение столбца, и все будет в порядке. Но есть еще одна вещь.

Вы хотите вызвать MPI_Scatter, который отправляет первый coltype (скажем) на процессор 0, следующий coltype на процессор 1 и т.д. Если вы делаете это с помощью простого массива 1d, легко понять, где "следующий" тип данных; если вы рассеиваете 1 int на каждый процессор, "следующий" int начинается сразу после завершения первого int.

Но ваш новый столбец coltype имеет общую степень, которая начинается с начала столбца до N*N MPI_DOUBLE позже - если MPI_Scatter следует той же логике (он делает), он начнет искать "следующий" столбец вне памяти матриц целиком и так далее со следующим и последующим. Мало того, что вы не получите ответ, который вам нужен, программа, скорее всего, потерпит крах.

Способ исправить это, чтобы сообщить MPI, что "размер" этого типа данных для целей расчета, где находится "следующий", - это размер в памяти между тем, где начинается один столбец, и начинается следующий столбец; то есть ровно один MPI_DOUBLE. Это не влияет на количество отправленных данных, которое по-прежнему составляет 1 столбцов данных; это влияет только на вычисление "следующий в строке". С столбцами (или строками) в массиве вы можете просто отправить этот размер в соответствующий размер шага в памяти, а MPI выберет правильный следующий столбец для отправки. Без этого оператора изменения ваша программа, скорее всего, выйдет из строя.

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