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

Двухточечные обмены

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

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

Двухточечные обмены

Двухточечный (point-to-point, p2p) обмен

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

Имеется несколько разновидностей двухточечного обмена

  • блокирующие прием/передача, которые приостанавливают выполнение процесса на время приема или передачи сообщения;
  • неблокирующие прием/передача, при которых выполнение процесса продолжается в фоновом режиме, а программа в нужный момент может запросить подтверждение завершения приема сообщения;
  • синхронный обмен, который сопровождается уведомлением об окончании приема сообщения;
  • асинхронный обмен, который таким уведомлением не сопровождается.
  • Двухточечный обмен возможен только между процессами, принадлежащими одной области взаимодействия (одному коммуникатору).

    Правильно организованный двухточечный обмен сообщениями должен исключать возможность блокировки или некорректной работы параллельной MPI-программы.

    Примеры ошибок в организации двухточечных обменов:

  • выполняется передача сообщения, но не выполняется его прием;
  • процесс-источник и процесс-получатель одновременно пытаются выполнить блокирующие передачу или прием сообщения.
  • Правильно

    Неправильно

    Неправильно

    В MPI приняты следующие соглашения об именах подпрограмм двухточечного обмена:

                            
    MPI_[I][R, S, B]Send
         

    здесь префикс [I] (Immediate) обозначает неблокирующий режим. Один из префиксов [R, S, B] обозначает режим обмена: по готовности, синхронный и буферизованный.

    Отсутствие префикса обозначает подпрограмму стандартного обмена.

    Имеется 8 разновидностей операции передачи сообщений.

    Для подпрограмм приема:

                            
    MPI_[I]Recv
         

    то есть всего 2 разновидности приема.

    Подпрограмма приема любого типа может принять сообщения от любой подпрограммы передачи.

    Стандартный блокирующий двухточечный обмен

    Передача-прием сообщения (p2p)

    Cтандартная блокирующая передача

                            
    MPI_Recv(buf, count, datatype, dest, tag, comm, status, ierr)
       
                            
    MPI_Send(buf, count, datatype, dest, tag, comm,  ierr)
         
  • buf - адрес первого элемента в буфере передачи;
  • count -  количество элементов в буфере передачи (допускается count = 0);
  • datatype - тип MPI каждого пересылаемого элемента;
  • dest  - ранг процесса-получателя сообщения (целое число от 0 до n – 1, где n  число процессов в области взаимодействия);
  • tag  - тег сообщения;
  • comm - коммуникатор;
  • ierr - код завершения.
  • При стандартной блокирующей передаче после завершения вызова (после возврата из функции/процедуры передачи) можно использовать любые переменные, использовавшиеся в списке параметров. Такое использование не повлияет на корректность обмена.

    Дальнейшая "судьба" сообщения зависит от реализации MPI. Сообщение может быть сразу передано процессу-получателю или может быть скопировано в буфер передачи.

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

    Cтандартный блокирующий прием

                         
    int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)
    MPI_Recv(buf, count, datatype, dest, tag, comm, status, ierr)
              
  • buf - адрес первого элемента в буфере передачи;
  • count -  количество элементов в буфере передачи;
  • datatype - тип MPI каждого пересылаемого элемента;
  • source  - ранг процесса-отправителя сообщения ( целое число от 0 до n – 1, где n  число процессов в области взаимодействия);
  • tag  - тег сообщения;
  • comm - коммуникатор;
  • status - статус обмена;
  • ierr - код завершения.
  • Значение параметра count может оказаться больше, чем количество элементов в принятом сообщении. В этом случае после выполнения приема в буфере изменится значение только тех элементов, которые соответствуют элементам фактически принятого сообщения.

    Для функцииMPI_Recv гарантируется, что после завершения вызова сообщение принято и размещено в буфере приема.

    Джокеры

    В качестве ранга источника сообщения и в качестве тега сообщения можно использовать "джокеры" :

  • MPI_ANY_SOURCE - любой источник;
  • MPI_ANY_TAG - любой тег.
  • Подпрограмма MPI_Recv может принимать сообщения, отправленные в любом режиме.

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

    Пример использования операции блокирующего двухточечного обмена

                           
    #include <mpi.h>
    #include <stdio.h>
    int main ( int argc, char *argv[] ) {
      int ProcNum, ProcRank, tmp;
      MPI_Status status;
      MPI_Init ( argc, argv );
      MPI_Comm_size ( MPI_COMM_WORLD, ProcNum);
      MPI_Comm_rank ( MPI_COMM_WORLD, ProcRank);
      if(ProcRank == 0){
        printf("Hello world from process %i \n", ProcRank);
        for(int i = 1; i < ProcNum; i++){
          MPI_Recv(tmp,1,MPI_INT,MPI_ANY_SOURCE,0,MPI_COMM_WORLD, status);
          printf("Hello world from process %i \n", tmp);
        }
      }else{
        MPI_Send(ProcRank,1,MPI_INT,0,0,MPI_COMM_WORLD);
      }
      MPI_Finalize();
      return 0;
    }
    

    Размер полученного сообщения (count) можно определить с помощью вызова подпрограммы

                         
              int MPI_Get_count(MPI_Status *status, MPI_Datatype datatype, int *count)
    
              MPI_Get_count(status, datatype, count, ierr)
             
  • count -  количество элементов в буфере передачи;
  • datatype - тип MPI каждого пересылаемого элемента;
  • status - статус обмена;
  • ierr - код завершения.
  • Аргумент datatype должен соответствовать типу данных, указанному в операции обмена

    Двухточечный обмен с буферизацией

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

    Размер буфера должен превосходить размер сообщения на величину MPI_BSEND_OVERHEAD. Это дополнительное пространство используется подпрограммой буферизованной передачи для своих целей.

    Если перед выполнением операции буферизованного обмена не выделен буфер, MPI ведет себя так, как если бы с процессом был связан буфер нулевого размера. Работа с таким буфером обычно завершается сбоем программы.

    Буферизованный обмен рекомендуется использовать в тех ситуациях, когда программисту требуется больший контроль над распределением памяти. Этот режим удобен и для отладки, поскольку причину переполнения буфера определить легче, чем причину тупика.

    При выполнении буферизованного обмена программист должен заранее создать буфер достаточного размера:

                            
    int MPI_Buffer_attach(void *buf, size)
    
    MPI_Buffer_attach(buf, size, ierr)
               

    В результате вызова создается буфер buf размером size байтов. В программах на языке Fortran роль буфера может играть массив. За один раз к процессу может быть подключен только один буфер.

    Буферизованная передача завершается сразу, поскольку сообщение немедленно копируется в буфер для последующей передачи. В отличие от стандартного обмена, в этом случае работа источника и адресата не синхронизована:

                            
    int MPI_Bsend(void *buf, int count, MPI_Datatype datatype, int   dest, int tag, MPI_Comm comm)
    
    MPI_Bsend(buf, count, datatype, dest, tag, comm, ierr)
               

    После завершения работы с буфером его необходимо отключить:

                            
    int MPI_Buffer_detach(void *buf, int *size)
    
    MPI_Buffer_detach(buf, size, ierr)
               

    Возвращается адрес (buf) и размер отключаемого буфера (size). Эта операция блокирует работу процесса до тех пор, пока все сообщения, находящиеся в буфере, не будут обработаны. Вызов данной подпрограммы можно использовать для форсированной передачи сообщений. После завершения вызова можно вновь использовать память, которую занимал буфер. В языке C данный вызов не освобождает автоматически память, отведенную для буфера.

    Пример программы, использующей обмен с буферизацией

                            
    #include "mpi.h"
    #include <stdio.h>
    int main(int argc,char *argv[])
    {
     int *buffer;
     int myrank;
     MPI_Status status;
     int buffsize = 1;
     int TAG = 0;
     MPI_Init(argc, argv);
     MPI_Comm_rank(MPI_COMM_WORLD, myrank);
     if (myrank == 0)
    {
     buffer = (int *) malloc(buffsize + MPI_BSEND_OVERHEAD);
     MPI_Buffer_attach(buffer, buffsize + MPI_BSEND_OVERHEAD);
     buffer = (int *) 10;
     MPI_Bsend(buffer, buffsize, MPI_INT, 1, TAG, MPI_COMM_WORLD);
     MPI_Buffer_detach(buffer, buffsize);
    }
    else
    {
     MPI_Recv(buffer, buffsize, MPI_INT, 0, TAG, MPI_COMM_WORLD, status);
     printf("received: %i\n", buffer);
    }
     MPI_Finalize();
     return 0;} 
                 

    Другие разновидности двухточечного обмена

    Cинхронный обмен

    Завершение передачи происходит только после того, как прием сообщения инициализирован другим процессом. Адресат посылает источнику "квитанцию" - уведомление о завершении приема. После получения этого уведомления обмен считается завершенным и источник "знает", что его сообщение получено:

                                        
    int MPI_Ssend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
    
    MPI_SSEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, IERR)
                    

    Обмен "по готовности"

    Передача "по готовности" выполняется с помощью подпрограммы MPI_Rsend:

                                        
    int MPI_Rsend(void *buf, int count, MPI_Datatype  datatype, int dest, int tag, MPI_Comm comm)
     
    MPI_Rsend(buf, count, datatype, dest, tag, comm, ierr)
    

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

    Совместные прием и передача

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

    Подпрограммы приемопередачи могут взаимодействовать с обычными подпрограммами обмена и подпрограммами зондирования.

    Подпрограмма MPI_Sendrecv выполняет прием и передачу данных с блокировкой:

                              
    int MPI_Sendrecv(void *sendbuf, int sendcount, MPI_Datatype sendtype, int dest, int sendtag, void *recvbuf, 
    int recvcount, MPI_Datatype recvtype, int source, int recvtag, MPI_Comm comm, MPI_Status *status)
    
    MPI_Sendrecv(sendbuf, sendcount, sendtype, dest, sendtag, recvbuf, recvcount, recvtype, source, recvtag, comm, status, ierr) 
                        

    Имеются разновидности операции приемопередачи.

    Подпрограмма MPI_Sendrecv_replace выполняет прием и передачу данных, используя общий буфер для передачи и приема:

                            
    int MPI_Sendrecv_replace(void *buf, int count,  MPI_Datatype datatype, int dest, int sendtag, 
    int source,  int recvtag, MPI_Comm comm, MPI_Status *status)
    
    MPI_Sendrecv_replace(BUF, COUNT, DATATYPE, DEST, SENDTAG, SOURCE, RECVTAG, COMM, STATUS, IERR) 
                        
    Вернуться к учебному плану