Подтвердить что ты не робот

MPI: блокирование против неблокирования

У меня возникают проблемы с пониманием концепции блокирования связи и неблокирования связи в MPI. Каковы различия между этими двумя? Какие преимущества и недостатки?

4b9b3361

Ответ 1

Блокировка связи осуществляется с помощью MPI_Send() и MPI_Recv(). Эти функции не возвращаются (то есть блокируются) до тех пор, пока связь не будет завершена. Упрощенно это означает, что буфер, переданный в MPI_Send() можно использовать повторно либо потому, что MPI где-то его сохранил, либо потому, что он был получен адресатом. Точно так же MPI_Recv() возвращается, когда приемный буфер был заполнен допустимыми данными.

Напротив, неблокирующая связь осуществляется с использованием MPI_Isend() и MPI_Irecv(). Эти функции возвращаются немедленно (т.е. они не блокируются), даже если связь еще не завершена. Вы должны вызвать MPI_Wait() или MPI_Test() чтобы увидеть, завершена ли связь.

Блокирующая связь используется, когда этого достаточно, поскольку ее несколько проще использовать. Неблокирующая связь используется при необходимости, например, вы можете вызвать MPI_Isend(), выполнить некоторые вычисления, а затем выполнить MPI_Wait(). Это позволяет вычислениям и коммуникациям перекрываться, что обычно приводит к повышению производительности.

Обратите внимание, что коллективное общение (например, все-сокращение) доступно только в его версии блокировки до MPIv2. IIRC, MPIv3 представляет неблокирующую коллективную связь.

Краткий обзор режимов отправки MPI можно посмотреть здесь.

Ответ 2

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

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

В отличие от отправки, получение имеет только один режим и может быть блокирующим или неблокирующим.

Прежде чем продолжить, необходимо также уточнить, что я явно упоминаю, какой из них является буфером MPI_Send\Recv, а какой - системным буфером (который является локальным буфером в каждом процессоре, принадлежащем библиотеке MPI, используемой для перемещения данных между рядами связи группа)

БЛОКИРОВКА СВЯЗИ: Блокировка не означает, что сообщение было доставлено получателю/получателю. Это просто означает, что буфер (отправка или получение) доступен для повторного использования. Чтобы повторно использовать буфер, достаточно скопировать информацию в другую область памяти, то есть библиотека может скопировать данные буфера в собственное место памяти в библиотеке, а затем, например, например, MPI_Send может вернуться.

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

1. Стандартный режим

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

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

Синтаксис для стандартной отправки ниже:

int MPI_Send(const void *buf, int count, MPI_Datatype datatype, 
             int dest, int tag, MPI_Comm comm)

2. Буферизованный режим

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

Синтаксис для буфера отправки:

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

3. Синхронный режим

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

Синтаксис для синхронной отправки:

int MPI_Ssend(const void *buf, int count, MPI_Datatype datatype, int dest,
              int tag, MPI_Comm comm)

4. Готовый режим

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

Синтаксис для готовой отправки:

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

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

Например, MPI_Send в общем случае является режимом блокировки, но в зависимости от реализации, если размер сообщения не слишком велик, MPI_Send скопирует исходящее сообщение из буфера отправки в системный буфер ("что в большинстве случаев имеет место в современной системе) и немедленно вернется. Давайте посмотрим на пример ниже:

//assume there are 4 processors numbered from 0 to 3
if(rank==0){
    tag=2;
    MPI_Send(&send_buff1, 1, MPI_DOUBLE, 1, tag, MPI_COMM_WORLD);
    MPI_Send(&send_buff2, 1, MPI_DOUBLE, 2, tag, MPI_COMM_WORLD);
    MPI_Recv(&recv_buff1, MPI_FLOAT, 3, 5, MPI_COMM_WORLD);
    MPI_Recv(&recv_buff2, MPI_INT, 1, 10, MPI_COMM_WORLD);
}

else if(rank==1){
     tag = 10;
    //receive statement missing, nothing received from proc 0
    MPI_Send(&send_buff3, 1, MPI_INT, 0, tag, MPI_COMM_WORLD);
    MPI_Send(&send_buff3, 1, MPI_INT, 3, tag, MPI_COMM_WORLD);
}

else if(rank==2){
    MPI_Recv(&recv_buff, 1, MPI_DOUBLE, 0, 2, MPI_COMM_WORLD);
    //do something with receive buffer
}

else{ //if rank == 3
    MPI_Send(send_buff, 1, MPI_FLOAT, 0, 5, MPI_COMM_WORLD);
    MPI_Recv(recv_buff, 1, MPI_INT, 1, 10, MPI_COMM_WORLD);
}

Давайте посмотрим, что происходит на каждом уровне в приведенном выше примере

Ранг 0 пытается отправить на ранг 1 и ранг 2, и получить от ранг 1 и 3.

Ранг 1 пытается отправить на ранг 0 и ранг 3 и не получает ничего из других рангов

Ранг 2 пытается получить от ранга 0, а затем выполнить некоторую операцию с данными, полученными в recv_buff.

Ранг 3 пытается отправить на ранг 0 и получить с рангом 1

Новички смущаются, когда ранг 0 отправляется на ранг 1, но ранг 1 не начал какую-либо операцию приема, поэтому связь должна блокироваться или останавливаться, а второй оператор отправки на ранге 0 вообще не должен выполняться (и это именно то, что MPI). В документации подчеркивается, что именно реализация определяет, будет ли исходящее сообщение буферизовано или нет). В большинстве современных систем такие сообщения небольших размеров (здесь размер равен 1) будут легко буферизироваться, и MPI_Send вернет и выполнит его в следующем операторе MPI_Send. Следовательно, в приведенном выше примере, даже если прием в ранге 1 не начат, 1-й MPI_Send в ранге 0 вернется и выполнит свой следующий оператор.

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

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

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

К счастью, MPI решил сделать вещи проще для пользователей с точки зрения приема, и в блокирующем обмене есть только один прием: MPI_Recv, и его можно использовать с любым из четырех режимов отправки, описанных выше. Для MPI_Recv блокировка означает, что получение возвращается только после того, как оно содержит данные в своем буфере. Это подразумевает, что получение может завершиться только после начала соответствующей отправки, но не подразумевает, может ли оно завершиться до завершения соответствующей отправки.

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

НЕБЛОКИРУЮЩАЯ СВЯЗЬ: Для неблокирующей связи приложение создает запрос на обмен данными для отправки и/или приема и возвращает дескриптор, а затем завершается. Это все, что нужно, чтобы гарантировать, что процесс выполняется. Т.е. библиотека MPI уведомляется о том, что операция должна быть выполнена.

Для отправителя это позволяет совмещать вычисления с коммуникацией.

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

Ответ 3

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

 if(rank==0)
 {
     MPI_Send(x to process 1)
     MPI_Recv(y from process 1)
 }
 if(rank==1)
 {
     MPI_Send(y to process 0);
     MPI_Recv(x from process 0);
 }

Что происходит в этом случае?

  • Процесс 0 отправляет x для обработки 1 и блокирует до тех пор, пока процесс 1 не получит x.
  • Процесс 1 отправляет y для обработки 0 и блокирует до тех пор, пока процесс 0 не получит y, но
  • процесс 0 блокируется таким образом, что процесс 1 блокирует бесконечность до тех пор, пока оба процесса не будут убиты.

Ответ 4

Это легко.

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

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