Параллельное программирование на основе MPI
6.6. Управление группами процессов и коммуникаторами
Рассмотрим теперь возможности MPI по управлению группами процессов и коммуникаторами.
Для изложения последующего материала напомним ряд понятий и определений, приведенных в начале данного раздела.
Процессы параллельной программы объединяются в группы. В группу могут входить все процессы параллельной программы; с другой стороны, в группе может находиться только часть имеющихся процессов. Соответственно, один и тот же процесс может принадлежать нескольким группам. Управление группами процессов предпринимается для создания на их основе коммуникаторов.
Под коммуникатором в MPI понимается специально создаваемый служебный объект, объединяющий в своем составе группу процессов и ряд дополнительных параметров ( контекст ), используемых при выполнении операций передачи данных. Как правило, парные операции передачи данных выполняются для процессов, принадлежащих одному и тому же коммуникатору. Коллективные операции применяются одновременно для всех процессов коммуникатора. Создание коммуникаторов предпринимается для уменьшения области действия коллективных операций и для устранения взаимовлияния разных выполняемых частей параллельной программы. Важно еще раз подчеркнуть - коммуникационные операции, выполняемые с использованием разных коммуникаторов, являются независимыми и не влияют друг на друга.
Все имеющиеся в параллельной программе процессы входят в состав создаваемого по умолчанию коммуникатора с идентификатором MPI_COMM_WORLD.
При необходимости передачи данных между процессами из разных групп необходимо создавать глобальный коммуникатор ( intercommunicator ). Взаимодействие между процессами разных групп оказывается необходимым в достаточно редких ситуациях, в данном учебном материале не рассматривается и может служить темой для самостоятельно изучения - см., например, Немнюгин и Стесик (2002), Group, et al. (1994), Pacheco (1996).
6.6.1. Управление группами
Группы процессов могут быть созданы только из уже существующих групп. В качестве исходной группы может быть использована группа, связанная с предопределенным коммуникатором MPI_COMM_WORLD.
Для получения группы, связанной с существующим коммуникатором, используется функция:
int MPI_Comm_group ( MPI_Comm comm, MPI_Group *group ).
Далее, на основе существующих групп, могут быть созданы новые группы:
- создание новой группы newgroup из существующей группы oldgroup, которая будет включать в себя n процессов, ранги которых перечисляются в массиве ranks:
int MPI_Group_incl(MPI_Group oldgroup,int n, int *ranks,MPI_Group *newgroup),
- создание новой группы newgroup из группы oldgroup, которая будет включать в себя n процессов, ранги которых не совпадают с рангами, перечисленными в массиве ranks:
int MPI_Group_excl(MPI_Group oldgroup,int n, int *ranks,MPI_Group *newgroup).
Для получения новых групп над имеющимися группами процессов могут быть выполнены операции объединения, пересечения и разности:
- создание новой группы newgroup как объединения групп group1 и group2:
int MPI_Group_union(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup);
- создание новой группы newgroup как пересечения групп group1 и group2:
int MPI_Group_intersection ( MPI_Group group1, MPI_Group group2, MPI_Group *newgroup ),
- создание новой группы newgroup как разности групп group1 и group2:
int MPI_Group_difference ( MPI_Group group1, MPI_Group group2, MPI_Group *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 )
(выполнение данной операции не затрагивает коммуникаторы, в которых используется удаляемая группа).
6.6.2. Управление коммуникаторами
Отметим прежде всего, что в данном пункте рассматривается управление интракоммуникаторами, используемыми для операций передачи данных внутри одной группы процессов. Как отмечалось ранее, применение интеркоммуникаторов для обменов между группами процессов выходит за пределы данного учебного материала.
Для создания новых коммуникаторов применимы два основных способа их получения:
- дублирование уже существующего коммуникатора:
int MPI_Comm_dup ( MPI_Comm oldcom, MPI_comm *newcomm ),
- создание нового коммуникатора из подмножества процессов существующего коммуникатора:
int MPI_comm_create (MPI_Comm oldcom, MPI_Group group, MPI_Comm *newcomm).
Дублирование коммуникатора может использоваться, например, для устранения возможности пересечения по тегам сообщений в разных частях параллельной программы (в т.ч. и при использовании функций разных программных библиотек).
Следует отметить также, что операция создания коммуникаторов является коллективной и, тем самым, должна выполняться всеми процессами исходного коммуникатора.
Для пояснения рассмотренных функций можно привести пример создания коммуникатора, в котором содержатся все процессы, кроме процесса, имеющего ранг 0 в коммуникаторе MPI_COMM_WORLD (такой коммуникатор может быть полезен для поддержки схемы организации параллельных вычислений "менеджер - исполнители" - см. раздел 6):
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. На основе сформированных групп создается набор коммуникаторов. При создании коммуникаторов для рангов процессов выбирается такой порядок нумерации, чтобы он соответствовал порядку значений параметров 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 ).