|
Коллективные операции и создание новых коммуникаторов: MPI5Comm3
Часто для эффективной реализации пересылки данных бывает удобно
воспользоваться вспомогательными коммуникаторами, которые включают
не все процессы параллельного приложения, а только требуемую их часть
(группу процессов). Задания на применение вспомогательных
коммуникаторов собраны в трех подгруппах группы MPI5Comm. Следует
заметить, что в заданиях группе MPI5Comm рассматриваются только так
называемые интракоммуникаторы, связанные с одной группой процессов.
В MPI можно создавать и другой вид коммуникаторов
интеркоммуникаторы, которые связаны не с одной, а с двумя группами
процессов. Интеркоммуникаторам посвящена группа задания MPI8Inter,
большинство из которых можно выполнять только в системе MPICH2 1.3,
поддерживающей стандарт MPI-2.
Рассмотрим одно из заданий, входящих в первую подгруппу группы
MPI5Comm: «Группы процессов и коммуникаторы» (задания из двух
следующих подгрупп, связанные с виртуальными топологиями,
обсуждаются в следующем разделе).
MPI5Comm3.
В каждом процессе, ранг которого делится на 3
(включая главный процесс), даны три целых числа. С помощью функции
MPI_Comm_split создать новый коммуникатор, включающий процессы,
ранг которых делится на 3. Используя одну коллективную операцию
пересылки данных для созданного коммуникатора, переслать исходные
числа в главный процесс и вывести эти числа в порядке возрастания
рангов переславших их процессов (включая числа, полученные из
главного процесса).
Указание.
При вызове функции MPI_Comm_split в процессах,
которые не требуется включать в новый коммуникатор, в качестве
параметра color следует указывать константу MPI_UNDEFINED.
Приведем окно задачника, появившееся на экране при
ознакомительном запуске программы-заготовки для этого задания:
При этом в консольном окне был выведен текст, показывающий, что в
параллельной программе было запущено десять процессов:
C:\PT4Work>"C:\Program Files (x86)\MPICH2\bin\mpiexec.exe"
-nopopup_debug -localonly 11 "C:\PT4Work\ptprj.exe"
Таким образом, в задании надо использовать лишь часть имеющихся
процессов. Разумеется, мы можем воспользоваться функциями MPI,
обеспечивающими обмен данными между двумя процессами (как в
решении задачи MPI2Send11, приведенном в разделе «Пересылка сообщений»), но более эффективным
будет вариант, использующий подходящую коллективную операцию
пересылки данных (заметим, что именно такой вариант решения указан в
формулировке задания). Однако коллективные операции выполняются для
всех процессов, входящих в некоторый коммуникатор, поэтому в
программе необходимо предварительно создать коммуникатор,
включающий только процессы, ранг которых делится на 3. Сделать это
можно несколькими способами; один из них связан с использованием
функции MPI_Comm_split, упомянутой в формулировке задания.
Функция MPI_Comm_split позволяет «расщепить» исходный
коммуникатор на набор коммуникаторов, каждый из которых связан с
некоторой частью процессов, входящих в исходный коммуникатор.
Следует учитывать, что массив новых коммуникаторов в программе не
возникает; вместо этого каждому из процессов программы функция
MPI_Comm_split предоставляет именно тот коммуникатор из созданного
набора, в который входит данный процесс. Предусмотрена также ситуация,
когда некоторые процессы не будут включены ни в один из созданных
коммуникаторов; для таких процессов функция MPI_Comm_split
возвращает «пустой» коммуникатор MPI_COMM_NULL.
Для разбиения процессов на новые группы в функции MPI_Comm_split
используется параметр color («цвет»). Все процессы одного цвета
включаются в один и тот же новый коммуникатор; при этом любой цвет
представляет собой обычное целое число. Предусмотрен также
«неопределенный цвет» MPI_UNDEFINED; его надо указывать для
процесса, который не следует включать ни в один из новых
коммуникаторов. Второй характеристикой, используемой в функции
MPI_Comm_split при создании нового набора коммуникаторов, является
параметр key («ключ»). Он определяет порядок, в котором будут
располагаться процессы в каждом из новых коммуникаторов: процессы в
каждом коммуникаторе упорядочиваются по возрастанию их ключей (если
некоторые процессы имеют одинаковые ключи, то их порядок определяется
средой MPI, которая управляет параллельной программой). Для
сохранения в каждом из вновь созданных коммуникаторов исходного
порядка следования процессов достаточно в качестве параметра key для
каждого процесса указать ранг этого процесса в исходном коммуникаторе.
Предусмотренная для функции MPI_Comm_split возможность
использования константы MPI_UNDEFINED позволяет создавать новые
коммуникаторы только для некоторых из имеющихся процессов. Ввиду
важности этой возможности о ней сказано в указании к данному заданию.
Учитывая перечисленные особенности функции MPI_Comm_split,
создадим с ее помощью коммуникатор, в который будут входить только
процессы ранга, кратного трем:
MPI_Comm comm;
int color = rank % 3 == 0 ? 0 : MPI_UNDEFINED;
MPI_Comm_split(MPI_COMM_WORLD, color, rank, &comm);
if (comm == MPI_COMM_NULL)
return;
Можно запустить данный вариант программы, чтобы убедиться, что
при создании нового коммуникатора мы не допустили ошибок (задачник
по-прежнему будет считать запуск программы ознакомительным, так как
ввод и вывод данных в ней не выполняется).
Последний условный оператор обеспечивает немедленный выход из
процесса, если с ним оказался связан «нулевой» коммуникатор comm
(разумеется, в нашем случае в условии выхода можно было бы
анализировать остаток от деления rank на 3, однако использованный
вариант является более универсальным).
Во всех остальных процессах осталось ввести три целых числа,
переслать все введенные числа в главный процесс, используя коллективную
функцию MPI_Gather, и вывести полученные числа. Для ввода исходных
чисел в каждом процессе достаточно описать массив data из трех
элементов. Размер результирующего массива res, который будет получен в
главном процессе, зависит от количества процессов параллельного
приложения. При обсуждении задания MPI3Call23 мы уже отмечали, что в
такой ситуации можно использовать либо статический массив достаточно
большого размера, либо динамический массив, размер которого будет
определен после того, как станет известно количество процессов. В предыдущем разделе,
выполняя задание MPI3Call23, мы использовали статический массив.
Теперь в качестве результирующего массива res будем использовать
динамический массив:
int data[3];
for (int i = 0; i < 3 ; ++i)
pt >> data[i];
MPI_Comm_size(comm, &size);
int *res = new int[3 * size];
MPI_Gather(data, 3, MPI_INT, res, 3, MPI_INT, 0, comm);
if (rank == 0)
for (int i = 0; i < 3 * size; ++i)
pt << res[i];
delete[] res;
Для нахождения общего числа полученных элементов мы
предварительно определили количество процессов в коммуникаторе comm
(с помощью функции MPI_Comm_size), записав его в переменную size.
Поскольку каждый процесс коммуникатора comm пересылает в главный
процесс три элемента, размер динамического массива res полагается
равным size * 3. Для выделения памяти мы использовали оператор new
языка C++.
Затем вызывается функция MPI_Gather. Обратите внимание
на то, что в функции MPI_Gather в качестве пятого параметра указывается
не размер массива res, а количество элементов, принимаемых от каждого
процесса. Заметим также, что функция MPI_Gather получает данные от всех
процессов коммуникатора comm, в том числе и от того процесса, который
служит получателем всех данных. При указании процесса-получателя мы
учли, что процесс ранга 0 в коммуникаторе MPI_COMM_WORLD является
одновременно и процессом ранга 0 в коммуникаторе comm.
В конце программы мы с помощью оператора delete освобождаем
динамическую память, ранее выделенную оператором new.
Запустив полученную программу, мы получим сообщение о том, что
задание выполнено.
Приведем полный текст полученного решения:
void Solve()
{
Task("MPI5Comm3");
int flag;
MPI_Initialized(&flag);
if (flag == 0)
return;
int rank, size;
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm comm;
int color = rank % 3 == 0 ? 0 : MPI_UNDEFINED;
MPI_Comm_split(MPI_COMM_WORLD, color, rank, &comm);
if (comm == MPI_COMM_NULL)
return;
int data[3];
for (int i = 0; i < 3 ; ++i)
pt >> data[i];
MPI_Comm_size(comm, &size);
int* res = new int[3 * size];
MPI_Gather(data, 3, MPI_INT, res, 3, MPI_INT, 0, comm);
if (rank == 0)
for (int i = 0; i < 3 * size; ++i)
pt << res[i];
delete[] res;
}
|