|
Коллективные операции и создание новых
коммуникаторов: MPIBegin73
Часто для эффективной реализации пересылки данных бывает удобно
воспользоваться вспомогательными коммуникаторами, которые включают
не все процессы параллельного приложения, а только требуемую их часть.
Задания на применение вспомогательных коммуникаторов собраны в двух последних подгруппах группы
MPIBegin: «Группы процессов и коммуникаторы»
и «Виртуальные топологии».
Рассмотрим одно из заданий, входящих в первую из этих подгрупп
(задания, связанные с виртуальными топологиями, рассматриваются в
следующем разделе).
MPIBegin73.
В каждом процессе, ранг которого делится на 3
(включая главный процесс), даны три целых числа. С помощью функции
MPI_Comm_split создать новый коммуникатор, включающий процессы,
ранг которых делится на 3. Используя одну коллективную операцию
пересылки данных для созданного коммуникатора, переслать исходные
числа в главный процесс и вывести эти числа в порядке возрастания
рангов переславших их процессов (включая числа, полученные из
главного процесса).
Приведем окно задачника, появившееся на экране при ознакомительном
запуске программы-заготовки для этого задания:
При этом в консольном окне
был выведен текст, показывающий, что в параллельной программе было
запущено 10 процессов:
c:\PT4Work>"C:\Program Files\MPICH\mpd\bin\MPIRun.exe"
-nopopup_debug localonly 10 "c:\PT4Work\Debug\ptprj.exe"
Таким образом, в задании надо использовать лишь часть имеющихся
процессов. Разумеется, мы можем воспользоваться функциями MPI,
обеспечивающими обмен данными между двумя процессами (как в
решении задачи MPIBegin17, приведенном в одном из предыдущих разделов), но
более эффективным будет вариант, использующий подходящую
коллективную операцию пересылки данных (заметим, что именно такой
вариант решения указан в формулировке задания). Однако коллективные
операции выполняются для всех процессов, входящих в некоторый
коммуникатор, поэтому в программе необходимо предварительно создать
коммуникатор, включающий только процессы, ранг которых делится на 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, создадим с ее
помощью коммуникатор, в который будут входить только процессы ранга,
кратного трем (в программе на языке Pascal надо дополнительно описать
переменную comm типа MPI_Comm и переменную color целого типа):
[C++]
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;
[Pascal]
if rank mod 3 = 0 then
color := 0
else
color := MPI_UNDEFINED;
MPI_Comm_split(MPI_COMM_WORLD, color, rank, comm);
if comm = MPI_COMM_NULL then exit;
Последний условный оператор обеспечивает немедленный выход из
процесса, если с ним оказался связан «нулевой»
коммуникатор comm (разумеется, в нашем случае в условии выхода можно
было бы анализировать остаток от деления rank на 3, однако
использованный вариант является более универсальным).
Во всех остальных процессах осталось ввести три целых числа,
переслать все введенные числа в главный процесс, используя
коллективную функцию MPI_Gather, и вывести полученные числа. Для
ввода исходных чисел в каждом процессе достаточно описать массив data
из трех элементов. Размер результирующего массива res, который будет
получен в главном процессе, зависит от количества процессов
параллельного приложения. При выполнении задания MPIBegin52 мы уже
отмечали, что в такой ситуации можно использовать либо статический
массив достаточно большого размера, либо динамический массив, размер
которого будет определен после того, как станет известно количество
процессов. В предыдущем пункте, выполняя задание MPIBegin52, мы
использовали статический массив. Теперь в качестве результирующего
массива res будем использовать динамический массив.
Опишем в программе на языке Pascal следующие переменные (для
динамического массива res диапазон индексов не указывается):
[Pascal]
data: array[0..2] of integer;
res: array of integer;
i: integer;
Дополним программу новым фрагментом кода:
[C++]
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;
[Pascal]
for i := 0 to 2 do
GetN(data[i]);
MPI_Comm_size(comm, size);
SetLength(res, 3 * size);
MPI_Gather(@data[0], 3, MPI_INT, @res[0], 3, MPI_INT, 0, comm);
if rank = 0 then
for i := 0 to 3 * size - 1 do
PutN(res[i]);
Для нахождения общего числа полученных элементов мы
предварительно определили количество процессов в коммуникаторе comm
(с помощью функции MPI_Comm_size), записав его в переменную size.
Поскольку каждый процесс коммуникатора comm пересылает в главный
процесс три элемента, размер динамического массива res полагается
равным size * 3 (в программе на языке Pascal для этого
используется процедура SetLength, в программе на С++ операция
new).
Затем вызывается функция MPI_Gather. Обратите
внимание на то, что в функции MPI_Gather в качестве пятого параметра
указывается не размер массива res, а количество элементов, принимаемых
от каждого процесса. Заметим также, что функция MPI_Gather
получает данные от всех процессов коммуникатора comm, в том числе и от
того процесса, который служит получателем всех данных. При указании
процесса-получателя мы учли, что процесс ранга 0 в коммуникаторе
MPI_COMM_WORLD является одновременно и процессом ранга 0 в
коммуникаторе comm.
Обратите внимание на способ передачи указателя на массивы data и res в программе
на языке Pascal: @data[0] и @res[0]. Заметим, что для статического массива data
в средах Delphi и Lazarus допустимо использовать более краткий вариант: @data.
После требуемого количества тестовых испытаний программы мы получим
сообщение о том, что задание выполнено.
Приведем полный текст полученного решения:
[C++]
void Solve()
{
Task("MPIBegin73");
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;
}
[Pascal]
program MPIBegin73;
uses PT4, MPI;
var
flag, size, rank: integer;
comm: MPI_Comm;
color: integer;
data: array[0..2] of integer;
res: array of integer;
i: integer;
begin
Task('MPIBegin73');
MPI_Initialized(flag);
if flag = 0 then exit;
MPI_Comm_size(MPI_COMM_WORLD, size);
MPI_Comm_rank(MPI_COMM_WORLD, rank);
if rank mod 3 = 0 then
color := 0
else
color := MPI_UNDEFINED;
MPI_Comm_split(MPI_COMM_WORLD, color, rank, comm);
if comm = MPI_COMM_NULL then exit;
for i := 0 to 2 do
GetN(data[i]);
MPI_Comm_size(comm, size);
SetLength(res, 3 * size);
MPI_Gather(@data[0], 3, MPI_INT, @res[0], 3, MPI_INT, 0, comm);
if rank = 0 then
for i := 0 to 3 * size - 1 do
PutN(res[i]);
end.
|