При создании этого документа помимо англоязычных пособий были использованы следующие авторские курсы на русском языке:
MPI (message passing interface) - стандартизованная и переносимая система передачи сообщений (библиотека функций). Стандарт определяет синтаксис и семантику библиотечных функций, используемых при написании переносимых программ с передачей сообщений на языках Fortran 77, С и С++.
Другими словами, MPI - это программный инструментарий для обеспечения связи между ветвями параллельного приложения.
Стандарт MPI-1 был принят группой порядка 80 исследователей из 40 организаций (как академических, так и промышленных) во время серии встреч в 1992-93 гг. В обсуждении стандарта могли принять участие все желающие. В июне 1994 была реализована версия 1.0
Существует несколько протестированных эффективных реализаций MPI, в том числе и для свободного использования (например, MPICH).
Введение
Парадигма передачи сообщений широко используется на параллельных компьютерах, в основном на масштабируемых (SPCs) с распределенной памятью и на сети рабочих станций. Хотя существуют различные вариации, основная концепция процесса взаимодействия процессоров посредством передачи сообщений хорошо понятна. Ранее каждый производитель параллельных компьютеров разрабатывал свою собственную библиотеку. Пока, наконец, не был разработан стандарт MPI. Разработчики использовали наиболее привлекательные возможности многих существующих систем передачи сообщений, а не адаптировали одну из систем как стандарт.
Основная цель MPI - достигнуть переносимость разрабатываемого ПО для разных параллельных компьютеров. В том числе и для компьютеров с разделяемой памятью, если для них реализован MPI. Разработанный код также может выполняться на сети рабочих станций, либо, как множество процессов на одной станции. Это, в частности, дает возможность отлаживать программы на персональном компьютере.
Другой тип совместимости, предлагаемый MPI - это возможность прозрачной работы на разнородных системах (множестве процессоров с различной архитектурой). В этом случае MPI будет автоматически производить конвертацию данных и использовать корректный протокол взаимодействия (однако надо использовать реализацию MPI для разнородных архитектур).
Другое важное достоинство MPI - высокая эффективность, даже посравнению с "родными" системами. Также, от пользователя скрыто, как именно выполняется та или иная операция, важно лишь то, что она делает. В результате MPI может быть легко реализован как на системах с буферизацией сообщений, так и без оной. Различные реализации могут использовать преимущества коммуникационных подсистем мультикомпьютера, например, при наличии коммуникационного сопроцессора выполнять значительную часть протокола на нем.
Эффективность MPI достигается также за счет невыполнения ненужной работы по пересылке большого количества лишней информации с каждым сообщением или кодированию-декодированию заголовков сообщений. MPI был разработан так, чтобы поддерживатьодновременное выполнение вычислений и коммуникаций, чтобы использовать наличие сопроцессоров. Это достигается использованием неблокируемых коммуникационных вызовов, которые разделяют инициацию коммуникации и ее завершение. Для повышения скорости в MPI используются приемы,о которых прикладные программисты зачастую просто не задумываются. Например, встроенная буферизация позволяет избежать задержек при отправке данных - управление в передающую ветвь возвращается немедленно, даже если ветвь-получатель еще не подготовилась к приему. MPI использует многопоточность (multi-threading), вынося большую часть своей работы в потоки(threads) с низким приоритетом. Буферизация и многопоточность сводят к минимуму негативное влияние неизбежных простоев при пересылках на производительность прикладной программы. На передачу данных типа "один-всем" затрачивается время, пропорциональное не числу участвующих ветвей, а логарифму этого числа. И так далее...
Масштабируемость - основная цель параллельной обработки. MPI позволяет или поддерживает масштабируемость.
Цели MPI
Разработчики ставили своей задачей:
Платформы
Как уже было сказано, программы, написанные с использованием MPI библиотеки, могут выполняться на мультикомпьютерах с распределенной, разделяемой памятью, на сети рабочих станций, на любой комбинации этих трех типов архитектур.
Интерфейс может использоваться в MIMD и MPMD программах (когда каждый процесс следует своим собственным путем в одном коде или даже выполняет свой код). Также может использоваться в SPMD программах. Хотя явная поддержка подпроцессов не обеспечивается, интерфейс разработан таким образом, чтобы не помешать их использованию. В следующей версии MPI обеспечивается динамическое порождение процессов.
Что включается в MPI:
Что не включено:
В июне 1995 была реализована версия 1.1 (с изменениями, которые были действительно срочно необходимы).
Процессы
MPI программа состоит из автономных процессов, выполняющих собственный код (Fortran 77, C, C++) в MIMD стиле. Коды, выполняемые каждым процессом, не обязаны быть идентичными. Разные процессы могут выполняться как на разных процессорах, так и на одном и том же - для программы это роли не играет, поскольку в обоих случаях механизм обмена данными одинаков. Процессы взаимодействуют посредством вызовов MPI примитивов. Обычно, каждый процесс выполняется над своим собственным адресным пространством, хотя возможны реализации MPI для разделяемой памяти.
MPI не определяет модель выполнения для каждого процесса. Процесс может быть последовательным либо состоять из подпроцессов, которые могут выполняться одновременно. В этом случае блокирующие MPI вызовы блокируют только тот подпроцесс, к которому обращен вызов.
MPI не обеспечивает механизмов для определения начального распределения процессов на физических процессорах. Этим занимается та система, на которой реализован MPI. Также стандарт MPI-1 не обеспечивает динамическое порождение и удаление процессов.
Итак, процессы обмениваются друг с другом данными в виде сообщений. Сообщения проходят под идентификаторами, которые позволяют программе и библиотеке связи отличать их друг от друга. Для совместного проведения тех или иных расчетов процессы внутри приложения объединяются в группы. Каждый процесс может узнать свой номер внутри группы, и, в зависимости от номера выполнять соответствующую часть расчетов. MPI всегда идентифицирует процессы в соответствии с их номером в группе (начиная с 0).
Особенность MPI: понятие области связи (communication domains). При запуске приложения все процессы помещаются в создаваемую для приложения общую область связи. При необходимости они могут создавать новые области связи на базе существующих. Все области связи имеют независимую друг от друга нумерацию процессов (нумерация начинается с 0). Программе пользователя в распоряжение предоставляется коммуникатор - описатель области связи. Многие функции MPI имеют среди входных аргументов коммуникатор, который ограничивает сферу их действия той областью связи, к которой он прикреплен. Для одной области связи может существовать несколько коммуникаторов. В этом случае приложение работает с такойобластью связи, как с несколькими разными областями. В исходных текстах примеров для MPI часто используется идентификатор MPI_COMM_WORLD. Это название коммуникатора, создаваемого библиотекой автоматически. Он описывает стартовую область связи, объединяющую все процессы приложения.
Типы вызовов (функций) MPI
Другими словами, локальные функции не инициируют пересылок данных между ветвями. Большинство информационных функций является локальными, т.к. копии системных данных уже хранятся в каждой ветви. Функция передачи MPI_Send и функция синхронизации MPI_Barrier НЕ являются локальными, поскольку производят пересылку. Следует заметить, что, к примеру, функция приема MPI_Recv (парная для MPI_Send) является локальной: она всего лишь пассивно ждет поступления данных, ничего не пытаясь сообщить другим ветвям.
Именованные константы
MPI процедуры иногда присваивают специальные значения аргументам. Например, tag - целочисленный аргумент операций процесс-процесс взаимодействий. Он может принимать специальное значение MPI_ANY_TAG.
Все именованные константы могут быть использованы при инициализации выражений. (искл-е - MPI_BOTTOM в Fortran). Эти константы не изменяют свое значение в процессе выполнения. В конце документа приведен список констант.
Стиль
Все идентификаторы начинаются с префикса "MPI_". Это правило без исключений. Не рекомендуется заводить пользовательские идентификаторы, начинающиеся с этой приставки, а также с приставок "MPID_", "MPIR_" и "PMPI_", которые используются в служебных целях.
Если идентификатор сконструирован из нескольких слов, слова в нем разделяются подчерками: MPI_Get_count, MPI_Comm_rank. Иногда, однако, разделитель не используется: MPI_Sendrecv, MPI_Alltoall.
Порядок слов в составном идентификаторе выбирается по принципу "от общего к частному": сначала префикс "MPI_", потом название категории ( Type, Comm, Group, Attr, Errhandler и т.д.), потом название операции ( MPI_Errhandler_create, MPI_Errhandler_set, ...). Наиболее часто употребляемые функции выпадают из этой схемы: они имеют "анти-методические", но короткие и стереотипные названия, например MPI_Barrier, или MPI_Unpack.
Имена констант (и неизменяемых пользователем переменных) записываются полностью заглавными буквами: MPI_COMM_WORLD, MPI_FLOAT. В именах функций первая за префиксом буква - заглавная, остальные маленькие: MPI_Send, MPI_Comm_size.
Заголовочный файл - mpi.h
Почти все функции возвращают код ошибки. Если все в порядке - MPI_SUCCESS. Несколько функций не возвращают этот код, поэтому могут выполняться как макросы.
Аргумены массивов нумеруются с 0.
0 - false, non-zero - true
При описании процедур MPI будем пользоваться словом OUT для обозначения "выходных" параметров, т.е. таких параметров, через которые процедура возвращает результаты.
Общие процедуры MPI
int MPI_Init( int* argc, char*** argv)
Возвращает: в случае успешного выполнения - MPI_SUCCESS, иначе - код ошибки. То же самое возвращают и все остальные функции, рассматриваемые в данном руководстве. MPI_Init получает адреса аргументов, стандартно получаемых самой main от операционной системыи хранящих параметры командной строки. В конец командной строки программы MPI-загрузчик mpirun добавляет ряд информационных параметров, которые требуются MPI_Init.
MPI_Abort(MPI_comm comm, int errorcode)
Вызов MPI_Abort из любой задачи принудительно завершает работу ВСЕХ задач, подсоединенных к заданной области связи. Если указан описатель MPI_COMM_WORLD, будет завершено всеприложение (все его задачи) целиком, что, по-видимому, и является наиболее правильным решением. Используйте код ошибки MPI_ERR_OTHER, если не знаете, как охарактеризовать ошибку в классификации MPI.
int MPI_Finalize( void )
Настоятельно рекомендуется не забывать вызывать эту функцию перед возвращением из программы. Для программы на языках C/C++ это:
Типы данных
Для описания базовых типов Си в MPI определены константы MPI_INT, MPI_CHAR, MPI_DOUBLE и так далее,имеющие тип MPI_Datatype. Их названия образуются префиксом "MPI_" и именем соответствующего типа (int, char, double, ...), записанным заглавными буквами (список констант, описывающих тип, приведен в конце документа). Пользователь может "регистрировать" в MPI свои собственные типы данных, например, структуры, после чего MPI сможет обрабатывать их наравне с базовыми. Процесс регистрации описывается ниже.
Процесс-процесс взаимодействия
Основной механизм коммуникаций в MPI - передача данных между парой процессов, одна сторона посылает, другая - получает.
MPI обеспечивает набор функций посылки и приема, которые посылают данные определенного типа с ассоциированным msgtag. Тип нужен для неоднородных систем - чтобы правильно конвертировать. Msgtag (это целое число от 0 до 32767, которое пользователь выбирает сам) дает возможность выбирать при приеме - можно принимать сообщения только с определенным msgtag, либо с любым (MPI_ANY_TAG).
Блокирующие функции MPI_Send и MPI_Recv
buf можно переписать после того, как содержимое буфера будет востребовано
int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int msgtag, MPI_Comm comm)
Блокирующая посылка сообщения с идентификатором msgtag, состоящего из count элементов типа datatype, процессу с номером dest. Все элементы сообщения расположены подряд в буфере buf. Значение count может быть нулем. Тип передаваемых элементов datatype должен указываться с помощью предопределенных констант типа. Разрешается передавать сообщение самому себе.
Блокировка гарантирует корректность повторного использования всех параметров после возврата из подпрограммы. Выбор способа осуществления этой гарантии: копирование в промежуточный буфер или непосредственная передача процессу dest, остается за MPI. Следует специально отметить, что возврат из подпрограммы MPI_Send не означает ни того, что сообщение уже передано процессу dest, ни того, что сообщение покинуло процессорный элемент, на котором выполняется процесс, выполнивший MPI_Send.
int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int msgtag, MPI_comm comm, MPI_Status *status)
Прием сообщения с идентификатором msgtag от процесса source с блокировкой. Число элементов в принимаемом сообщении не должно превосходить значения count. Если число принятых элементов меньше значения count, то гарантируется, что в буфере buf изменятся только элементы, соответствующие элементам принятого сообщения. Если нужно узнать точное число элементов в сообщении, то можно воспользоваться функцией MPI_Probe.
Блокировка гарантирует, что после возврата из подпрограммы все элементы сообщения приняты и расположены в буфере buf.
В качестве номера процесса-отправителя можно указать предопределенную константу MPI_ANY_SOURCE - признак того, что подходит сообщение от любого процесса. В качестве идентификатора принимаемого сообщения можно указать константу MPI_ANY_TAG - признак того, что подходит сообщение с любым идентификатором.
Если процесс посылает два сообщения другому процессу и оба эти сообщения соответствуют одному и тому же вызову MPI_Recv, то первым будет принято то сообщение, которое было отправлено раньше.
status содержит информацию о принятом сообщении: его идентификатор, номер задачи-передатчика, код завершения и количество фактически пришедших данных.
С одной стороны, мы передаем в MPI_Recv номер задачи, от которой ждем сообщение, и его идентификатор; а с другой - получаем их от MPI в структуре status? Это сделано потому, что MPI_Recv может быть вызвана с аргументами-джокерами ("принимай что угодно/от кого угодно"), и после такого приема данных программа узнает фактические номер/идентификатор, читая поля MPI_SOURCE и MPI_TAG из структуры status.
Поле MPI_ERROR, как правило, проверять необязательно - обработчик ошибок, устанавливаемый MPI по умолчанию, в случае сбоя завершит выполнение программы ДО возврата из MPI_Recv. Таким образом, после возврата из MPI_Recv поле status.MPI_ERROR может быть равно только 0 (MPI_SUCCESS);
Тип MPI_Status не содержит поля, в которое записывалась бы фактическая длина пришедшего сообщения. Длину можно узнать так
int MPI_Get_Count( MPI_Status *status, MPI_Datatype datatype, int *count)
По значению параметра status данная подпрограмма определяет число уже принятых (после обращения к MPI_Recv) или принимаемых (после обращения к MPI_Probe или MPI_IProbe) элементов сообщения типа datatype.
int MPI_Probe( int source, int msgtag, MPI_Comm comm, MPI_Status *status)
Получение информации о структуре ожидаемого сообщения с блокировкой. Возврата из подпрограммы не произойдет до тех пор, пока сообщение с подходящим идентификатором и номером процесса-отправителя не будет доступно для получения. Атрибуты доступного сообщения можно определить обычным образом с помощью параметра status. Следует обратить внимание, что подпрограмма определяет только факт прихода сообщения, но реально его не принимает. Стандарт MPI гарантирует, что следующий за MPI_Probe вызов MPI_Recv с теми же параметрами (имеются в виду номер задачи-передатчика, идентификатор сообщения и коммуникатор) поместит в буфер пользователя именно то сообщение, которое было принято функцией MPI_Probe. MPI_Probe нужна в двух случаях:
MPI_Probe( MPI_ANY_SOURCE, tagMessageInt, MPI_COMM_WORLD, &status ); /* MPI_Probe вернет управление после того как примет */ /* данные в системный буфер */ MPI_Get_count( &status, MPI_INT, &bufElems ); buf = malloc( sizeof(int) * bufElems ); MPI_Recv( buf, bufElems, MPI_INT, ... /* ... дальше параметры у MPI_Recv такие же, как в MPI_Probe ); */ /* MPI_Recv останется просто скопировать */ /* данные из системного буфера в пользовательский */
Вместо этого, конечно, можно просто завести на приемной стороне буфер заведомо большой, чтобы вместить в себя самое длинное из возможных сообщений, но такой стиль не является оптимальным, если длина сообщений может изменяться в слишком широких пределах.
MPI_Recv( floatBuf, floatBufSize, MPI_FLOAT, MPI_ANY_SOURCE, tagFloatData, ... ); MPI_Recv( intBuf,intBufSize,MPI_INT,MPI_ANY_SOURCE, tagIntData,... ); MPI_Recv( charBuf,charBufSize,MPI_CHAR,MPI_ANY_SOURCE, tagCharData,... );
Теперь, если в момент выполнения сообщение с идентификатором tagCharData придет раньше двух остальных, MPI будет вынужден "законсервировать" его на время выполнения первых двух вызовов MPI_Recv. Это чревато непроизводительными расходами памяти. MPI_Probe позволит задать порядок извлечения сообщений в буфер пользователя равным порядку их поступления напринимающую сторону, делая это не в момент компиляции, а непосредственно в момент выполнения:
for( i=0; i<3; i++ ) { MPI_Probe( MPI_ANY_SOURCE,MPI_ANY_TAG,MPI_COMM_WORLD,&status ); switch( status.MPI_TAG ) { case tagFloatData: MPI_Recv( floatBuf, floatBufSize, MPI_FLOAT, ... ); break; case tagIntData: MPI_Recv( intBuf, intBufSize, MPI_INT, ... ); break; case tagCharData: MPI_Recv( charBuf, charBufSize, MPI_CHAR, ... ); break; } /* конец switch */ } /* конец for */
Многоточия здесь означают, что последние 4 параметра у MPI_Recv такие же, как и у предшествующей им MPI_Probe.
Неблокирующие функции
MPI обеспечивает также неблокирующие send и receive функции, которые позволяют совмещать вычисления с коммуникациями или несколько передач сообщений.
int MPI_ISend(void *buf, int count, MPI_Datatype datatype, int dest, int msgtag, MPI_Comm comm, MPI_Request *request)
Передача сообщения, аналогичная MPI_Send, однако возврат из подпрограммы происходит сразу после инициализации процесса передачи без ожидания обработки всего сообщения, находящегося в буфере buf. Это означает, что нельзя повторно использовать данный буфер для других целей без получения дополнительной информации о завершении данной посылки. Окончание процесса передачи (т.е. того момента, когда можно переиспользовать буфер buf без опасения испортить передаваемое сообщение) можно определить с помощью параметра request и процедур MPI_Wait и MPI_Test.
Сообщение, отправленное любой из процедур MPI_Send и MPI_ISend, может быть принято любой из процедур MPI_Recv и MPI_IRecv.
int MPI_IRecv(void *buf, int count, MPI_Datatype datatype, int source, int msgtag, MPI_comm comm, MPI_Request *request)
Прием сообщения, аналогичный MPI_Recv, однако возврат из подпрограммы происходит сразу после инициализации процесса приема без ожидания получения сообщения в буфере buf. Окончание процесса приема можно определить с помощью параметра request и процедур MPI_Wait и MPI_Test.
int MPI_Wait( MPI_Request *request, MPI_Status *status)
Ожидание завершения асинхронных процедур MPI_ISend или MPI_IRecv, ассоциированных с идентификатором request. В случае приема, атрибуты и длину полученного сообщения можно определить обычным образом с помощью параметра status.
int MPI_WaitAll( int count, MPI_Request *requests, MPI_Status *statuses)
Выполнение процесса блокируется до тех пор, пока все операции обмена, ассоциированные с указанными идентификаторами, не будут завершены. Если во время одной или нескольких операций обмена возникли ошибки, то поле ошибки в элементах массива statuses будет установлено в соответствующее значение.
int MPI_WaitAny( int count, MPI_Request *requests, int *index, MPI_Status *status)
Выполнение процесса блокируется до тех пор, пока какая-либо операция обмена, ассоциированная с указанными идентификаторами, не будет завершена. Если несколько операций могут быть завершены, то случайным образом выбирается одна из них. Параметр index содержит номер элемента в массиве requests, содержащего идентификатор завершенной операции.
int MPI_WaitSome( int incount, MPI_Request *requests, int *outcount, int *indexes, MPI_Status *statuses)
Выполнение процесса блокируется до тех пор, пока по крайней мере одна из операций обмена, ассоциированных с указанными идентификаторами, не будет завершена. Параметр outcount содержит число завершенных операций, а первые outcount элементов массива indexes содержат номера элементов массива requests с их идентификаторами. Первые outcount элементов массива statuses содержат параметры завершенных операций.
int MPI_Test( MPI_Request *request, int *flag, MPI_Status *status)
Проверка завершенности асинхронных процедур MPI_ISend или MPI_IRecv, ассоциированных с идентификатором request. В параметре flag возвращает значение 1, если соответствующая операция завершена, и значение 0 в противном случае. Если завершена процедура приема, то атрибуты и длину полученного сообщения можно определить обычным образом с помощью параметра status.
int MPI_TestAll( int count, MPI_Request *requests, int *flag, MPI_STatus *statuses)
В параметре flag возвращает значение 1, если все операции, ассоциированные с указанными идентификаторами, завершены (с указанием параметров сообщений в массиве statuses). В противном случае возвращается 0, а элементы массива statuses неопределены.
int MPI_TestAny(int count, MPI_Request *requests, int *index, int *flag, MPI_Status *status)
Если к моменту вызова подпрограммы хотя бы одна из операций обмена завершилась, то в параметре flag возвращается значение 1, index содержит номер соответствующего элемента в массиве requests, а status - параметры сообщения.
int MPI_TestSome( int incount, MPI_Request *requests, int *outcount, int *indexes, MPI_Status *statuses)
Данная подпрограмма работает так же, как и MPI_TestSome, за исключением того, что возврат происходит немедленно. Если ни одна из указанных операций не завершилась, то значение outcount будет равно нулю.
int MPI_Iprobe( int source, int msgtag, MPI_Comm comm, int *flag, MPI_Status *status)
Получение информации о поступлении и структуре ожидаемого сообщения без блокировки. В параметре flag возвращает значение 1, если сообщение с подходящими атрибутами уже может быть принято (в этом случае ее действие полностью аналогично MPI_Probe), и значение 0, если сообщения с указанными атрибутами еще нет.
Объединение запросов на взаимодействие
Процедуры данной группы позволяют снизить накладные расходы, возникающие в рамках одного процессора при обработке приема/передачи и перемещении необходимой информации между процессом и сетевым контроллером. Несколько запросов на прием и/или передачу могут объединяться вместе для того, чтобы далее их можно было бы запустить одной командой. Способ приема сообщения никак не зависит от способа его посылки: сообщение, отправленное с помощью объединения запросов либо обычным способом, может быть принято как обычным способом, так и с помощью объединения запросов.
int MPI_Send_Init( void *buf, int count, MPI_Datatype datatype, int dest, int msgtag, MPI_Comm comm, MPI_Request *request)
Формирование запроса на выполнение пересылки данных. Все параметры точно такие же, как и у подпрограммы MPI_ISend, однако в отличие от нее пересылка не начинается до вызова подпрограммы MPI_StartAll.
int MPI_Recv_Init( void *buf, int count, MPI_Datatype datatype, int source, int msgtag, MPI_Comm comm, MPI_Request *request)
Формирование запроса на выполнение приема данных. Все параметры точно такие же, как и у подпрограммы MPI_IReceive, однако в отличие от нее реальный прием не начинается до вызова подпрограммы MPI_StartAll.
MPI_Start_All( int count, MPI_Request *requests)
Запуск всех отложенных взаимодействий, ассоциированных вызовами подпрограмм MPI_Send_Init и MPI_Recv_Init с элементами массива запросов requests. Все взаимодействия запускаются в режиме без блокировки, а их завершение можно определить обычным образом с помощью процедур MPI_Wait и MPI_Test.
Совмещенные прием/передача сообщений
Некоторые конструкции с приемо-передачей применяются очень часто:
Обмен данными с соседями по группе (в группе четное количество ветвей!):
MPI_Comm_size( MPI_COMM_WORLD, &size ); MPI_Comm_rank( MPI_COMM_WORLD, &rank ); if( rank % 2 ) { /* Ветви с четными номерами сначала * передают следующим нечетным ветвям, * потом принимают от предыдущих */ MPI_Send(..., ( rank+1 ) % size ,...); MPI_Recv(..., ( rank+size-1 ) % size ,...); } else { /* Нечетные ветви поступают наоборот: * сначала принимают от предыдущих ветвей, * потом передают следующим. */ MPI_Recv(..., ( rank-1 ) % size ,...); MPI_Send(..., ( rank+1 ) % size ,...); }
Посылка данных и получение подтверждения:
MPI_Send(..., anyRank ,...);/* Посылаем данные */ MPI_Recv(..., anyRank ,...);/* Принимаем подтверждение */
Ситуация настолько распространенная, что в MPI специально введены две функции, осуществляющие одновременно посылку одних данных и прием других. Первая из них - MPI_Sendrecv. Ее прототип содержит 12 параметров: первые 5 параметров такие же, как у MPI_Send, остальные 7 параметров такие же как у MPI_Recv. Один ее вызов проделывает те же действия, для которых в первом фрагменте требуется блок IF-ELSE с четырьмя вызовами. Следует учесть, что:
int MPI_Sendrecv( void *sbuf, int scount, MPI_Datatype stype, int dest, int stag, void *rbuf, int rcount, MPI_Datatype rtype, int source, MPI_DAtatype rtag, MPI_Comm comm, MPI_Status *status)
MPI_Sendrecv_replace помимо общего коммуникатора использует еще и общий для приема-передачи буфер. Не очень удобно, что параметр count получает двойное толкование: это и количество отправляемых данных, и предельная емкость входного буфера. Показания к применению:
MPI_Sendrecv_replace так же гарантированно не вызывает клинча.
Что такое клинч? Дальше следует краткая иллюстрация этой ошибки, очень распространенной там, где для пересылок используется разделяемая память.
Вариант 1:
-- Ветвь 1 -- -- Ветвь 2 --
Recv( из ветви 2 ) Recv( из ветви 1 )
Send( в ветвь 2 ) Send( в ветвь 1 )
Вариант 1 вызовет клинч, какой бы инструментарий не использовался: функция приема не вернет управления до тех пор, пока не получит данные; поэтому функция передачи не может приступить к отправке данных; поэтому функция приема, и так далее …
Вариант 2:
-- Ветвь 1 -- -- Ветвь 2 --
Send( в ветвь 2 ) Send( в ветвь 1 )
Recv( из ветви 2 ) Recv( из ветви 1 )
Вариант 2 вызовет клинч, если функция передачи возвращает управление только после того, как данные попали в пользовательский буфер на приемной стороне. Скорее всего, именно так и возьмется реализовывать передачу через разделяемую память/семафоры программист-проблемщик.
Однако при использовании MPI зависания во втором варианте не произойдет! MPI_Send, если на приемной стороне нет готовности (не вызван MPI_Recv), не станет ее дожидаться, а положит данные во временный буфер и вернет управление программе НЕМЕДЛЕННО. Когда MPI_Recv будет вызван, данные он получит не из пользовательского буфера напрямую, а из промежуточного системного. Буферизация - дело громоздкое - может быть, и не всегда сильно экономит время (особенно на SMP-машинах), зато повышает надежность: делает программу более устойчивой к ошибкам программиста.
MPI_Sendrecv и MPI_Sendrecv_replace также делают программу более устойчивой: с их использованием программист лишается возможности перепутать варианты 1 и 2.
Типы данных
Зачем MPI знать тип передаваемых данных?
Стандартные функции пересылки данных, например, memcpy, обходятся без подобной информации - им требуется знать только размер в байтах. Вместо одного такого аргумента функции MPI получают два: количество элементов некоторого типа и символический описатель указанного типа (MPI_INT, и т.д.). Причин тому несколько:
typedef struct { charc; doubled; } CharDouble;
Передача разнотипных данных
Все вышеперечисленные функции приема-передачи оперируют массивами – непрерывными последовательностями однотипных данных. Однако программисту может потребоваться пересылать данные, которые либо разнотипны, либо не-непрерывно расположены в памяти, либо то и другое сразу (особо тяжелый случай).
Способ 1. Каждый элемент в разнотипном наборе данных посылается отдельно:
#define msgTag 10 struct { inti; float f[4]; charc[8]; } s; MPI_Send(&s.i, 1, MPI_INT,targetRank, msgTag,MPI_COMM_WORLD ); MPI_Send( s.f, 4, MPI_FLOAT, targetRank, msgTag+1, MPI_COMM_WORLD ); MPI_Send( s.c, 8, MPI_CHAR,targetRank, msgTag+2, MPI_COMM_WORLD );
... и на приемной стороне столько же раз вызывается MPI_Recv.
Этот способ является крайне не эффективным, так как каждая операция пересылки требует инициализации.
Способ 2 ("классический"). Функция приема/передачи вызывается один раз, но до/после нее многократно вызывается функция упаковки/распаковки:
Передача:
int bufPos = 0; char tempBuf[ sizeof(s) ]; MPI_Pack(&s.i,1,MPI_INT,tempBuf,sizeof(tempBuf),&bufPos,MPI_COMM_WORLD); MPI_Pack(s.f,4,MPI_FLOAT,tempBuf,sizeof(tempBuf),&bufPos,MPI_COMM_WORLD); MPI_Pack(s.c,8,MPI_CHAR,tempBuf,sizeof(tempBuf),&bufPos,MPI_COMM_WORLD ); MPI_Send(tempBuf, bufPos,MPI_BYTE,targetRank,msgTag,MPI_COMM_WORLD );
Прием:
int bufPos = 0; char tempBuf[ sizeof(s) ]; MPI_Recv( tempBuf, sizeof(tempBuf), MPI_BYTE, sourceRank, msgTag, MPI_COMM_WORLD, &status ); MPI_Unpack(tempBuf,sizeof(tempBuf),&bufPos,&s.i,1,MPI_INT,MPI_COMM_WORLD); MPI_Unpack(tempBuf,sizeof(tempBuf),&bufPos,s.f,4,MPI_FLOAT,MPI_COMM_WORLD); MPI_Unpack(tempBuf,sizeof(tempBuf),&bufPos,s.c,8,MPI_CHAR,MPI_COMM_WORLD);
Этот способ пришел в MPI из PVM, где предлагается в качестве единственного. Он прост в понимании. Замечания по применению:
1.MPI_BYTE - это особый описатель типа; который не описывает тип данных для конкретного языка программирования (в Си он ближе всего к unsigned char). Использование MPI_BYTE означает, что содержимое соответствующего массива НЕ ДОЛЖНО подвергаться НИКАКИМ преобразованиям - и на приемной, и на передающей стороне массив будет иметь одну и ту же длину и одинаковое ДВОИЧНОЕ представление.
2.Зачем функциям упаковки/распаковки требуется описатель области связи? Описатель, помимо прочего, несет в себе информацию о распределении подсоединенных к области связи задач по процессорам и компьютерам. Если процессоры одинаковые, или задачи выполняются на одном и том же процессоре, данные просто копируются, иначе происходит их преобразование в/из формата XDR (eXternal Data Representation - разработан фирмой Sun Microsystems, используется в Интернете для взаимодействия разнотипных машин). Учтите, что коммуникаторы у функций упаковки/распаковки и у соответствующей функции передачи/приема должны совпадать, иначе произойдет ошибка;
3.По мере того как во временный буфер помещаются данные или извлекаются оттуда, MPI сохраняет текущую позицию в переменной, которая в приведенном примере названа bufPos. Не забудьте проинициализировать ее нулем перед тем как начинать упаковывать/извлекать. Естественно, что передается она не по значению, а по ссылке. Первый же аргумент - "адрес временного буфера" - во всех вызовах остается неизменным;
4.В примере НЕКОРРЕКТНО выбран размер временного буфера: использовалось НЕВЕРНОЕ предположение, что в XDR-формате данные займут места не больше, чем в формате используемого ветвью процессора; или что XDR-преобразование заведомо не будет применено. Правильным же решением будет для определения необходимого размера временного буфера на приемной стороне использовать связку MPI_Probe / MPI_Get_count / MPI_Recv, а на передающей - функцию MPI_Pack_size:
int bufSize = 0; void *tempBuf; MPI_Pack_size( 1, MPI_INT,MPI_COMM_WORLD, &bufSize ); MPI_Pack_size( 4, MPI_FLOAT, MPI_COMM_WORLD, &bufSize ); MPI_Pack_size( 8, MPI_CHAR,MPI_COMM_WORLD, &bufSize ); tempBuf = malloc( bufSize ); /* ... теперь можем упаковывать, не опасаясь переполнения */
Однако и второй способ замедляет работу: по сравнению с единственным вызовом memcpy на SMP-машине или одном процессоре несколько раз производится упаковка/распаковка - дело весьма небыстрое!
Способ 3 ("жульнический"). Если есть уверенность, что одни и те же типы данных в обеих ветвях приложения имеют одинаковое двоичное представление, то:
Передача:MPI_Send( &s, sizeof(s), MPI_BYTE, ... ); Прием:MPI_Recv( &s, sizeof(s), MPI_BYTE, ... );
Способ 4. Создание и использование собственных типов данных.
Общие правила:
Конструкторы типа.
MPI_Type_contiguous : самый простой конструктор типа, он создает описание массива. В следующем примере оба вызова MPI_Send делают одно и то же.
int a[16]; MPI_Datatype intArray16; MPI_Type_contiguous( 16, MPI_INT, &intArray16 ); MPI_Type_commit( &intArray16 ); MPI_Send( a, 16, MPI_INT, ... ); MPI_Send( a, 1, intArray16, ... ); MPI_Type_free( &intArray16 );
Функция MPI_Type_count возвращает количество ячеек в переменной составного типа: после MPI_Type_count( intArray16, &count ) значение count станет равным 16. Как правило, прямой необходимости использовать эти функции нет, и тем не менее.
MPI_Type_vector(int count,int blocklength,int stride,MPI_Datatype oldtype,MPI_Datatype &newtype)
То есть:
Функция MPI_Type_hvector полностью ей аналогична, за одним исключением: расстояние между элементами задается не в количестве ячеек базового типа, а в байтах.
MPI_Type_indexed : расширение "векторного" описателя; длины массивов и расстояния между ними теперь не фиксированы, а у каждого массива свои. Соответственно, второй и третий аргументы здесь - не переменные, а массивы: массив длин и массив позиций.
Пример: создание шаблона для выделения верхней правой части матрицы.
#defineSIZE100 float a[ SIZE ][ SIZE ]; int pos[ SIZE ] int len[ SIZE ]; MPI_Datatype upper; ... for( i=0; i<SIZE; i++ ) {/*xxxxxx*/ pos[i] = SIZE*i + i;/*.xxxxx*/ len[i] = SIZE - i; /*..xxxx*/ }/*...xxx*/ MPI_Type_indexed( SIZE,/* количество массивов в переменной нового типа */ len,/* длины этих массивов */ pos,/* их позиции от начала переменной, */ /* отсчитываемые в количестве ячеек */ MPI_FLOAT,/* тип ячейки массива */ &upper ); MPI_Type_commit( &upper ); /* Поступающий поток чисел типа 'float' будет * размещен в верхней правой части матрицы 'a' */ MPI_Recv( a, 1, upper, .... );
Аналогично работает функция MPI_Type_hindexed, но позиции массивов от начала переменной задаются не в количестве ячеек базового типа, а в байтах.
MPI_Type_struct(int count,int *len,MPI_Aint *pos,MPI_Datatype *types,MPI_Datatype *newtype )
Здесь используется тип MPI_Aint: это просто скалярный тип, переменная которого имеет одинаковый с указателем размер. Введен он исключительно для единообразия с Фортраном, в котором нет типа "указатель". По этой же причине имеется и функция MPI_Address: в Си она не нужна (используются оператор вычисления адреса & и основанный на нем макрос offsetof() ); а в Фортране оператора вычисления адреса нет, и используется MPI_Address.
Пример создания описателя типа "структура":
#include <stddef.h>/* подключаем макрос 'offsetof()' */ typedef struct { inti; double d[3]; longl[8]; charc; } AnyStruct; AnyStruct st; MPI_Datatype anyStructType; intlen[5] = { 1, 3, 8, 1, 1 }; MPI_Aintpos[5] = { offsetof(AnyStruct,i), offsetof(AnyStruct,d), offsetof(AnyStruct,l), offsetof(AnyStruct,c), sizeof(AnyStruct) }; MPI_Datatype typ[5] = { MPI_INT,MPI_DOUBLE,MPI_LONG,MPI_CHAR,MPI_UB }; MPI_Type_struct( 5, len, pos, typ, &anyStructType ); MPI_Type_commit( &anyStructType ); /* подготовка закончена */ MPI_Send( st, 1, anyStructType, ... );
Обратите внимание: структура в примере содержит 4 поля, а массивы для ее описания состоят из 5 элементов. Сделано это потому, что MPI должен знать не только смещения полей, но и размер всей структуры. Для этого и служит псевдотип MPI_UB ("upper bound"). Адрес начала структуры и адрес ее первого поля, как правило, совпадают, но если это не так, то нулевым элементом массива typ должен быть MPI_LB.
MPI_Type_extent и MPI_Type_size : важные информационные функции.
Групповые (коллективные) взаимодействия
Под термином "коллективные" в MPI подразумеваются три группы функций:
Коллективная функция одним из аргументов получает описатель области связи (коммуникатор). Вызов коллективной функции является корректным, только если произведен из всех процессов-абонентов соответствующей области связи, и именно с этим коммуникатором в качестве аргумента (хотя для одной области связи может иметься несколько коммуникаторов, подставлять их вместо друг друга нельзя). В этом и заключается коллективность: либо функция вызывается всем коллективом процессов, либо никем; третьего не дано.
Как поступить, если требуется ограничить область действия для коллективной функции только частью присоединенных к коммуникатору задач, или наоборот - расширить область действия? Создавайте временную группу/область связи/коммуникатор на базе существующих, как это показано в разделе про коммуникаторы.
Итак, коллективная функция должна быть вызвана каждым процессом, быть может, со своим набором параметров. Возврат из функции коллективного взаимодействия может произойти в тот момент, когда участие процесса в данной операции уже закончено. Как и для блокирующих процедур, возврат означает то, что разрешен свободный доступ к буферу приема или посылки, но не означает ни того, что операция завершена другими процессами, ни даже того, что она ими начата (если это возможно по смыслу операции).
Функции коллективного обмена данными
Основные особенности и отличия от коммуникаций типа "точка-точка":
int MPI_Bcast( void *buf, int count, MPI_Datatype datatype, int source, MPI_Comm comm)
Рассылка сообщения от процесса source всем процессам, включая рассылающий процесс. При возврате из процедуры содержимое буфера buf процесса source будет скопировано в локальный буфер процесса. Значения параметров count, datatype и source должны быть одинаковыми у всех процессов.
int MPI_Gather( void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int rcount, MPI_Datatype rtype, int dest, MPI_Comm comm)
Функции поддержки распределенных операций
Сборка данных со всех процессов в буфере rbuf процесса dest. Каждый процесс, включая dest, посылает содержимое своего буфера sbuf процессу dest. Собирающий процесс сохраняет данные в буфере rbuf, располагая их в порядке возрастания номеров процессов. Параметр rbuf имеет значение только на собирающем процессе и на остальных игнорируется, значения параметров count, datatype и dest должны быть одинаковыми у всех процессов.
int MPI_AllReduce( void *sbuf, void *rbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm)
Выполнение count глобальных операций op с возвратом count результатов во всех процессах в буфере rbuf. Операция выполняется независимо над соответствующими аргументами всех процессов. Значения параметров count и datatype у всех процессов должны быть одинаковыми. Из соображений эффективности реализации предполагается, что операция op обладает свойствами ассоциативности и коммутативности.
Идентификаторы глобальных операций:
int MPI_Reduce( void *sbuf, void *rbuf, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm)
Функция аналогична предыдущей, но результат будет записан в буфер rbuf только у процесса root.
Пример использования функции MPI_Reduce:
int vector[16]; int resultVector[16]; MPI_Comm_rank( MPI_COMM_WORLD, &myRank ); for( i=0; i<16; i++ ) vector[i] = myRank*100 + i; MPI_Reduce( vector, /* каждая задача в коммуникаторе предоставляет вектор */ resultVector, /* задача номер 'root' собирает данные сюда */ 16,/* количество ячеек в исходном и результирующем массивах */ MPI_INT, /* и тип ячеек */ MPI_SUM, /* описатель операции: поэлементное сложение векторов */ 0,/* номер задачи, собирающей результаты в 'resultVector' */ MPI_COMM_WORLD /* описатель области связи */ ); if( myRank==0 ) /* печатаем resultVector, равный сумме векторов */
Синхронизация процессов
int MPI_Barrier( MPI_Comm comm)
Блокирует работу процессов, вызвавших данную процедуру, до тех пор, пока все оставшиеся процессы группы comm также не выполнят эту процедуру.
Коммуникаторы, группы и области связи.
Группа - это некое множество ветвей. Одна ветвь может быть членом нескольких групп. В распоряжение программиста предоставлен тип MPI_Group и набор функций, работающих с переменными и константами этого типа. Констант, собственно, две: MPI_GROUP_EMPTY может быть возвращена, если группа с запрашиваемыми характеристиками в принципе может быть создана, но пока не содержит ни одной ветви; MPI_GROUP_NULL возвращается, когда запрашиваемые характеристики противоречивы. Согласно концепции MPI, после создания группу нельзя дополнить или усечь - можно создать только новую группу под требуемый набор ветвей на базе существующей.
Область связи ("communication domain") - это нечто абстрактное: в распоряжении программиста нет типа данных, описывающего непосредственно области связи, как нет и функций по управлению ими. Области связи автоматически создаются и уничтожаются вместе с коммуникаторами. Абонентами одной области связи являются ВСЕ задачи либо одной, либо двух групп.
Коммуникатор, или описатель области связи - это верхушка трехслойного пирога (группы, области связи, описатели областей связи), в который "запечены" задачи: именно с коммуникаторами программист имеет дело, вызывая функции пересылки данных, а также подавляющую часть вспомогательных функций. Одной области связи могут соответствовать несколько коммуникаторов. Коммуникаторы являются "несообщающимися сосудами": если данные отправлены через один коммуникатор, ветвь-получатель сможет принять их только через этот же самый коммуникатор, но ни через какой-либо другой.
Зачем вообще нужны разные группы, разные области связи и разные их описатели?
Коммуникаторы распределяются автоматически (функциями семейства "Создать новый комуникатор"), и для них не существует джокеров ("принимай через какой угодно коммуникатор") - вот еще два их существенных достоинства перед идентификаторами сообщений. Идентификаторы (целые числа) распределяются пользователем вручную, и это служит источником двух частых ошибок вследствие путаницы на приемной стороне:
Важно помнить, что ВСЕ функции, создающие коммуникатор, являются КОЛЛЕКТИВНЫМИ! Именно это качество позволяет таким функциям возвращать в разные ветви ОДИН И ТОТ ЖЕ описатель. Коллективность, напомню, заключется в следующем:
Создание коммуникаторов и групп
Копирование. Самый простой способ создания коммуникатора - скопировать "один-в-один" уже имеющийся:
MPI_Comm tempComm; MPI_Comm_dup( MPI_COMM_WORLD, &tempComm ); /* ... передаем данные через tempComm ... */ MPI_Comm_free( &tempComm );
Новая группа при этом не создается - набор задач остается прежним. Новый коммуникатор наследует все свойства копируемого.
Расщепление. Соответствующая коммуникатору группа расщепляется на непересекающиеся подгруппы, для каждой из которых заводится свой коммуникатор.
MPI_Comm_split( existingComm, /* существующий описатель, например MPI_COMM_WORLD */ indexOfNewSubComm, /* номер подгруппы, куда надо поместить ветвь */ rankInNewSubComm,/* желательный номер в новой подгруппе */ &newSubComm );/* описатель области связи новой подгруппы */
Эта функция имеет одинаковый первый параметр во всех ветвях, но разные второй и третий - и в зависимости от них разные ветви определяются в разные подгруппы; возвращаемый в четвертом параметре описатель будет принимать в разных ветвях разные значения (всего столько разных значений, сколько создано подгрупп). Если indexOfNewSubComm равен MPI_UNDEFINED, то в newSubComm вернется MPI_COMM_NULL, то есть ветвь не будет включена ни в какую из созданных групп.
Создание через группы. В предыдущих двух случаях коммуникатор создается от существующего коммуникатора напрямую, без явного создания группы: группа либо та же самая, либо создается автоматически. Самый же общий способ таков:
Такой механизм позволяет, в частности, не только расщеплять группы подобно MPI_Comm_split, но и объединять их. Всего в MPI определено 7 разных функций конструирования групп.
Может ли задача обратиться к области связи, абонентом которой не является? Нет. Описатель области связи передается в задачу функциями MPI, которые одновременно делают эту задачу абонентом описываемой области. Таков единственный существующий способ получить описатель. Попытки "пиратскими" средствами обойти это препятствие (например, получить описатель, посредством MPI_Send/MPI_Recv переслать его в другую задачу, не являющуюся его абонентом, и там им воспользоваться) не приветствуются, и исход их, скорее всего, будет определяться деталями реализации.
Полезная нагрузка коммуникатора: атрибуты.
Помимо характеристик области связи, тело коммуникатора содержит в себе некие дополнительные данные (атрибуты). Механизм хранения атрибутов называется "caching". Атрибуты могут быть системные и пользовательские; в системных, в частности, хранятся:
Атрибуты идентифицируются целыми числами, которые MPI назначает автоматически. Некоторые константы для описания системных атрибутов: MPI_TAG_UB, MPI_HOST, MPI_IO, MPI_WTIME_IS_GLOBAL. К этим атрибутам программист обращается редко, и менять их не может; а для таких часто используемых атрибутов, как обработчик ошибок или описание топологии, существуют персональные наборы функций, например, MPI_Errhandler_xxx.
Атрибуты - удобное место хранения совместно используемой информации; помещенная в атрибут одной из ветвей, такая информация становится доступной всем использующим коммуникатор ветвям БЕЗ пересылки сообщений (вернее, на MPP-машине, к примеру, сообщения будут, но на системном уровне, т.е. скрытые от глаз программиста).
Пользовательские атрибуты создаются и уничтожаются функциями MPI_Keyval_create и MPI_Keyval_free; модифицируются функциями MPI_Attr_put, MPI_Attr_get и MPI_Attr_delete.
При создании коммуникатора на базе существующего атрибуты из последнего тем или иным образом копируются или нет в зависимости от функции копирования типа MPI_Copy_function, адрес которой является параметром функции создания атрибута.
То же и для удаления атрибутов при уничтожении коммуникатора: задается пользовательской функцией типа MPI_Delete_function, указываемой при создании атрибута.
Корректное удаление отслуживших описателей.
Здесь имеются в виду ВСЕ типы системных данных, для которых предусмотрена функция MPI_Xxx_free (и константа MPI_XXX_NULL):
1.коммуникаторы;
2.группы;
3.типы данных;
4.распределенные операции;
5.квитанции (request's);
6.атрибуты коммуникаторов;
7.обработчики ошибок (errhandler's).
Далее приводится описание на примере коммуникаторов и групп, но изложенная схема является общей для всех типов ресурсов.
Не играет роли, в каком порядке уничтожать взаимосвязанные описатели. Главное - не забыть вызвать функцию удаления ресурса MPI_Xxx_free вовсе. Соответствующий ресурс не будет удален немедленно, он прекратит существование только если будут выполнены два условия:
Взаимосвязанными описателями являются описатели коммуникатора и группы (коммуникатор ссылается на группу); или описатели типов, если один создан на базе другого (порожденный ссылается на исходный).
Пример:
MPI_Comm subComm; MPI_Group subGroup; int rank; MPI_Comm_rank( MPI_COMM_WORLD, &rank ); MPI_Comm_split( MPI_COMM_WORLD, rank / 3, rank % 3, &subComm ); /* Теперь создан коммуникатор subComm, и автоматически создана * группа, на которую распространяется его область действия. * На коммуникатор заведена ссылка из программы - subComm. * На группу заведена системная ссылка из коммуникатора. */ MPI_Comm_group( subComm, &subGroup ); /* Теперь на группу имеется две ссылки - системная * из коммуникатора, и пользовательская subGroup. */ MPI_Group_free( &subGroup ); /* Пользовательская ссылка на группу уничтожена, * subGroup сброшен в MPI_GROUP_NULL. * Собственно описание группы из системных данных не удалено, * так как на него еще ссылается коммуникатор. */ MPI_Comm_free( &subComm ); /* Удалена пользовательская ссылка на коммуникатор, * subComm сброшен в MPI_COMM_NULL. Так как других ссылок * на коммуникатор нет, его описание удаляется из системных данных. * Вместе с коммуникатором удалена системная ссылка на группу. * Так как других ссылок на группу нет, ее описание удаляется * из системных данных. */
Еще раз: для MPI не играет роли, в каком порядке будут вызваны завершающие вызовы MPI_Xxx_free, это дело программы.
И не пытайтесь уничтожать константные описатели вроде MPI_COMM_WORLD или MPI_CHAR: их создание и уничтожение - дело самого MPI.
Предопределенные константы
Предопределенные константы типа элементов сообщений
Константы MPI Тип в C MPI_CHAR signed char MPI_SHORT signed int MPI_INT signed int MPI_LONG signed long int MPI_UNSIGNED_CHAR unsigned char MPI_UNSIGNED_SHORT unsigned int MPI_UNSIGNED unsigned int MPI_UNSIGNED_LONG unsigned long int MPI_FLOAT float MPI_DOUBLE double MPI_LONG_DOUBLE long double
Другие предопределенные типы
MPI_Status - структура; атрибуты сообщений; содержит три обязательных поля: MPI_Source (номер процесса отправителя) MPI_Tag (идентификатор сообщения) MPI_Error (код ошибки) MPI_Request - системный тип; идентификатор операции посылки-приема сообщения MPI_Comm - системный тип; идентификатор группы (коммуникатора) MPI_COMM_WORLD - зарезервированный идентификатор группы, состоящей их всех процессов приложения
Константы-пустышки
MPI_COMM_NULL MPI_DATATYPE_NULL MPI_REQUEST_NULL
Константа неопределенного значения
MPI_UNDEFINED
Глобальные операции
MPI_MAX MPI_MIN MPI_SUM MPI_PROD
Любой процесс/идентификатор
MPI_ANY_SOURCE MPI_ANY_TAG
Код успешного завершения процедуры
MPI_SUCCESS