Введение в программирование на кластерах

Группы процессов и коммуникаторы

Показывать лекцию целиком

Презентацию к данной лекции Вы можете скачать здесь.

Группы процессов и коммуникаторы

В MPI имеются средства создания и преобразования коммуникаторов, которые дают возможность программисту в дополнение к стандартным предопределенным объектам создавать свои собственные. Это позволяет использовать разнообразные схемы взаимодействия процессов.

Используя средства MPI, можно создать новый коммуникатор, содержащий, например, те же процессы, что и исходный, но с новым контекстом (новыми свойствами).

Группы процессов

Группой называют упорядоченное множество процессов.

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

В MPI имеется специальная предопределенная пустая группа MPI_GROUP_EMPTY.

Коммуникаторы

Коммуникаторы бывают двух типов:

  • интракоммуникаторы - для операций внутри одной группы процессов;
  • интеркоммуникаторы - для двухточечного обмена между двумя группами процессов.
  • Интракоммуникатором является MPI_COMM_WORLD.

    В MPI-программах чаще используются интракоммуникаторы.

    Интракоммуникатор включает экземпляр группы, контекст обмена для всех его видов, а также, возможно, виртуальную топологию и другие атрибуты. Контекст обеспечивает возможность создания изолированных друг от друга, а потому безопасных областей взаимодействия. Система сама управляет их разделением. Контекст играет роль дополнительного тега, который дифференцирует сообщения.

    Можно организовать обмены между двумя непересекающимися группами процессов.

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

    Интеробмены реализуются с помощью интеркоммуникаторов, которые объединяют две группы процессов общим контекстом.

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

    Конструкторы интеркоммуникаторов являются блокирующими операциями, поэтому во избежание тупиковых ситуаций локальная и удаленная группа не должны пересекаться (они не должны содержать одинаковые процессы).

    Создание групп процессов

    Создание групп процессов

    Созданию нового коммуникатора предшествует создание соответствующей группы процессов. Операции создания групп аналогичны математическим операциям над множествами:

  • объединение - к процессам первой группы (group1) добавляются процессы второй группы (group2), не принадлежащие первой;
  • пересечение - в новую группу включаются все процессы, принадлежащие двум группам одновременно. Ранги им назначаются как в первой группе;
  • разность - в новую группу включаются все процессы первой группы, не входящие во вторую группу. Ранги назначаются как в первой группе.
  • Новую группу можно создать только из уже существующих групп. Базовая группа, из которой формируются все другие группы, связана с коммуникатором MPI_COMM_WORLD.

    В подпрограммах создания групп, как правило, нельзя использовать пустой коммуникатор MPI_COMM_NULL.

    Доступ к группе group, связанной с коммуникатором comm можно получить, обратившись к подпрограмме MPI_Comm_group:

    int MPI_Comm_group(MPI_Comm comm, MPI_Group *group)
    MPI_Comm_group(comm, group, ierr)
        

    Ее выходным параметром является группа.

    Для выполнения операций с группой к ней сначала необходимо получить доступ.

    Подпрограмма MPI_Group_incl создает новую группу newgroup из n процессов, входящих в группу oldgroup. Ранги этих процессов содержатся в массиве ranks:

    int MPI_Group_incl(MPI_Group oldgroup, int n, int *ranks, MPI_Group *newgroup)
    MPI_Group_incl(oldgroup, n, ranks, newgroup, ierr)
        

    В новую группу войдут процессы с рангами ranks[0], …, ranks[n — 1], причем рангу i в новой группе соответствует ранг ranks[i] в старой группе. При n = 0 создается пустая группа MPI_GROUP_EMPTY.

    С помощью данной подпрограммы можно не только создать новую группу, но и изменить порядок процессов в старой группе.

    Подпрограмма MPI_Group_excl создает группу newgroup, исключая из исходной группы (group) процессы с рангами ranks[0], …, ranks[n — 1]:

    int MPI_Group_excl(MPI_Group oldgroup, int n, int *ranks, MPI_Group *newgroup)
    MPI_Group_excl(oldgroup, n, ranks, newgroup, ierr)
        

    При n = 0 новая группа тождественна старой.

    Подпрограмма MPI_Group_range_incl создает группу newgroup из группы group, добавляя в нее n процессов, ранг которых указан в массиве ranks:

    int MPI_Group_range_incl(MPI_Group oldgroup, int n, int ranks[][3], MPI_Group *newgroup)
    MPI_Group_range_incl(oldgroup, n, ranks, newgroup, ierr)
        

    Массив ranks состоит из целочисленных триплетов вида (первый_1, последний_1, шаг_1), …, (первый_n, последний_n, шаг_n). В новую группу войдут процессы с рангами (по первой группе) первый_1, первый_1 + шаг_1, ….

    Подпрограмма MPI_Group_difference создает новую группу (newgroup) из разности двух групп (group1) и (group2):

    int MPI_Group_difference(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup)
    MPI_Group_difference(group1, group2, newgroup, ierr)
        

    Подпрограмма MPI_Group_intersection создает новую группу (newgroup) из пересечения групп group1 и group2:

    int MPI_Group_intersection(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup)
    MPI_Group_intersection(group1, group2, newgroup, ierr)
        

    Подпрограмма MPI_Group_union создает группу (newgroup), объединяя группы group1 и group2:

    int MPI_Group_union(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup)
    MPI_Group_union(group1, group2, newgroup, ierr)
        

    Имеются и другие подпрограммы-конструкторы новых групп.

    Деструктор группы

    Вызов подпрограммы MPI_Group_free уничтожает группу group:

    int MPI_Group_free(MPI_Group *group)
    MPI_Group_free(group, ierr)
        

    Получение информации о группе

    Для определения количества процессов (size) в группе (group) используется подпрограмма MPI_Group_size:

    int MPI_Group_size(MPI_Group group, int *size)
    MPI_Group_size(group, size, ierr)
        

    Подпрограмма MPI_Group_rank возвращает ранг (rank) процесса в группе group:

    int MPI_Group_rank(MPI_Group group, int *rank)
    MPI_Group_rank(group, rank, ierr)
        

    Если процесс не входит в указанную группу, возвращается значение MPI_UNDEFINED.

    Управление коммуникаторами

    Создание коммуникатора - коллективная операция и соответствующая подпрограмма должна вызываться всеми процессами коммуникатора. Подпрограмма MPI_Comm_dup дублирует уже существующий коммуникатор oldcomm:

    int MPI_Comm_dup(MPI_Comm oldcomm, MPI_Comm *newcomm)
    MPI_Comm_dup(oldcomm, newcomm, ierr)
      

    В результате вызова создается новый коммуникатор (newcomm) с той же группой процессов, с теми же атрибутами, но с другим контекстом.

    Эта подпрограмма может применяться как к интра-, так и интеркоммуникаторам.

    Подпрограмма MPI_Comm_create создает новый коммуникатор (newcomm) из подмножества процессов (group) другого коммуникатора (oldcomm):

    int MPI_Comm_create(MPI_Comm oldcomm, MPI_Group group, MPI_Comm *newcomm)
    MPI_Comm_create(oldcomm, group, newcomm, ierr)
      

    Вызов этой подпрограммы должны выполнить все процессы из старого коммуникатора, даже если они не входят в группу group, с одинаковыми аргументами.

    Данная операция применяется только к интракоммуникаторам. Она позволяет выделять подмножества процессов со своими областями взаимодействия, если, например, требуется уменьшить "зернистость" параллельной программы. Побочным эффектом применения подпрограммы MPI_Comm_create является синхронизация процессов. Если, например, одновременно создаются несколько коммуникаторов, они должны создаваться в одной последовательности всеми процессами.

    Пример

    #include "mpi.h"
    #include <stdio.h>
    int main(int argc,char *argv[])
    {
     char message[24];
     MPI_Group MPI_GROUP_WORLD;
     MPI_Group group;
     MPI_Comm fcomm;
     int size, q, proc;
     int* process_ranks;
     int rank, rank_in_group;
     MPI_Status status;
    
     MPI_Init(argc, argv);
     MPI_Comm_size(MPI_COMM_WORLD, size);
     MPI_Comm_rank(MPI_COMM_WORLD, rank);
    
     printf("New group contains processes:");
     q = size — 1;
     process_ranks = (int*) malloc(q*sizeof(int));
     for (proc = 0; proc < q; proc++)
    {
     process_ranks[proc] = proc;
     printf("%i ", process_ranks[proc]);
     }
     printf("\n");
     MPI_Comm_group(MPI_COMM_WORLD, MPI_GROUP_WORLD);
     MPI_Group_incl(MPI_GROUP_WORLD, q, process_ranks, group);
     MPI_Comm_create(MPI_COMM_WORLD, group, fcomm);
     if (fcomm != MPI_COMM_NULL) {
     MPI_Comm_group(fcomm, group);
     MPI_Comm_rank(fcomm, rank_in_group);
     if (rank_in_group == 0) {
     strcpy(message, "Hi, Parallel Programmer!");
     MPI_Bcast(message, 25, MPI_BYTE, 0, fcomm);
     printf("0 send: %s\n", message);
     }
    else
    {
     MPI_Bcast(message, 25, MPI_BYTE, 0, fcomm);
     printf("%i received: %s\n", rank_in_group, message);
    }
     MPI_Comm_free(fcomm);
     MPI_Group_free(group);
    }
     MPI_Finalize();
     return 0;
    }
        

    В следующем примере процессы разбиваются на две группы. Одна содержит процессы с чётными рангами, а другая – с нечётными.

    #include "stdio.h"
    #include "mpi.h"
    
    void main(int argc, char *argv[])
    {
      int num, p;
      int Neven, Nodd, members[6], even_rank, odd_rank;
      MPI_Group group_world, even_group, odd_group;
    
      MPI_Init(argc, argv); 
      MPI_Comm_rank(MPI_COMM_WORLD, num);  
      MPI_Comm_size(MPI_COMM_WORLD, p);
      Neven = (p + 1)/2;
      Nodd = p - Neven;
      members[0] = 2;
      members[1] = 0;
      members[2] = 4;
      MPI_Comm_group(MPI_COMM_WORLD, group_world);
      MPI_Group_incl(group_world, Neven, members, even_group);
      MPI_Group_excl(group_world, Neven, members,  odd_group);
      MPI_Barrier(MPI_COMM_WORLD);
      if(num == 0) {
       printf("Number of processes is %d\n", p);
       printf("Number of odd processes is %d\n", Nodd);
       printf("Number of even processes is %d\n", Neven);
       printf("members[0] is assigned rank %d\n", members[0]);
       printf("members[1] is assigned rank %d\n", members[1]);
       printf("members[2] is assigned rank %d\n", members[2]);
       printf("\n");
       printf("     num     even      odd\n");
      }
      MPI_Barrier(MPI_COMM_WORLD);
      MPI_Group_rank(even_group, even_rank);
      MPI_Group_rank( odd_group,  odd_rank);
      printf("%8d %8d %8d\n",num, even_rank, odd_rank);
      MPI_Finalize();    
    }
        

    Подпрограмма MPI_Comm_free помечает коммуникатор comm для удаления:

    int MPI_Comm_free(MPI_Comm *comm)
    MPI_Comm_free(comm, ierr)
        

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

    К числу операций управления коммуникаторами можно отнести операции MPI_Comm_size и MPI_Comm_rank. Они позволяют, в частности, распределить роли между процессами в модели master-slave.

    Операции обмена между группами процессов

    Интеробмены

    При выполнении интеробмена процессу-источнику сообщения указывается ранг адресата относительно удаленной группы, а процессу-получателю — ранг источника (также относительно удаленной по отношению к получателю группы).

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

    Интеробмен возможен, только если создан соответствующий интеркоммуникатор, а это можно сделать с помощью подпрограммы:

    int MPI_Intercomm_create(MPI_Comm local_comm, int local_leader, MPI_Comm peer_comm, 
    int remote_leader, int tag, MPI_Comm *new_intercomm)
    MPI_Intercomm_create(local_comm, local_leader, peer_comm, 
    remote_leader, tag, new_intercomm, ierr)
        

    Входные параметры этой подпрограммы:

  • local_comm - локальный интракоммуникатор;
  • local_leader - ранг лидера в локальном коммуникаторе (обычно 0);
  • peer_comm - удаленный коммуникатор;
  • remote_leader - ранг лидера в удаленном коммуникаторе (обычно 0);
  • tag - тег интеркоммуникатора, используемый лидерами обеих групп для обменов в контексте родительского коммуникатора.
  • Выходной параметр - интеркоммуникатор (new_intercomm).

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

    При работе с MPI_Intercomm_create локальная и удаленная группы процессов не должны пересекаться, иначе возможны "тупики".

    Пример

    #include "mpi.h"
    #include <stdio.h>
    int main(int argc,char *argv[])
    {
     int counter, message, myid, numprocs, server;
     int color, remote_leader_rank, i, ICTAG = 0;
     MPI_Status status;
     MPI_Comm oldcommdup, splitcomm, oldcomm, inter_comm;
     MPI_Init(argc, argv);
     oldcomm = MPI_COMM_WORLD;
     MPI_Comm_dup(oldcomm, oldcommdup);
     MPI_Comm_size(oldcommdup, numprocs);
     MPI_Comm_rank(oldcommdup, myid);
     server = numprocs — 1;
     color = (myid == server);
     MPI_Comm_split(oldcomm, color, myid, splitcomm);
    if(!color) {
     remote_leader_rank = server;
     }
     else {
     remote_leader_rank = 0;
     }
     MPI_Intercomm_create(splitcomm, 0, oldcommdup, remote_leader_rank, ICTAG, inter_comm);
     MPI_Comm_free(oldcommdup);
     if (myid == server) {
     for(i = 0; i<server; i++){
      MPI_Recv(message, 1, MPI_INT, i, MPI_ANY_TAG, inter_comm, status);
      printf("Process rank %i received %i from %i\n", myid, message, status.MPI_SOURCE);}
    }
    
    else{
     counter = myid;
     MPI_Send(counter, 1, MPI_INT,  0, 0, inter_comm);
     printf("Process rank %i send %i\n", myid, counter);
    }
     MPI_Comm_free(inter_comm );
     MPI_Finalize();
    } 
        

    В примере процессы делятся на две группы: первая состоит из одного процесса (процесс с максимальным рангом в исходном коммуникаторе MPI_COMM_WORLD), это - "сервер", а во вторую входят все остальные процессы.

    Между этими группами создается интеркоммуникатор inter_comm.

    Процессы-клиенты передают серверу сообщения.

    Сервер принимает эти сообщения с помощью подпрограммы стандартного блокирующего двухточечного приема и выводит их на экран.

    "Ненужные" коммуникаторы удаляются.

    Вернуться к учебному плану