Параллельное программирование на основе MPI
5.3. Операции передачи данных между двумя процессами
Продолжим начатое в п. 5.2.1 изучение функций MPI для выполнения операций передачи данных между процессами параллельной программы.
5.3.1. Режимы передачи данных
Рассмотренная ранее функция MPI_Send обеспечивает так называемый стандартный (standard) режим отправки сообщений, при котором (см. также п. 5.2.1.3):
- на время выполнения функции процесс – отправитель сообщения блокируется;
- после завершения функции буфер может быть использован повторно;
- состояние отправленного сообщения может быть различным – сообщение может располагаться на процессе -отправителе, может находиться в состоянии передачи, может храниться на процессе -получателе или же может быть принято процессом -получателем при помощи функции MPI_Recv.
Кроме стандартного режима в MPI предусматриваются следующие дополнительные режимы передачи сообщений:
- синхронный ( synchronous ) режим состоит в том, что завершение функции отправки сообщения происходит только при получении от процесса - получателя подтверждения о начале приема отправленного сообщения. Отправленное сообщение или полностью принято процессом - получателем, или находится в состоянии приема;
- буферизованный ( buffered ) режим предполагает использование дополнительных системных или задаваемых пользователем буферов для копирования в них отправляемых сообщений. Функция отправки сообщения завершается сразу же после копирования сообщения в системный буфер;
- режим передачи по готовности ( ready ) может быть использован только, если операция приема сообщения уже инициирована. Буфер сообщения после завершения функции отправки сообщения может быть повторно использован.
Для именования функций отправки сообщения для разных режимов выполнения в MPI применяется название функции MPI_Send, к которому как префикс добавляется начальный символ названия соответствующего режима работы, т.е.:
- MPI_Ssend – функция отправки сообщения в синхронном режиме;
- MPI_Bsend – функция отправки сообщения в буферизованном режиме;
- MPI_Rsend – функция отправки сообщения в режиме по готовности.
Список параметров всех перечисленных функций совпадает с составом параметров функции MPI_Send.
Для применения буферизованного режима передачи может быть создан и передан MPI буфер памяти, используемая для этого функция имеет вид:
int MPI_Buffer_attach(void *buf, int size),
где
- buf — адрес буфера памяти;
- size — размер буфера.
После завершения работы с буфером он должен быть отключен от MPI при помощи функции:
int MPI_Buffer_detach(void *buf, int *size),
где
- buf — адрес буфера памяти;
- size — возвращаемый размер буфера.
По практическому использованию режимов можно привести следующие рекомендации:
- стандартный режим обычно реализуется как буферизированный или синхронный, в зависимости от размера передаваемого сообщения, и зачастую является наиболее оптимизированным по производительности;
- режим передачи по готовности формально является наиболее быстрым, но используется достаточно редко, т. к. обычно сложно гарантировать готовность операции приема;
- буферизованный режим также выполняется достаточно быстро, но может приводить к большим расходам ресурсов (памяти), – в целом может быть рекомендован для передачи коротких сообщений;
- синхронный режим является наиболее медленным, т.к. требует подтверждения приема, однако не нуждается в дополнительной памяти для хранения сообщения. Этот режим может быть рекомендован для передачи длинных сообщений.
В заключение отметим, что для функции приема MPI_Recv не существует различных режимов работы.
5.3.2. Организация неблокирующих обменов данными между процессами
Все рассмотренные ранее функции отправки и приема сообщений являются блокирующими, т.е. приостанавливающими выполнение процессов до момента завершения работы вызванных функций. В то же время при выполнении параллельных вычислений часть сообщений может быть отправлена и принята заранее, до момента реальной потребности в пересылаемых данных. В таких ситуациях было бы крайне желательным иметь возможность выполнения функций обмена данными без блокировки процессов для совмещения процессов передачи сообщений и вычислений. Такой неблокирующий способ выполнения обменов является, конечно, более сложным для использования, но при правильном применении может в значительной степени уменьшить потери эффективности параллельных вычислений из-за медленных (по сравнению с быстродействием процессоров) коммуникационных операций.
MPI обеспечивает возможность неблокированного выполнения операций передачи данных между двумя процессами. Наименование неблокирующих аналогов образуется из названий соответствующих функций путем добавления префикса I ( Immediate ). Список параметров неблокирующих функций содержит обычный набор параметров исходных функций и один дополнительный параметр request с типом MPI_Request (в функции MPI_Irecv отсутствует также параметр status ):
int MPI_Isend(void *buf, int count, MPI_Datatype type, int dest, int tag, MPI_Comm comm, MPI_Request *request), int MPI_Issend(void *buf, int count, MPI_Datatype type, int dest, int tag, MPI_Comm comm, MPI_Request *request), int MPI_Ibsend(void *buf, int count, MPI_Datatype type, int dest, int tag, MPI_Comm comm, MPI_Request *request), int MPI_Irsend(void *buf, int count, MPI_Datatype type, int dest, int tag, MPI_Comm comm, MPI_Request *request), int MPI_Irecv(void *buf, int count, MPI_Datatype type, int source, int tag, MPI_Comm comm, MPI_Request *request).
Вызов неблокирующей функции приводит к инициации запрошенной операции передачи, после чего выполнение функции завершается и процесс может продолжить свои действия. Перед своим завершением неблокирующая функция определяет переменную request, которая далее может использоваться для проверки завершения инициированной операции обмена.
Проверка состояния выполняемой неблокирующей операции передачи данных производится при помощи функции:
int MPI_Test(MPI_Request *request, int *flag, MPI_status *status),
где
- request — дескриптор операции, определенный при вызове неблокирующей функции;
- flag — результат проверки ( true, если операция завершена);
- status — результат выполнения операции обмена (только для завершенной операции).
Операция проверки является неблокирующей, т.е. процесс может проверить состояние неблокирующей операции обмена и продолжить далее свои вычисления, если по результатам проверки окажется, что операция все еще не завершена. Возможная схема совмещения вычислений и выполнения неблокирующей операции обмена может состоять в следующем:
MPI_Isend(buf, count, type, dest, tag, comm, &request); ѕ do { ѕ MPI_Test(&request, &flag, &status); } while (!flag);
Если при выполнении неблокирующей операции окажется, что продолжение вычислений невозможно без получения передаваемых данных, то может быть использована блокирующая операция ожидания завершения операции:
int MPI_Wait(MPI_Request *request, MPI_status *status),
где
- request — дескриптор операции, определенный при вызове неблокирующей функции;
- status — результат выполнения операции обмена (только для завершенной операции).
Кроме рассмотренных, MPI содержит ряд дополнительных функций проверки и ожидания неблокирующих операций обмена:
- MPI_Testall — проверка завершения всех перечисленных операций обмена;
- MPI_Waitall — ожидание завершения всех операций обмена;
- MPI_Testany — проверка завершения хотя бы одной из перечисленных операций обмена;
- MPI_Waitany — ожидание завершения любой из перечисленных операций обмена;
- MPI_Testsome — проверка завершения каждой из перечисленных операций обмена;
- MPI_Waitsome — ожидание завершения хотя бы одной из перечисленных операций обмена и оценка состояния по всем операциям.
Приведение простого примера использования неблокирующих функций достаточно затруднительно. Хорошей возможностью для освоения рассмотренных функций могут служить, например, параллельные алгоритмы матричного умножения (см. "Параллельные методы матричного умножения" ).
5.3.3. Одновременное выполнение передачи и приема
Одной из часто выполняемых форм информационного взаимодействия в параллельных программах является обмен данными между процессами, когда для продолжения вычислений процессам необходимо отправить данные одним процессам и в то же время получить сообщения от других. Простейший вариант этой ситуации состоит, например, в обмене данными между двумя процессами. Реализация таких обменов при помощи обычных парных операций передачи данных может быть неэффективна, кроме того, такая реализация должна гарантировать отсутствие тупиковых ситуаций, которые могут возникать, например, когда два процесса начинают передавать сообщения друг другу с использованием блокирующих функций передачи данных.
Достижение эффективного и гарантированного одновременного выполнения операций передачи и приема данных может быть обеспечено при помощи функции MPI:
int MPI_Sendrecv(void *sbuf,int scount,MPI_Datatype stype, int dest, int stag, void *rbuf,int rcount,MPI_Datatype rtype, int source,int rtag, MPI_Comm comm, MPI_Status *status),
где
- sbuf, scount, stype, dest, stag — параметры передаваемого сообщения;
- rbuf, rcount, rtype, source, rtag — параметры принимаемого сообщения;
- comm — коммуникатор, в рамках которого выполняется передача данных;
- status — структура данных с информацией о результате выполнения операции.
Как следует из описания, функция MPI_Sendrecv передает сообщение, описываемое параметрами ( sbuf, scount, stype, dest, stag ), процессу с рангом dest и принимает сообщение в буфер, определяемый параметрами ( rbuf, rcount, rtype, source, rtag ), от процесса с рангом source.
В функции MPI_Sendrecv для передачи и приема сообщений применяются разные буферы. В случае же когда отсылаемое сообщение больше не нужно на процессе -отправителе, в MPI имеется возможность использования единого буфера:
int MPI_Sendrecv_replace(void *buf, int count, MPI_Datatype type, int dest, int stag, int source, int rtag, MPI_Comm comm, MPI_Status* status),
где
- buf, count, type — параметры передаваемого сообщения;
- dest — ранг процесса, которому отправляется сообщение;
- stag — тег для идентификации отправляемого сообщения;
- source — ранг процесса, от которого выполняется прием сообщения;
- rtag — тег для идентификации принимаемого сообщения;
- comm — коммуникатор, в рамках которого выполняется передача данных;
- status — структура данных с информацией о результате выполнения операции.
Пример использования функций для одновременного выполнения операций передачи и приема приведен в "Параллельные методы матричного умножения" при разработке параллельных программ матричного умножения.