Параллельное программирование на основе MPI
5.6. Управление группами процессов и коммуникаторами
Рассмотрим теперь возможности MPI по управлению группами процессов и коммуникаторами.
Для изложения последующего материала напомним ряд понятий и определений, приведенных в начале данной лекции.
Процессы параллельной программы объединяются в группы. В группу могут входить все процессы параллельной программы ; с другой стороны, в группе может находиться только часть имеющихся процессов. Один и тот же процесс может принадлежать нескольким группам. Управление группами процессов предпринимается для создания на их основе коммуникаторов.
Под коммуникатором в MPI понимается специально создаваемый служебный объект, который объединяет в своем составе группу процессов и ряд дополнительных параметров (контекст), используемых при выполнении операций передачи данных. Парные операции передачи данных выполняются только для процессов, принадлежащих одному и тому же коммуникатору. Коллективные операции применяются одновременно для всех процессов коммуникатора. Создание коммуникаторов предпринимается для уменьшения области действия коллективных операций и для устранения взаимовлияния разных выполняемых частей параллельной программы. Важно еще раз подчеркнуть – коммуникационные операции, выполняемые с использованием разных коммуникаторов, являются независимыми и не влияют друг на друга.
Все имеющиеся в параллельной программе процессы входят в состав создаваемого по умолчанию коммуникатора с идентификатором MPI_COMM_WORLD.
При необходимости передачи данных между процессами из разных групп необходимо создавать определенные в стандарте MPI -2 глобальные коммуникаторы ( intercommunicator ). Взаимодействие между процессами разных групп оказывается необходимым в достаточно редких ситуациях, в данном учебном материале не рассматривается и может служить темой для самостоятельного изучения – см., например, [ [ 4 ] , [ 40 ] – [ 42 ] , [ 57 ] ].
5.6.1. Управление группами
Группы процессов могут быть созданы только из уже существующих групп. В качестве исходной группы может быть использована группа, связанная с предопределенным коммуникатором MPI_COMM_WORLD. Также иногда может быть полезным коммуникатор MPI_COMM_SELF, определенный для каждого процесса параллельной программы и включающий только этот процесс.
Для получения группы, связанной с существующим коммуникатором, применяется функция:
int MPI_Comm_group(MPI_Comm comm, MPI_Group *group),
где
- comm — коммуникатор;
- group — группа, связанная с коммуникатором.
Далее, на основе существующих групп, могут быть созданы новые группы:
- создание новой группы newgroup из существующей группы oldgroup, которая будет включать в себя n процессов — их ранги перечисляются в массиве ranks:
int MPI_Group_incl(MPI_Group oldgroup, int n, int *ranks, MPI_Group *newgroup),
где
- oldgroup — существующая группа;
- n — число элементов в массиве ranks;
- ranks — массив рангов процессов, которые будут включены в новую группу;
- newgroup — созданная группа;
- создание новой группы newgroup из группы oldgroup, которая будет включать в себя n процессов, чьи ранги не совпадают с рангами, перечисленными в массиве ranks:
int MPI_Group_excl(MPI_Group oldgroup, int n, int *ranks, MPI_Group *newgroup),
где
- oldgroup — существующая группа;
- n — число элементов в массиве ranks;
- ranks — массив рангов процессов, которые будут исключены из новой группы;
- newgroup — созданная группа.
Для получения новых групп над имеющимися группами процессов могут быть выполнены операции объединения, пересечения и разности:
- создание новой группы newgroup как объединения групп group1 и group2:
int MPI_Group_union(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup),
где
- group1 — первая группа;
- group2 — вторая группа;
- newgroup — объединение групп;
- создание новой группы newgroup как пересечения групп group1 и group2:
int MPI_Group_intersection(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup),
где
- group1 — первая группа;
- group2 — вторая группа;
- newgroup — пересечение групп;
- создание новой группы newgroup как разности групп group1 и group2:
int MPI_Group_difference(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup),
где
- group1 — первая группа;
- group2 — вторая группа;
- newgroup — разность групп;
При конструировании групп может оказаться полезной специальная пустая группа MPI_COMM_EMPTY.
Ряд функций MPI обеспечивает получение информации о группе процессов:
int MPI_Group_size(MPI_Group group, int *size),
где
int MPI_Group_rank(MPI_Group group, int *rank),
где
После завершения использования группа должна быть удалена:
int MPI_Group_free(MPI_Group *group),
где
- group — группа, подлежащая удалению
(выполнение данной операции не затрагивает коммуникаторы, в которых используется удаляемая группа).
5.6.2. Управление коммуникаторами
Отметим прежде всего, что в данном пункте рассматривается управление интракоммуникаторами, используемыми для операций передачи данных внутри одной группы процессов. Как отмечалось ранее, применение интеркоммуникаторов для обменов между группами процессов выходит за пределы данного учебного материала.
Для создания новых коммуникаторов существуют два основных способа:
- дублирование уже существующего коммуникатора:
int MPI_Comm_dup(MPI_Comm oldcom, MPI_comm *newcom),
где
- oldcom — существующий коммуникатор, копия которого создается;
- newcom — новый коммуникатор;
int MPI_comm_create(MPI_Comm oldcom, MPI_Group group, MPI_Comm *newcom),
где
- oldcom — существующий коммуникатор;
- group — подмножество процессов коммуникатора oldcom;
- newcom — новый коммуникатор.
Дублирование коммуникатора может применяться, например, для устранения возможности пересечения по тегам сообщений в разных частях параллельной программы (в том числе и при использовании функций разных программных библиотек).
Следует отметить также, что операция создания коммуникаторов является коллективной и, тем самым, должна выполняться всеми процессами исходного коммуникатора.
Для пояснения рассмотренных функций можно привести пример создания коммуникатора, в котором содержатся все процессы, кроме процесса, имеющего ранг 0 в коммуникаторе MPI_COMM_WORLD (такой коммуникатор может быть полезен для поддержки схемы организации параллельных вычислений "менеджер – исполнители" – см. "Принципы разработки параллельных методов" ):
MPI_Group WorldGroup, WorkerGroup; MPI_Comm Workers; int ranks[1]; ranks[0] = 0; // Получение группы процессов в MPI_COMM_WORLD MPI_Comm_group(MPI_COMM_WORLD, &WorldGroup); // Создание группы без процесса с рангом 0 MPI_Group_excl(WorldGroup, 1, ranks, &WorkerGroup); // Создание коммуникатора по группе MPI_Comm_create(MPI_COMM_WORLD, WorkerGroup, &Workers); ... MPI_Group_free(&WorkerGroup); MPI_Comm_free(&Workers);
Быстрый и полезный способ одновременного создания нескольких коммуникаторов обеспечивает функция:
int MPI_Comm_split(MPI_Comm oldcomm, int split, int key, MPI_Comm *newcomm),
где
- oldcomm — исходный коммуникатор;
- split — номер коммуникатора, которому должен принадлежать процесс ;
- key — порядок ранга процесса в создаваемом коммуникаторе;
- newcomm — создаваемый коммуникатор.
Создание коммуникаторов относится к коллективным операциям, и поэтому вызов функции MPI_Comm_split должен быть выполнен в каждом процессе коммуникатора oldcomm. В результате выполнения функции процессы разделяются на непересекающиеся группы с одинаковыми значениями параметра split. На основе сформированных групп создается набор коммуникаторов. Для того чтобы указать, что процесс не должен входить ни в один из создаваемых коммуникаторов, необходимо воспользоваться константой MPI_UNDEFINED в качестве значения параметра split. При создании коммуникаторов для рангов процессов в новом коммуникаторе выбирается такой порядок нумерации, чтобы он соответствовал порядку значений параметров key ( процесс с большим значением параметра key получает больший ранг, процессы с одинаковым значением параметра key сохраняют свою относительную нумерацию).
В качестве примера можно рассмотреть задачу представления набора процессов в виде двумерной решетки. Пусть p=q*q есть общее количество процессов ; следующий далее фрагмент программы обеспечивает получение коммуникаторов для каждой строки создаваемой топологии:
MPI_Comm comm; int rank, row; MPI_Comm_rank(MPI_COMM_WORLD, &rank); row = rank / q; MPI_Comm_split(MPI_COMM_WORLD, row, rank, &comm);
При выполнении данного примера, например, при p=9, процессы с рангами (0, 1, 2) образуют первый коммуникатор, процессы с рангами (3, 4, 5) – второй и т. д.
После завершения использования коммуникатор должен быть удален, для чего используется функция:
int MPI_Comm_free(MPI_Comm *comm),
где
- comm — коммуникатор, подлежащий удалению.