Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Параллельное программирование.docx
Скачиваний:
2
Добавлен:
01.04.2025
Размер:
396.91 Кб
Скачать

Параллельное программирование (Фролова)

  1. Интерфейс передачи сообщений, библиотека функций MPI. Основные понятия и определения.

Изучение принципа разработки параллельных программ начнем с распределенного адресного пространства и возможностей библиотеки MPI. MPI – Message Passing Interface, является библиотекой функций, в основу которой положен принцип обмена данными между логическими процессами параллельной задачи. Библиотека функций MPI реализована в С/C++ и Fortran интерфейсах. Первая версия библиотеки MPI разработана Аргонской национальной лабораторией США в 1994. В настоящее время стандарт MPI адаптирован для большинства высокопроизводительных вычислительных систем с архитектурой общей и распределенной памяти. На сегодняшний день MPI реализован в двух стандартах: MPI-1 и MPI-2. MPI-2 является расширенной версией MPI-1, предусматривается возможность динамического изменения количества процессов и разделяемого ввода/вывода.

Под параллельной программой в рамках MPI понимается множество одновременно выполняемых процессов, имеющих раздельные адресные пространства. Каждый процесс параллельной программы порождается на основе копии одного и того же программного кода (модель SPMP - одна программа множество процессов single program multiple processes). Программный код, представленный в виде исполняемой программы, должен быть доступен в момент запуска на всех используемых процессорах.

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

Процессы параллельной программы объединяются в группы. Под коммуникатором в MPI понимается специально создаваемый служебный объект, объединяющий в своем составе группу процессов, используемых при выполнении программы. Коммуникатор, ограничивая "пространственную" область коммуникаций, определяет взаимно-однозначное соответствие между логическими номерами процессов (ранк процесса) и сетевыми адресами физических процессоров на которых выполняется параллельная программа, связывая “верхний” уровень логических процессов - с “низким” уровнем коммуникаций. Ни одна операция передачи данных MPI не может быть выполнена без использования коммуникатора.

Коммуникаторы разделяются на два вида: интра-коммуникаторы (внутригрупповые коммуникаторы), предназначены для операций в пределах отдельной группы процессов, и интер-коммуникаторы (межгрупповые коммуникаторы), предназначены для выполнения send/recv оперций между процессами, входящими в две группы с разными коммуникаторами.

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

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

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

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

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

MPI – Message Passing Interface, является библиотекой функций, которые обеспечивают в первую очередь обмен данными между процессами. Для использования функций библиотеки в некоторой исполнительной среде, необходима реализация MPI. В настоящее время разработкой реализаций MPI занимается множество компаний, наиболее известными являются Intel (реализация Intel-MPI), ANL(MPICH-реализация Аргонской Национальной Лаборатории), openmpi разрабатывается под GNU лицензией и т.д.

Возможны два способа построения реализаций:

1) прямая реализация для конкретной ЭВМ и

2) реализация через ADI (интерфейс абстрактного устройства).

Т.к. архитектурных типов параллельных систем довольно много, то количество реализаций в первом случае будет слишком велико. Во втором случае строится реализация для абстрактного устройства, а затем архитектура ADI, используя оборудование конкретной вычислительной системы, реализуется в этой системе программно. Такая двухступенчатая реализация MPI уменьшает число вариантов и обеспечивает переносимость реализаций.

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

В состав MPICH входят скриптовые (командные) файлы для компиляции и запуска параллельных программ, и несколько сервисных файлов (bin каталог). Lib каталог содержит требуемые для сборки приложения библиотеки кодов MPI-функций. Основными являются libmpich.a (libmpich.lib) и libfmpich.a (libfmpich.lib). Include каталог содержит требуемые для компиляции программы подключаемые файлы с прототипами MPI-функций (.h). Основными являются mpi.h (mpif.h для Фортран реализации).

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

Запуск приложений выполняет командный файл mpirun:

mpirun –np <кол-во_процессов> <имя_исполняемого_файла>

  1. Реализация интерфейса передачи данных (MPICH). Общая характеристика среды выполнения MPI программ.

В Аргонской национальной лаборатории США подготовлены и получили широкое распространение реализации MPI, получившие название MPICH (CH взята из названия пакета Сhameleon, который ранее использовался для систем с передачей сообщений, многое из этого пакета вошло в MPIСH).

Имеется три поколения MPIСH, связанных с развитием ADI.

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

ADI-2 – добавлен эффективный коммуникационный механизм с большим набором функций.

ADI-3 – обеспечило адаптацию к различным коммуникационным архитектурам, удаленный доступ и поддержку операций MPI-2, таких как динамическое управление процессами.

Для обмена сообщениями ADI должен обеспечивать четыре набора функций:

1) описания передаваемых и получаемых сообщений;

2) перемещения данных между ADI и передающей аппаратурой;

3) управления списком сообщений (как посланных, так и принимаемых);

4) получения основной информации об исполнительной среде и ее состоянии.

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

Каждая реализация библиотеки функций MPI содержит ряд собственных протоколов, устанавливающих правила выполнения основных операций передачи данных. В реализации MPIСH используются 2 протокола: Eager и Rendezvous.

Eager. При отправке данных, с адресом начала буфера MPI должен включить информацию о тэге, коммуникаторе, длине, источнике и получателе сообщения. Эту дополнительную информацию называют оболочкой (envelope), которая следует за данными, образующими информационную часть сообщения. Передачу данных вместе с оболочкой реализует eager протокол.

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

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

Rendezvous. Передачу “больших” сообщений, объем которых превышает установленный в MPI размер буфера приема-передачи, реализует протокол Rendezvous. В этом случае принимающий процесс получает только оболочку, содержащую служебную информацию о передаваемом сообщении. Только после того, как процесс отправитель получит подтверждение на возможность приема, данные будут отправлены. Главное преимущество протокола «рандеву» состоит в том, что он позволяет принимать произвольно большие сообщения в любом количестве. Для передачи “коротких” сообщений используется протокол Eager.

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

Канальный интерфейс является одним из наиболее важных уровней иерархии ADI и может иметь множественные реализации. Например, р4 – для систем с передачей сообщений, и p2 – для систем с общей памятью. Реализация Chameleon, построена на базе интерфейса p4, который частично используется в реализации MPICH.

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

Передача и прием сообщений процессами являются основным механизмом MPI. Основные операции двухточечных обменов это - send и receive. В MPI реализовано два типа обменов, блокирующие обмены и не блокирующие обмены, в каждом из двух типов может использоваться 4 режима связи: стандартный, буферизующий(B), синхронный (S), по готовности (R).

Блокирующие обмены.

MPI_Send()

MPI_Bsend() MPI_Recv()

MPI_Ssend()

MPI_Rsend()

Для всех режимов передачи используется одна функция приема MPI_Recv.

Функция передачи – стандартный режим:

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

Функция приема:

int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)

OUT buf адрес буфера передачи/приема

IN count количество элементов в буфере передачи/приема

IN datatype тип передаваемых/принимаемых данных

IN dest/source ранг (номер процесса) источника (куда/от кого)

IN tag тэг сообщения (integer)

IN comm коммуникатор

OUT status объект статуса (Status)

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

Tag – это целое число, которое является отличительным признаком сообщения. Если на одном процессе несколько операций передачи сообщения (как у нас, на 0-вом процессе), то они не могут иметь одинаковый tag.

///////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Send процесс 0 -> Recv остальные процессы

// mpirun –np <кол-во_процессов> <имя_исполняемого_файла>

/////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include <mpi.h>

#include <iostream>

using namespace std;

void main( int argc, char *argv[] )

{

int proc, size;

MPI_Status stat[2];

MPI_Init( &argc, &argv);

MPI_Comm_rank( MPI_COMM_WORLD, &proc);

MPI_Comm_size( MPI_COMM_WORLD, &size);

double b[10];

if (proc == 0) /* код для процесса с номером 0*/

{

double a[10];

for(int i=0; i<10; i++)

a[i]=(double)proc;

for(i=1; i<size; i++){ /*передача массива */

MPI_Send(&a[0], 10, MPI_DOUBLE, i, 100, MPI_COMM_WORLD);

}

}

else /* код для остальных процессов */

{ /* прием массива на остальных процессах */

MPI_Recv(&b[0], 10, MPI_DOUBLE, 0, 100, MPI_COMM_WORLD, &stat[0]);

for(i=0; i<10; i++)

cout<<b[i]<<”\t|;

}

MPI_Finalize();

}

Возвращаемая статусная информация

MPI_Status - это структура, которая содержит три поля:

• MPI_SOURCE,

• MPI_TAG,

• MPI_ERROR.

Следовательно, status.MPI_SOURCE, status.MPI_TAG и status.MPI_ERROR содержат: ранк источника, тэг и код ошибки принятого сообщения.

Аргумент status также возвращает информацию о длине принятого сообщения. Так как, эта информация не является полем структуры status, то чтобы получить значение длины сообщения нужно вызвать функцию MPI_GET_COUNT.

int MPI_Get_count(MPI_Status *status, MPI_Datatype datatype, int *count)

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

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

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

M PI_Isend()

M PI_Ibsend() MPI_Irecv()

M PI_Issend()

MPI_Irsen

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

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

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

Дескриптор запроса (request)

Все не блокирующие вызовы и блокирующая операция приема создают объект коммуникационного запроса и связывают его с дескриптором запроса (request). Дескриптор запроса является коммуникационным объектом. Когда операция передачи или приема завершена, дескриптор запроса устанавливается в MPI_REQUEST_NULL, при этом статусный объект (status), связанный с этим запросом, инициализируется (происходит занесение значений в поля структуры status).

В не блокирующих обменах дескриптор запроса (request) используется при ожидании завершения обмена и определения статуса (status) обмена.

Между запросом и статусом существует взаимно однозначное соответствие. Состояние статуса может соответствовать пришедшему сообщению или быть empty (пусто), в этом случае tag = MPI_ANY_TAG, error = MPI_SUCCESS, source = MPI_ANY_SOURCE,

Функции завершения обменов

Чтобы завершить не блокирующий обмен, используются функции MPI_Wait, MPI_Test. Операция завершения обменов MPI_Wait является блокирующей, MPI_Test – не блокирующей.

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

MPI_Test (request, flag, status)

INOUT request коммуникационный запрос (дескриптор запроса)

OUT flag true/false, если операция завершена/не завершена (логический тип)

OUT status статусный объект (статус)

Обращение к MPI_Test возвращает flag = true, если операция, определенная дескриптором запроса, завершена. В таком случае status содержит информацию о завершенной операции, дескриптор запроса request принимает значение MPI_REQUEST_NULL. Иначе, вызов возвращает flag = false и значение статуса не определено. MPI_Test является локальной операцией.

MPI_Test можно вызывать с нулевым (MPI_REQUEST_NULL) аргументом запроса. В этом случае операция заканчивается немедленно и возвращает flag = true и empty для status.

MPI_Wait (request, status)

INOUT request запрос (дескриптор)

OUT status объект состояния (статус)

MPI_Wait разблокирует процесс, когда операция передачи данных, указанная в запросе завершена, в этом случае дескриптор запроса устанавливается в MPI_REQUEST_NULL, в status возвращается информация о завершенной операции.

Разрешается вызывать MPI_Wait с нулевым (MPI_REQUEST_NULL) аргументом запроса. В этом случае операция заканчивается немедленно со статусом empty.

Множественные завершения

MPI_ Waitany или MPI_Testany используются для ожидания завершения одной из нескольких операций.

MPI_Waitall или MPI_Testall используются для завершения всех операций в списке.

Waitsome или MPI_Testsome используются для завершения некоторых операций из списка.

MPI_Waitany (count, array_of_requests, index, status)

IN count длина списка (целое)

IN/OUT array_of_requests массив запросов (массив дескрипторов)

OUT index индекс дескриптора завершенной операции (целое)

OUT status статусный объект (статус)

MPI_Waitany блокирует работу процесса до тех пор, пока не завершится любая операция, из указанного массива дескрипторов запроса. В функцию подается массив из count запросов, используя который функция завершает ждущие окончания операции приема/передачи данных. Смысл суффикса ANY заключается в том, выбирается для завершения не конкретное сообщение с однозначно определенными атрибутами в запросе, а первые в очереди, т.е. выполняется произвольный выбор. Операция возвращает в index порядковый номер завершенного запроса и заполняет поля в структуре status. Запрос становится не активным и дескриптор запроса устанавливается в MPI_REQUEST_NULL.

Массив array_of_request может содержать нулевые значения. В этом случае вызов заканчивается немедленно с index =MPI_UNDEFINED и со статусом empty.

MPI_Testany (count, array_of_requests, index, flag, status)

IN count длина списка (целое)

IN/OUT array_of_requests массив запросов (массив дескрипторов)

OUT index индекс дескриптора для завершенной операции (целое)

OUT flag true, если одна из операций завершена

OUT status статусный объект (статус)

MPI_Testany не блокирует работу процесса, выполняет тестирование завершенных операций обмена из указанного массива дескрипторов запроса. Если любая из указанных операций обмена завершена, функция устанавливает flag = true, индекс запроса присваивается параметру index и заполняются поля в структуре status; запрос удаляется и дескриптор запроса устанавливается в MPI_REQUEST_NULL. Если ни одна операция приема/передачи с поданным массивом запросов не завершилась на момент вызова функции MPI_Testany, возвращается flag = false, значение MPI_UNDEFINED в index и состояние аргумента status является неопределенным. Массив array_of_request может содержать нулевые значения. В этом случае вызов заканчивается немедленно с flag = true, index = MPI_UNDEFINED и status = empty.

MPI_Waitall ( count, array_of_requests, array_of_statuses)

IN count длина списков (целое)

INOUT array_of_requests массив запросов (массив дескрипторов)

OUT array_of_statuses массив статусных объектов (массив статусов)

MPI_Waitall блокирует работу, пока все операции обмена, связанные с дескрипторами в указанном массиве запросов не завершатся. Массив запросов и массив статусов должны иметь одинаковое количество элементов. Если завершение какого либо обмена в MPI_Waitall, оказался неудачным, функция возвращает код MPI_ERR_IN_STATUS и устанавливает в поля ошибки каждого статуса специфический код. Код ошибки будет MPI_SUCCESS, если все обмены завершены удачно. Если в MPI_Waitall подать неверный параметр, то обмен не будет завершен.

MPI_Testall (count, array_of_requests, flag, array_of_statuses)

IN count длина списка (целое)

INOUT array_of_requests массив запросов (массив дескрипторов)

OUT flag (логический тип)

OUT array_of_statuses массив статусных объектов(массив статусов)

MPI_Testall не блокирует работу процесса, возвращает flag=true, если на момент вызова функции все обмены, указанные в массиве дескрипторов запросов завершены. В этом случае заполняется каждый статусный элемент из массива array_of_statuses, который соответствует каждому завершенному обмену. Запрос удаляется и дескриптор устанавливается в MPI_REQUEST_NULL. В противном случае возвращается flag=false.

MPI_Waitsome(incount, array_of_requests, outcount, array_of_indices, array_of_statuses)

IN incount длина массива запросов (целое)

INOUT array_of_requests массив запросов (массив дескрипторов)

OUT outcount число завершенных запросов (целое)

OUT array_of_indices массив индексов операций, которые завершены (массив целых)

OUT array_of_statuses (массив статусов)

MPI_Waitsome блокирует работу процесса до тех пор, пока хотя бы одна операция, связанная с указанным массивом дескрипторов запроса, не завершится. Возвращает в outcount количество завершенных запросов. В массив array_of_indices присваиваются индексы завершенных операций (значения в array_of_indices соответствуют индексу завершенного обмена в массиве array_of_requests). В массив array_of_status возвращает статус завершенных операций. Завершенные запросы удаляются, связанные с ними дескрипторы устанавливаются в MPI_REQUEST_NULL.

MPI_Testsome(incount, array_of_requests, outcount, array_of_indices, array_of_statuses)

IN incount длина массива запросов (целое)

IN OUT array_of_requests массив запросов (массив дескрипторов)

OUT outcount число завершенных запросов (целое)

OUT array_of_indices массив индексов завершенных операций (массив целых)

OUT array_of_statuses (массив статусов)

MPI_Testsome не блокирует работу процесса, выполняется аналогично MPI_Waitsome. Если ни одной операции на момент вызова функции не завершено, outcount = 0.

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

/////// MPI_Isend process 0 -> MPI_Irecv all process ///////////

/////// program run : mpirun -np 4 file_name.exe ///////////

#include <iostream.h>

#include <mpi.h>

#define NumProcForPrintResult 1

void main( int argc, char *argv[] ) {

int proc, size;

int i, j=0;

double a[10], b[10];

MPI_Status *stat;

MPI_Request *req;

MPI_Init( &argc, &argv);

MPI_Comm_rank( MPI_COMM_WORLD, &proc);

MPI_Comm_size( MPI_COMM_WORLD, &size);

if (proc == 0) { /* код для proc=0 */

stat=new MPI_Status [6];

req=new MPI_Request [6];

for(i=0; i<10; i++)

a[i]=(double)i+1.2;

MPI_Irecv(&a[0], 10, MPI_DOUBLE, 0, 99, MPI_COMM_WORLD, &req[0]);

/* MPI_Wait(&req[0],&stat[0]); //ошибка!!!!! блокировка */

/* for(i=0; i<10; i++) //возможна ошибка, передача, возможно

cout<<b[i])<<” “; // не закончилась принимающие массивы без значение

*/

for(i=0,j=1; i<size; i++,j++)

MPI_Isend(&a[0], 10, MPI_DOUBLE, i, 99, MPI_COMM_WORLD, &req[j]);

MPI_Waitall(5,req,stat);

}

else { /* код для остальных процессов */

stat=new MPI_Status [2];

req=new MPI_Request [2];

MPI_Irecv(&b[0], 10, MPI_DOUBLE, 0, 99, MPI_COMM_WORLD, &req[0]);

MPI_Wait(&req[0],&stat[0]);

}

if(proc==NumProcForPrintResult) {

for(i=0; i<10; i++)

cou<<b[i]<<” “;

cout<<endl;

}

MPI_Finalize();

}

Семантика не блокирующих коммуникаций

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

Функции проверки и отмены сообщений

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

Для того, чтобы избежать возможных ошибок в таких ситуациях, а также с целью экономии памяти в MPI реализованы функции MPI_Probe и MPI_Iprobe.

Функции MPI_Probe и MPI_Iprobe позволяют проверить входные сообщения по номеру источника отправления (source) без их реального приема (до вызова функции MPI_Recv(…)). Пользователь затем может решить, как ему принимать эти сообщения, основываясь на информации, полученной в структуре status. В частности, пользователь может выделить память для приемного буфера согласно длине пришедшего сообщения.

MPI_Iprobe (source, tag, comm, flag, status)

IN source номер процесса-отправителя или MPI_ANY_SOURCE

IN tag значение тэга или MPI_ANY_TAG

IN comm коммуникатор

OUT flag (логическое значение)

OUT status статус (статус)

MPI_Probe (source, tag, comm, status)

IN source номер источника или MPI_ANY_SOURCE

IN tag значение тэга или MPI_ANY_TAG

IN comm коммуникатор

OUT status статус (статус)

Функция MPI_Probe является блокирующей и заканчивается после того, как соответствующее сообщение было найдено. Функция MPI_Iprobe – является не блокирующей. Она просматривает всю очередь пришедших сообщений и возвращает значение flag = true, если сообщение с заданными в функции атрибутами, соответствующими аргументам source, tag и comm доставлено. В этом случае поля структуры status, которая является аргументом функции, инициализируются значениями, в соответствии с пробованным сообщением. В противном случае вызов возвращает flag = false, и оставляет status неопределенным.

Все пришедшие сообщения выстраиваются в очередь до тех пор, пока не будет вызвана функция приема MPI_Recv, которая выбирает из очереди по заданным в функции атрибутам соответствующее сообщение. Сообщение принимается и системные ресурсы, используемые для данного обмена, освобождаются. Если в функциях MPI_Probe и MPI_Iprobe используются MPI_ANY_SOURCE и MPI_ANY_TAG, будет получена информация о первом пришедшем сообщении. Определить номер процесса-отправителя, значение tag и длины сообщения можно, обращаясь к полям структуры status. При использовании MPI_ANY_SOURCE и tag будет получена информация о первом пришедшем сообщении с данным значением tag. При использовании MPI_ANY_TAG и source будет получена информация о первом пришедшем сообщении от процесса с номером source.

Аргумент status также возвращает информацию о длине принятого сообщения. Однако, эта информация не содержится как поле структуры status; чтобы получить значение длины принятого сообщения необходимо вызвать функцию MPI_GET_COUNT.

MPI_GET_COUNT(status, datatype, count)

IN status статус, возвращенный операцией приема (Status)

IN datatype тип принимаемых данных (handle)

OUT count количество принятых элементов (integer)

Операция MPI_Cancel маркирует для отмены ждущие выполнения не блокирующие операции обмена по указанному в параметре дескриптору запроса.

MPI_Cancel (request)

IN request - коммуникационный запрос (дескриптор)

Вызов MPI_Cancel заканчивается немедленно. После маркировки отмены операции обмена необходимо ее завершить, используя вызов MPI_Wait или MPI_Test (или любые производные операции). Если обмен отмечен для отмены, то вызов MPI_Wait для этой операции гарантирует ее завершение.

Если используется буферизующий обмен (MPI_Ibsend), будет освобожден буфер, выделенный для обмена.

Отмена операции обмена и выполнение обмена одновременно выполняться не могут, либо выполняется обмен, либо выполняется его отмена. Если операция была отменена, то информация о произведенном действии будет возвращена в аргументе status той операции, которая выполняет обмен.

MPI_TEST_CANCELLED(status, flag)

Возвращает flag = true, если обмен, связанный с объектом статуса, был успешно отменён. В этом случае, все другие поля статуса (такие как count и tag) не определены. В противном случае возвращается flag = false. Если операция приёма могла быть отменена, тогда следует вначале вызвать MPI_TEST_CANCELLED, чтобы проверить была ли операция отменена, перед проверкой других полей возвращенного статуса.

Совмещение передачи и приема сообщений

MPI_SENDRECV выполняет операцию блокирующей передачи-приема. Передача и прием используют тот же самый коммуникатор, но, возможно, различные тэги. Буферы отправителя и получателя должны быть разделены и могут иметь различную длину и типы данных. Сообщение, посланное операцией send–receive, может быть получено обычной операцией приема или опробовано операцией probe, send–receive может также получать сообщения, посланные обычной операцией передачи.

MPI_ Sendrecv (sendbuf, sendcount, sendtype, dest, sendtag, recvbuf, recvcount, recvtype, source, recvtag, comm, status)

IN sendbuf начальный адрес буфера отправителя (альтернатива)

IN sendcount число элементов в буфере отправителя (целое)

IN sendtype тип элементов в буфере отправителя (дескриптор)

IN dest номер процесса-получателя (целое)

IN sendtag тэг процесса-отправителя (целое)

OUT recvbuf начальный адрес приемного буфера (альтернатива)

IN recvcount число элементов в приемном буфере (целое)

IN recvtype тип элементов в приемном буфере (дескриптор)

IN source номер процесса-отправителя (целое)

IN recvtag тэг процесса-получателя (целое)

IN comm коммуникатор (дескриптор)

OUT status статус (статус)

MPI_Sendrecv_replace(buf, count, type, dest, sendtag, source, recvtag, comm, status)

Имеет один и тот же буфер передачи и приема сообщения.

  1. Коллективные операции передачи данных. Основные определения. Принцип взаимодействия процессов при выполнении барьерной синхронизации, широковещательной передачи, операций сборки и рассылки данных.

К операциям коллективного обмена относятся:

1 Барьерная синхронизация всех процессов группы MPI_Barrier(comm )

2.Широковещательная передача от одного процесса всем остальным процессам группы MPI_Bcast(buffer, count, datatype, root, comm )

3 Операция сбора данных

3.1 Сбор данных из всех процессов группы в один процесс

MPI_Gather(sendbuf, sendcount, sendtype, recvbuf, recvcount, recvtype, int root, comm);

3.2 Сбор данных из всех процессов группы во все процессы группы

MPI_Allgather(sendbuf, sendcount, sendtype, recvbuf, recvcount, recvtype, comm);

3.3 Сбор данных из всех процессов группы в один процесс со смещением.

MPI_Gatherv(sendbuf, sendcount, sendtype, recvbuf, int *recvcount, int *displs, recvtype, root, comm);

3.4 Сбор данных из всех процессов группы во все процессы группы со смещением

MPI_Allgatherv(sendbuf, sendcount, sendtype, recvbuf, recvcounts, int *displs, recvtype, comm);

4 Операция рассылки данных

4.1Рассылка данных из одного процесса группы на все процессы группы

MPI_Scatter(sendbuf, sendcount, sendtype, recvbuf, recvcount, recvtype, root, comm);

4.2Рассылка данных из одного процесса группы на все процессы группы

MPI_Scatterv(sendbuf, sendcount, int *displs, sendtype, recvbuf, recvcount, recvtype, root, comm);

5. Операции редукции (обработки данных), выполняют передачу данных с последующим выполнением заданной операции, например: сложение, нахождение максимума/минимума и т.д. (операции может создавать программист). Результатом выполнения функции будет результат выполнения операции. Более подробно операцию рассмотрим позже.

MPI_Reduce(sendbuf, recvbuf, count, datatype, MPI_Op op, root, comm); - результат находится на процессе root.

MPI_Allreduce(sendbuf, recvbuf, count, datatype, MPI_Op op, comm); - результат находится на всех процессах группы.

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

Вызов коллективной функции может возвращать управление сразу, как только его участие в коллективной операции завершено.

1. Барьерная синхронизация - MPI_Barrier (comm)

IN comm коммуникатор (дескриптор)

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

2. Широковещательная передача

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

MPI_Bcast(buffer, count, datatype, root, comm )

IN/OUT buffer адрес начала буфера

IN count количество записей в буфере (целое)

IN datatype тип данных в буфере

IN root номер корневого процесса (целое)

IN comm коммуникатор

3. Сбор данных

MPI_Gather(sendbuf, sendcount, sendtype, recvbuf, recvcount, recvtype, int root, comm);

IN sendbuf начальный адрес буфера передачи сообщения

IN sendcount количество элементов в отсылаемом сообщении (целое)

IN sendtype тип элементов в отсылаемом сообщении

OUT recvbuf начальный адрес буфера сборки данных (исп-ся только корневым проц.)

IN recvcount колич. элем-ов в принимаемом сообщении (исп-ся только корневым проц.)

IN recvtype тип элементов в получаемом сообщения на процессе-получателе

IN root номер процесса-получателя (целое)

IN comm коммуникатор (дескриптор)

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

Выполнение MPI_Gather будет давать такой же результат, как если бы каждый из n процессов группы (включая корневой процесс) выполнил вызов MPI_Send (sendbuf, sendcount, sendtype, root, …), а принимающий процесс выполнил n вызовов MPI_Recv(recvbuf + i * recvcount * extent(recvtype), recvcount, recvtype, i, …)

Количество посланных и полученных данных должно совпадать, т.е. sendcount== recvcount. Аргумент recvcount в главном процессе показывает количество элементов, которые он получил от каждого процесса, а не общее количество полученных элементов.

В корневом процессе используются все аргументы функции, на остальных процессах используются только аргументы sendbuf, sendcount, sendtype, root, comm. Аргументы comm и root должны иметь одинаковые значения во всех процессах.

MPI_Gatherv(sendbuf, sendcount, sendtype, recvbuf, recvcounts, displs, recvtype, root, comm)

IN sendbuf начальный адрес буфера отправителя

IN sendcount количество элементов в отсылаемом сообщении (целое)

IN sendtype тип элементов в отсылаемом сообщении

OUT recvbuf адрес буфера процесса сборки данных (существ-но для корневого процесса)

IN recvcounts массив цел. чисел (gsize), содержит количество элементов, полученных от каждого процесса (используется корневым процессом)

IN displs массив целых чисел (gsize). Эл-нт i определяет смещение относит. recvbuf, в котором размещаются данные из процесса i (исп-ся корневым процессом)

IN recvtype тип данных элементов в буфере процесса-получателя

IN root номер процесса-получателя (целое)

IN comm коммуникатор

MPI_Gatherv имеет аналогичные параметры, за исключением int* recvcounts и int*displs – это массивы целых чисел, размеры которых равны размеру группы (числу процессов).

В отличие от MPI_Gather при использовании функции MPI_Gatherv разрешается принимать от каждого процесса переменное число элементов данных, поэтому в функции MPI_Gatherv аргумент recvcounts является массивом. Если Вы выполняете операцию вне ветки какого-либо процесса, значение sendcount будет одно для всех процессов в группе. Для того, чтобы процессы могли выполнить операцию с различным sendcount функцию надо вызывать в каждой ветке, определяющей код процесса, правильно определяя значения элементов массива recvcounts на корневом процессе.

Выполнение MPI_Gatherv будет давать такой же результат, как если бы каждый процесс, включая корневой, посылал корневому процессу сообщение: MPI_Send(sendbuf, sendcount, sendtype, root, …), а принимающий процесс выполнил n операций приема: MPI_Recv(recvbuf+displs[i]*extern(recvtype), recvcounts[i], recvtype, i, …).

Сообщения помещаются в буфер принимающего процесса в порядке возрастания номеров процессов, от которых они приходят, то есть данные, посланные процессом i, помещаются в i-ю часть принимающего буфера recvbuf на корневом процессе. i-я часть recvbuf начинается со смещения displs[i].

В принимающем процессе используются все аргументы функции MPI_Gatherv, а на всех других процессах используются только аргументы sendbuf, sendcount, sendtype, root, comm. Переменные comm и root должны иметь одинаковые значения во всех процессах.

  1. Рассылка данных

MPI_Scatter(sendbuf, sendcount, sendtype, recvbuf, recvcount, recvtype, root, comm);

IN sendbuf адрес буфера рассылки (исп-ся только корневым проц.)

IN sendcount кол-во эл-ов, посылаемых каждому процессу (исп-ся корневым проц.)

IN sendtype тип данных элементов в буфере посылки (исп-ся корневым проц.)

OUT recvbuf адрес буфера процесса-получателя

IN recvcount количество элементов в буфере корневого процесса (целое)

IN recvtype тип данных элементов приемного буфера

IN root номер процесса-получателя (целое)

IN comm коммуникатор (дескриптор)

MPI_Scatter – обратная функция по отношению к MPI_Gather. Результат ее выполнения таков, как если бы корневой процесс выполнил n операций посылки: MPI_Send(senbuf + i * extent(sendtype), sendcount, sendtype, i, …), и каждый процесс выполнит прием: MPI_Recv(recvbuf, recvcount, recvtype, i, …).

Значения sendcount[i], sendtype на главном процессе, должны быть теми же, что и recvcount, recvtype на процессе i. Количество посланных и полученных данных должно совпадать для всех процессов. Процесс, который выполняет рассылку использует все аргументы функции, принимающие процессы используют только аргументы recvbuf, recvcount, recvtype, root, comm. Аргументы root и comm должны быть одинаковыми во всех процессах.

Сбор данных на всех процессах

MPI_Allgather( sendbuf, sendcount, sendtype, recvbuf, recvcount, recvtype, comm)

IN sendbuf начальный адрес посылающего буфера

IN sendcount количество элементов в буфере (целое)

IN sendtype тип данных элементов в посылающем буфере

OUT recvbuf адрес принимающего буфера

IN recvcount кол-во элементов, полученных от любого процесса (целое)

IN recvtype тип данных элементов принимающего буфера

IN comm коммуникатор

Функцию MPI_Allgather можно представить как MPI_Gather, где результат принимают все процессы. Блок данных, посланный j-м процессом принимается каждым процессом и помещается в j-й блок буфера recvbuf.

MPI_Allgather можно представить как MPI_Gatherv, где результат получают все процессы. j-й блок данных, посланный каждым процессом, принимается каждым процессом и помещается в j-й блок буфера recvbuf. Эти блоки могут быть разного размера.

MPI_Allgatherv( sendbuf, sendcount, sendtype, recvbuf, recvcounts, displs, recvtype, comm)

IN sendbuf начальный адрес посылающего буфера

IN sendcount количество элементов в посылающем буфере (целое)

IN sendtype тип данных элементов в посылающем буфере

OUT recvbuf адрес принимающего буфера

IN recvcounts целочисл. массив (gsize), содерж.кол-во эл-ов, получ. от каждого процесса

IN displs целочисл. массив (gsize), элемент i – смещение (относит. recvbuf), куда помещаются принимаемые данные от процесса i

IN recvtype тип данных элементов принимающего буфера

IN comm коммуникатор

Значения sendcount, sendtype в процессе j должны быть такими же, как recvcounts[j], recvtype в любом другом процессе.

Результат вызова MPI_Allgatherv (...) такой же, как если бы все процессы выполнили n вызовов: MPI_Gatherv(sendbuf, sendcount, sendtype, recvbuf, recvcounts, displs,recvtype, root, comm), для root = 0, …, n-1.

Функция MPI_Alltoall – расширение функции MPI_Allgather для случая, когда каждый процесс посылает данные каждому процессу. При этом j-й блок, посланный процессом i, принимается процессом j и помещается в i-й блок буфера recvbuf.

MPI_ Alltoall(sendbuf, sendcount, sendtype, recvbuf, recvcount, recvtype, comm)

IN sendbuf начальный адрес посылающего буфера

IN sendcount количество элементов посылаемых в каждый процесс (целое)

IN sendtype тип данных элементов посылающего буфера

OUT recvbuf адрес принимающего буфера

IN recvcount количество элементов, принятых от какого-либо процесса (целое)

IN recvtype тип данных элементов принимающего буфера

IN comm коммуникатор

Результат выполнения функции MPI_Alltoall такой же, как если бы каждый процесс выполнил посылку данных каждому процессу (включая себя) вызовом: MPI_Send(sendbuf + i * sendcount * extent(sendtype), sendcount, sendtype, i, ...), и принял данные от всех остальных процессов путем вызова: MPI_Recv(recvbuf + i* recvcount* extent(recvtype), recvcount, i,…).

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

MPI_Alltoallv(sendbuf, sendcounts, sdispls, sendtype, recvbuf,recvcounts, rdispls, recvtype, comm)

IN sendbuf начальный адрес посылающего буфера (альтернатива)

IN sendcounts целочисл. массив (gsize), опред-ет кол-во посыл. кажд. процессу элементов

IN sdispls целочисл. массив (gsize). Эл-нт j содержит смещение области (относит. sendbuf), из которой берутся данные для процесса j

IN sendtype тип данных элементов посылающего буфера

OUT recvbuf адрес принимающего буфера

IN recvcounts целочисл. массив (gsize), содержит число эл-ов, которые могут быть приняты от каждого процесса

IN rdispls целочисл. массив (gsize). Эл-нт i определяет смещение области (относительно recvbuf), в которой размещаются данные, получаемые из процесса i

IN recvtype тип данных элементов принимающего буфера

IN comm коммуникатор

5. Операции редукции (обработки данных)

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

MPI_Reduce(sendbuf, recvbuf, count, datatype, op, root, comm)

IN sendbuf адрес посылающего буфера

OUT recvbuf адрес принимающего буфера (исп-ся только корневым процессом)

IN count количество элементов в посылающем буфере (целое)

IN datatype тип данных элементов посылающего буфера

IN op операция редукции

IN root номер главного процесса (целое)

IN comm коммуникатор

Базовые (предопределенные) типы операций MPI

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

Пример выполнения операции суммирования элементов массива для 3-ех процессов (в каждом массиве по 4 элемента, сообщения собираются на процессе с рангом 2)

Операции MPI_MINLOC и MPI_MAXLOC

Операции MPI_MINLOC/MPI_MAXLOC используются для определения глобального минимума/максимума и соответствующих им индексов. Каждый процесс предоставляет значение и свой номер в группе. Операция редукции с op = MPI_MAXLOC возвратит значение максимума и номер первого процесса с этим значением. Аналогично, MPI_MINLOC возвратит значение минимума и номер процесса, если процессов с таким значением несколько будет возвращен номер первого.

Чтобы использовать MPI_MINLOC и MPI_MAXLOC в операции редукции, нужно определить тип данных datatype, который представляет пару (значение и индекс). MPI предоставляет девять таких предопределенных типов данных – 3 фортрана и 6 Си:

MPI_FLOAT_INT переменные типа float и int

MPI_DOUBLE_INT переменные типа double и int

MPI_LONG_INT переменные типа long и int

MPI_2INT пара переменных типа int

MPI_SHORT_INT переменные типа short и int

MPI_LONG_DOUBLE_INT переменные типа long double и int

Пример. Функция MPI_Reduce с операцией MPI_MAXLOC

#include <stdio.h>

#include <mpi.h>

struct{

double value; int proc;

}local_max,global_max;

void main( int argc, char *argv[] ) {

int i, rank, size;

MPI_Init(&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &rank);

MPI_Comm_size(MPI_COMM_WORLD, &size);

double array[10];

double max_value;

for(i=0; i<10; i++) array[i]=(double)rand()/RAND_MAX + 0.1*rank;

max_value=array[0];

for(i=0; i<10; i++){

if(array[i]>max_value)

max_value=array[i];

}

local_max.value=max_value;

local_max.proc=rank;

MPI_Reduce(&local_max,&global_max,4,MPI_DOUBLE_INT,MPI_MAXLOC,size-1, MPI_COMM_WORLD );

if (rank == size-1)

printf("global maximum=%f number process=%d\n",global_max.value, global_max.proc);

MPI_Finalize();

}

Пример. Функция MPI_Reduce с операцией MPI_SUM

#include <stdio.h>

#include <mpi.h>

void main( int argc, char *argv[] ) {

int a[]={1,4,7,2,1,9,6,8,3,5}, summa[10];

int i, rank;

MPI_Init( &argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &rank);

MPI_Reduce(&a[0],&summa[0],10,MPI_INT,MPI_SUM,1,MPI_COMM_WORLD );

if (rank ==1) { //результат помещается на root процесс, root=1

for (i=0; i<10; i++) {

printf("%d\n", summa[i]);

}

}

MPI_Finalize();

}

  1. Принцип взаимодействия параллельных процессов при выполнении обобщенных коллективных операций сборки и рассылки данных.

Сбор данных

MPI_Gather(sendbuf, sendcount, sendtype, recvbuf, recvcount, recvtype, int root, comm);

IN sendbuf начальный адрес буфера передачи сообщения

IN sendcount количество элементов в отсылаемом сообщении (целое)

IN sendtype тип элементов в отсылаемом сообщении

OUT recvbuf начальный адрес буфера сборки данных (исп-ся только корневым проц.)

IN recvcount колич. элем-ов в принимаемом сообщении (исп-ся только корневым проц.)

IN recvtype тип элементов в получаемом сообщения на процессе-получателе

IN root номер процесса-получателя (целое)

IN comm коммуникатор (дескриптор)

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

Выполнение MPI_Gather будет давать такой же результат, как если бы каждый из n процессов группы (включая корневой процесс) выполнил вызов MPI_Send (sendbuf, sendcount, sendtype, root, …), а принимающий процесс выполнил n вызовов MPI_Recv(recvbuf + i * recvcount * extent(recvtype), recvcount, recvtype, i, …)

Количество посланных и полученных данных должно совпадать, т.е. sendcount== recvcount. Аргумент recvcount в главном процессе показывает количество элементов, которые он получил от каждого процесса, а не общее количество полученных элементов.

В корневом процессе используются все аргументы функции, на остальных процессах используются только аргументы sendbuf, sendcount, sendtype, root, comm. Аргументы comm и root должны иметь одинаковые значения во всех процессах.

MPI_Gatherv(sendbuf, sendcount, sendtype, recvbuf, recvcounts, displs, recvtype, root, comm)

IN sendbuf начальный адрес буфера отправителя

IN sendcount количество элементов в отсылаемом сообщении (целое)

IN sendtype тип элементов в отсылаемом сообщении

OUT recvbuf адрес буфера процесса сборки данных (существ-но для корневого процесса)

IN recvcounts массив цел. чисел (gsize), содержит количество элементов, полученных от каждого процесса (используется корневым процессом)

IN displs массив целых чисел (gsize). Эл-нт i определяет смещение относит. recvbuf, в котором размещаются данные из процесса i (исп-ся корневым процессом)

IN recvtype тип данных элементов в буфере процесса-получателя

IN root номер процесса-получателя (целое)

IN comm коммуникатор

MPI_Gatherv имеет аналогичные параметры, за исключением int* recvcounts и int*displs – это массивы целых чисел, размеры которых равны размеру группы (числу процессов).

В отличие от MPI_Gather при использовании функции MPI_Gatherv разрешается принимать от каждого процесса переменное число элементов данных, поэтому в функции MPI_Gatherv аргумент recvcounts является массивом. Если Вы выполняете операцию вне ветки какого-либо процесса, значение sendcount будет одно для всех процессов в группе. Для того, чтобы процессы могли выполнить операцию с различным sendcount функцию надо вызывать в каждой ветке, определяющей код процесса, правильно определяя значения элементов массива recvcounts на корневом процессе.

Выполнение MPI_Gatherv будет давать такой же результат, как если бы каждый процесс, включая корневой, посылал корневому процессу сообщение: MPI_Send(sendbuf, sendcount, sendtype, root, …), а принимающий процесс выполнил n операций приема: MPI_Recv(recvbuf+displs[i]*extern(recvtype), recvcounts[i], recvtype, i, …).

Сообщения помещаются в буфер принимающего процесса в порядке возрастания номеров процессов, от которых они приходят, то есть данные, посланные процессом i, помещаются в i-ю часть принимающего буфера recvbuf на корневом процессе. i-я часть recvbuf начинается со смещения displs[i].

В принимающем процессе используются все аргументы функции MPI_Gatherv, а на всех других процессах используются только аргументы sendbuf, sendcount, sendtype, root, comm. Переменные comm и root должны иметь одинаковые значения во всех процессах.

5. Рассылка данных

MPI_Scatter(sendbuf, sendcount, sendtype, recvbuf, recvcount, recvtype, root, comm);

IN sendbuf адрес буфера рассылки (исп-ся только корневым проц.)

IN sendcount кол-во эл-ов, посылаемых каждому процессу (исп-ся корневым проц.)

IN sendtype тип данных элементов в буфере посылки (исп-ся корневым проц.)

OUT recvbuf адрес буфера процесса-получателя

IN recvcount количество элементов в буфере корневого процесса (целое)

IN recvtype тип данных элементов приемного буфера

IN root номер процесса-получателя (целое)

IN comm коммуникатор (дескриптор)

MPI_Scatter – обратная функция по отношению к MPI_Gather. Результат ее выполнения таков, как если бы корневой процесс выполнил n операций посылки: MPI_Send(senbuf + i * extent(sendtype), sendcount, sendtype, i, …), и каждый процесс выполнит прием: MPI_Recv(recvbuf, recvcount, recvtype, i, …).

Значения sendcount[i], sendtype на главном процессе, должны быть теми же, что и recvcount, recvtype на процессе i. Количество посланных и полученных данных должно совпадать для всех процессов. Процесс, который выполняет рассылку использует все аргументы функции, принимающие процессы используют только аргументы recvbuf, recvcount, recvtype, root, comm. Аргументы root и comm должны быть одинаковыми во всех процессах.

  1. Типы данных MPI. Понятие производных типов данных MPI. Конструкторы производных типов данных.

Типы данных MPI

При выполнении операций передачи сообщений в функциях MPI необходимо указывать тип пересылаемых данных. MPI содержит набор базовых типов данных, во многом совпадающих с типами данных в алгоритмических языках C и Fortran. Кроме того, в MPI содержит функции для создания производных типов данных. Все типы данных MPI содержатся в файле mpi.h Аналогично функциям MPI, базовые типы данных имеют префикс MPI, например, MPI_INT, MPI_DOUBLE и т.д. В стандарте MPI не предусмотрен механизм преобразование типов данных, используемый в Си.

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

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

Конструкторы типов данных. Функции упаковки и распаковки данных.

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

Другая проблема заключается в том, что мы не можем передать за одну отправку, используя стандартные типы данных MPI переменные разных типов, например int и double или более сложные конструкции, например структуры с различными по типу и размеру полями. Выполнить такую передачу можно несколькими способами:

1. преобразовать данные меньшего типа к большему, в данном случае int к double. Естественно, что при выполнении такой передачи длина сообщения будет больше, поскольку каждый 4-ух байтовый int будет преобразован в 8-ми байтовый double. Поэтому, чем больше данных типа int и меньше данных типа double, тем больше увеличится объем передаваемого сообщения. Это неэффективно.

2. использовать операцию упаковки и распаковки. Это лучше предыдущего способа, но если необходимо часто передавать нестандартные данные, то многократный вызов операций MPI_Pack/MPI_Unpack также приводят к дополнительным накладным расходам, поскольку выполняют большую работу по переписыванию данных из одной области памяти в другую. Что при больших объемах данных занимает достаточно много времени.

3. Создать новый тип данных и использовать его на протяжении всей работы программы.

Операции упаковки и распаковки данных

Тип MPI_PACKED используется для передачи данных, которые были явно упакованы, или для получения данных, которые будут явно распакованы.

MPI_Pack (inbuf, incount, datatype, outbuf, outsize, position, comm)

IN inbuf начало входного буфера (альтернатива)

IN incount число единиц входных данных (целое)

IN datatype тип данных каждой входной единицы (дескриптор)

OUT outbuf начало выходного буфера (альтернатива)

IN outsize размер выходного буфера в байтах (целое)

INOUT position текущая позиция в буфере в байтах (целое)

IN comm коммуникатор для упакованного сообщения

MPI_Pack пакует сообщение из буфера передачи, который определяется аргументами inbuf, incount, datatype в выходной буфер. Выходной буфер это непрерывная область памяти, содержащая outsize байтов, начиная с адреса outbuf (длина подсчитывается в байтах, а не элементах).

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

MPI_Unpack (inbuf, insize, position, outbuf, outcount, datatype, comm)

IN inbuf начало входного буфера (альтернатива)

IN insize размер входного буфера в байтах (целое)

INOUT position текущая позиция в байтах (целое)

OUT outbuf начало выходного буфера (альтернатива)

IN outcount число единиц для распаковки (целое)

IN datatype тип данных каждой выходной единицы данных (дескриптор)

IN comm коммуникатор для упакованных сообщений (дескриптор)

MPI_Unpack распаковывает сообщение в приемный буфер, который определяется аргументами outbuf, outcount, datatype из буфера, определенного аргументами inbuf и insize. Входной буфер - это непрерывная область памяти, содержащая insize байтов, начиная с адреса inbuf.

Входное значение position есть первая ячейка во входном буфере, занятом упакованным сообщением. Далее значение рosition увеличивается на размер упакованного сообщения, таким образом выходное значение рosition - это первая ячейка во входном буфере после сообщением, которое было упаковано.

В основном функции используются в том случае, если необходимо сформировать один буфер передачи из нескольких. В этом случае, выполняется несколько последовательно связанных обращений к MPI_Pack, где первый вызов определяет position = 0, и каждый последующий вызов вводит значение position, которое было выходом для предыдущего вызова, и то же самое значение для outbuf, outcount. Этот упакованный объект содержит эквивалентное сообщение, которое выполнялось бы по одной передаче отдельно с каждым буфером передачи.

Упакованный объект передается и принимается с типом MPI_PACKED любой операцией передачи сообщений (двухточечной или коллективной) с последующей распаковкой.

Если упакованный объект был последовательно сформирован из нескольких буферов, т.е. выполнено несколько операций MPI_Pack, то он может быть распакован в несколько последовательных сообщений последовательностью вызовов функции MPI_Unpack, где первый вызов определяет position = 0, а каждый последующий вызов вводит значение position, которое было выходом предыдущего обращения.

Очевидно, что количество упаковок должно совпадать с количеством распаковок.

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

MPI_Pack_size(incount, datatype, comm, size)

IN incount аргумент count для упакованного вызова (целое)

IN datatype аргумент datatype для упакованного вызова (дескриптор)

IN comm аргумент communicator для упакованного вызова (дескриптор)

OUT size верхняя граница упакованного сообщения в байтах (целое)

Функция MPI_Pack_size возвращает в size верхнюю границу значения position, которая создана обращением к функции упаковки MPI_Pack.

Пример. Функция MPI_Pack/MPI_Unpack

#include <stdio.h>

#include <mpi.h>

void main( int argc, char *argv[] ) {

int ii, iii=2;

double jj, jjj=5.3;

int position, count;

char buff_pack[100], buff_unpack[100];

MPI_Status stat;

if (rank == 0) {

position = 0;

MPI_Pack(&iii,1,MPI_INT,buff_pack,100, &position, MPI_COMM_WORLD);

MPI_Pack(&jjj,1,MPI_DOUBLE,buff_pack,100,&position, MPI_COMM_WORLD);

MPI_Send(&buff_pack[0], position, MPI_PACKED, 1, 0, MPI_COMM_WORLD);

}

else {

MPI_Probe(0, 0, MPI_COMM_WORLD, &stat);

MPI_Get_count(&stat, MPI_PACKED, &count);

MPI_Recv( &buff_unpack[0], count, MPI_PACKED, 0, 0, MPI_COMM_WORLD, &stat);

position = 0;

MPI_Unpack(&buff_unpack[0],count,&position, &ii,1, MPI_INT, MPI_COMM_WORLD);

MPI_Unpack(&buff_unpack[0],count,&position, &jj,1, MPI_DOUBLE,

MPI_COMM_WORLD);

printf("ii=%d jj=%f\n",ii,jj);

}

MPI_Finalize();

}

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

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

Часть карты типа с указанием только базовых типов называется сигнатурой типа:

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

Например, пусть в сообщение должны входить значения переменных:

double a; /* адрес 24 */

double b; /* адрес 40 */

int n; /* адрес 48 */

Тогда производный тип для описания таких данных должен иметь карту типа следующего вида: { (MPI_DOUBLE,0), (MPI_DOUBLE,16), (MPI_INT,24) }

Для характеристики типа данных в MPI используется следующий ряд понятий:

- нижняя граница типа

- верхняя граница типа

- протяженность типа extent(TypeMap) = ub(TypeMap)-lb(TypeMap).

Нижняя граница – определяет смещение для первого байта значений типа данных.

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

Понятие протяженности отличается от понятия размер типа. Протяженность – это размер памяти в байтах, который нужно отводить для одного элемента производного типа. Размер типа данных - это число байтов, которые занимают в памяти данные (разность между адресами последнего и первого байтов данных). Отсюда - различие в значениях протяженности и размера зависит от величины округления при выравнивании адресов. Так, в рассматриваемом примере размер типа равен 28, а протяженность – 32 (предполагается, что выравнивание выставлено на 8 байт).

Итак, создание общего типа данных определяется:

• последовательностью базовых типов данных,

• последовательностью смещений байтов.

1. Непрерывный.

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

MPI_Type_contiguous(count, oldtype, newtype)

IN count число повторений (неотрицательное целое)

IN oldtype старый тип данных (дескриптор)

OUT newtype новый тип данных (дескриптор)

newtype это тип данных, полученный размножением count копий oldtype.

Сконструированный объект типа данных должен быть зафиксирован перед тем, как он может быть использован в обменах. Функция, которая фиксирует новый тип данных имеет следующий вид:

MPI_Type_commit (datatype)

INOUT Datatype тип данных, который фиксируется (handle)

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

Освобождение созданного типа данных

MPI_Type_free (datatype)

INOUT datatype тип данных, который освобождается (handle)

Устанавливает datatype в MPI_DATATYPE_NULL.

2. Вектор

Выполняет размножение типов данных, которые состоят из равных блоков. Интервал между блоками кратно протяжённости старого типа данных.

MPI_Type_vector(count, blocklength, stride, oldtype, newtype)

IN count число блоков (int)

IN blocklength число элементов в каждом блоке (int)

IN stride число элементов между началом каждого блока (int)

IN oldtype старый тип данных

OUT newtype новый тип данных

Пример: пусть, oldtype имеет карту типа {(double, 0), (char,8)}, с протяжённостью 16. Вызов MPI_Type_vector(2, 3, 4, oldtype, newtype) создаст тип данных с картой:

{(double, 0), (char, 8), (double, 16), (char, 24), (double, 32), (char, 40), (double, 64), (char, 72), (double, 80), (char, 88), (double, 96), (char, 104)}.

Это два блока с тремя копиями старого типа в каждом, с шагом 4 элемента (416 байтов) друг от друга.

Вызов MPI_Type_contiguous (count, oldtype, newtype) эквивалентен вызову MPI_Type_vector (count, 1, 1, oldtype, newtype)

3. Hвектор

Функция MPI_Type_hvector аналогична MPI_Type_vector, за исключением того, что stride задается в байтах, а не в элементах, поэтому смещение возможно на произвольное число байт. Символ h добавляет смысл heterogeneous – неоднородность.

int MPI_Type_hvector(int count, int blocklength, int stride, MPI_Datatype oldtype,

MPI_Datatype *newtype)

IN count число блоков

IN blocklength число элементов в каждом блоке

IN stride число байт между началом каждого блока

IN oldtype старый тип данных

OUT newtype новый тип данных

4. Индексированные данные

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

MPI_Type_indexed (count, array_of_blocklengths, array_of_displacements, oldtype, newtype)

IN count число блоков

IN array_of_blocklengths число элементов в каждом блоке (массив длиной count)

IN array_of_displacements смещение каждого блока, в количестве протяжённостей oldtype (массив длиной count)

IN oldtype старый тип данных

OUT newtype новый тип данных

Пример Пусть oldtype имеет карту типа {(double, 0), (char, 8)}, c протяженностью 16. Пусть B = (3,1) и пусть D = (4,0). Вызов MPI_Type_indexed (2, B, D, oldtype, newtype) возвращает тип данных с картой типа {(double, 64), (char, 72), (double, 80), (char, 88), (double, 96), (char, 104), (double, 0), (char, 8)}: три карты старого типа, начинающиеся со смещения 64, и одна копия, начинающаяся со смещения 0.

Вызов MPI_Type_vector (count, blocklength, stride, oldtype, newtype) эквивалентен вызову MPI_Type_indexed (count, B, D, oldtype, newtype), где

Функция MPI_Type_hindexed идентична MPI_Type_indexed, за исключением того, что смещения между блоками в array_of_displacements определены в байтах, а не в значениях, кратных протяжённости oldtype. Буква h добавляет смысл heterogeneous – неоднородность.

MPI_Type_hindexed(count, array_of_blocklengths, array_of_displacements, oldtype, newtype)

IN count число блоков - также число элементов в array_of_displacements и array_of_blocklengths (nonnegative integer)

IN array_of_blocklengths число элементов в каждом блоке (массив nonegative integer)

IN array_of_displacements смещение каждого блока, в байтах (массив integer)

IN oldtype старый тип данных (handle)

OUT newtype новый тип данных (handle)

6. Структурный

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

MPI_Type_struct (count, array_of_blocklengths, array_of_displacements, array_of_types, newtype)

IN count количество блоков (int)

IN array_of_blocklengths число элем-ов в каждом блоке (массив int длиной count)

IN array_of_displacements смещение каждого блока в байтах (массив int длиной count)

IN array_of_types тип элемента в каждом блоке (массив типов длиной count)

OUT newtype новый тип данных (handle)

Пример Пусть type1 имеет карту типа {(double, 0), (char, 8)}, с протяжённостью 16. Пусть B=(2,1,3), D=(0,16,26), и T=(MPI_FLOAT, type1, MPI_CHAR). Тогда вызов MPI_TYPE_STRUCT(3, B, D, T, newtype) создает тип данных с картой,

{(float,0), (float,4), (double,16), (char,24), (char,26), (char,27), (char,28)}:

две копии MPI_FLOAT начинаются с 0, затем копия типа type1 начинается с 16, далее идут три копии MPI_CHAR, начинающиеся с 26.

Пример. Копирование нижней треугольной части матрицы. Для создания нового типа данных используем MPI_Type_indexed

#include <mpi.h>

#include <iostream.h>

void main( int argc, char *argv[] ) {

int a[8][8], b[8][8];

int disp[8], block[8];

MPI_Comm_rank(MPI_COMM_WORLD, &rank);

MPI_Status stat;

for(i=0; i<8; i++){

for(int j=0; j<8; j++){

a[i][j]= (j<=i)?(i+1):0;

cout<<a[i][j]<<"\t";

}

cout<<endl;

}

// копирует нижнюю треугольную часть матрицы a в нижнюю треугольную часть

// матрицы b

MPI_Comm_rank(MPI_COMM_WORLD, &rank);

// вычисляет начало и размер каждого блока (блок – это строка матрицы)

disp[0] = 0;

block[0] = 1;

for (i=1; i<8; i++){

disp[i] = 8*i;

block[i] = i+1;

}

// создание типа данных для нижней треугольной части матрицы, далее этот тип

// данных можно использовать в обменах

MPI_Datatype my_type; //объявили имя нового типа данных

MPI_Type_indexed(count, block, disp, MPI_INT, &my_type);

MPI_Type_commit(&my_type); //зафиксировали новый тип данных

if(rank==0)

MPI_Send(a, 1, my_type, 1, 99, MPI_COMM_WORLD);

if(rank==1){

MPI_Recv(b, 1, my_type, 0, 99, MPI_COMM_WORLD, &stat);

cout<<endl<<"RESULT MATRIX"<<endl;

for(i=0; i<8; i++){

for(int j=0; j<8; j++){

cout<<((j<=i)?b[i][j]:0)<<"\t";

}

cout<<endl;

}

}

MPI_Finalize();

}

  1. Управление группами процессов и коммуникаторами. Конструирование групп и коммуникаторов.

Группы и коммуникаторы

Интерфейс MPI использует такие понятия, как группы процессов (groups), виртуальные топологии (virtual topologies) и коммуникаторы (communicators).

Коммуникаторы создают область для всех операций обмена в MPI. Коммуникаторы разделяются на два вида: интра-коммуникаторы - внутригрупповые коммуникаторы, предназначенные для операций в пределах группы процессов, и интер-коммуникаторы - межгрупповые коммуникаторы, предназначенные для обменов между двумя группами процессов. Начальный для всех процессов интра-коммуникатор MPI_COMM_WORLD, который создается сразу при обращении к функции MPI_INIT. Удалить коммуникатор MPI_COMM_WORLD нельзя. Кроме того, существует коммуникатор, который содержит только себя как процесс – MPI_COMM_SELF.

Группы определяют упорядоченную набор процессов по именам, именами процессов являются их порядковые номера. Группы определяют область для выполнения обменов, но в операциях обмена могут использоваться только коммуникаторы. Группа определена в пределах коммуникатора. В MPI определена предопределенная группа: MPI_GROUP_EMPTY – это группа без процессов.

Управление группой

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

Функции доступа к группе

1. MPI_Group_size(MPI_Group group, int *size) позволяет определить размер группы.

IN group группа (дескриптор)

OUT size количество процессов в группе (целое)

2. MPI_Group_rank(MPI_Group group, int *rank) служит для определения номера процесса в группе.

IN group группа (дескриптор)

OUT rank номер процесса в группе или MPI_UNDEFINED, если процесс не является членом группы (целое)

3. MPI_Group_translate_ranks (MPI_Group group1, int n, int *ranks1, MPI_Group group2, int *ranks2)

IN group1 группа1 (дескриптор)

IN n число номеров в массивах ranks1 и ranks2 (целое)

IN ranks1 массив из номеров процессов в группе1

IN group2 группа2 (дескриптор)

OUT ranks2 массив соответствующих номеров процессов в группе2, MPI_UNDEFINED, если соответствие отсутствует.

Функция MPI_Group_translate_ranks определяет относительную нумерацию одинаковых процессов в двух различных группах. Например, если известны номера некоторых процессов в MPI_COMM_WORLD, то можно узнать их номера в подмножестве этой группы.

4. MPI_Group _compare(group1, group2, result)

group1 первая группа (дескриптор)

group2 вторая группа (дескриптор)

OUT result результат (целое)

Функция MPI_Group_compare сравнивает группы. Если члены группы и их порядок в обеих группах одинаковы, результат будет MPI_IDENT. Если члены группы одинаковы, но порядок различен, то результат будет MPI_SIMILAR. В остальных случаях - MPI_UNEQUAL.

Конструкторы групп

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

MPI не имеет механизма для формирования группы с нуля – группа может формироваться только на основе другой, предварительно определенной группы. Базовая группа, на основе которой определены все другие группы, является группой, связанной с коммуникатором MPI_COMM_WORLD (через функцию MPI_COMM_GROUP).

1. MPI_Comm_group(MPI_Comm comm, MPI_Group *group)

IN comm коммуникатор (дескриптор)

OUT group группа, соответствующая comm (дескриптор)

Функция MPI_Comm_group возвращает в group дескриптор группы из comm.

2. MPI_Group_union(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup)

IN group1 первая группа (дескриптор)

IN group2 вторая группа (дескриптор)

OUT newgroup объединенная группа (дескриптор)

Объединение (union) – содержит все элементы первой группы (group1) и следующие за ними элементы второй группы (group2), не входящие в первую группу.

3. MPI_Group_intersection(MPI_Group group1, MPI_Group group2,MPI_Group *newgroup)

IN group1 первая группа (дескриптор)

IN group2 вторая группа (дескриптор)

OUT newgroup группа, образованная пересечением (дескриптор)

Пересечение (intersect) – содержит все элементы group1, которые также находятся в group2 и упорядоченные, как в первой группе.

4. MPI_Group_difference(MPI_Group group1, MPI_Group group2,MPI_Group *newgroup)

IN group1 первая группа(дескриптор)

IN group2 вторая группа (дескриптор)

OUT newgroup исключенная группа (дескриптор)

Разность (difference) – содержит все элементы group1, которые не находятся в group2.

5. MPI_ Group_incl(group, n, ranks, newgroup)

IN group группа (дескриптор)

IN n количество элементов в массиве номеров (и размер newgroup, целое)

IN ranks номера процессов в group, перешедших в новую группу (массив целых)

OUT newgroup новая группа, полученная из прежней, упорядоченная согласно ranks

Cоздает новую группу, которая состоит из n процессов исходной группы group с номерами rank[0],..., rank[n-1]; процесс с номером i в newgroup есть процесс с номером ranks[i] в group. Если n = 0, то newgroup имеет значение MPI_GROUP_EMPTY.

6. MPI_Group _excl(group, n, ranks, newgroup)

IN group группа (дескриптор)

IN n количество элементов в массиве номеров (целое)

IN ranks массив целочисленных номеров в group, не входящих в newgroup

OUT newgroup новая группа, полученная из прежней, сохраняющая порядок, определенный group (дескриптор)

Создает новую группу, путем удаления из group процессов с номерами ranks[0] ,...ranks[n-1]. Если n = 0, то newgroup идентична group.

Деструктор групп - MPI_Group _free(group)

INOUT group идентификатор группы (дескриптор)

Функции доступа к коммуникаторам

Все следующие операции являются локальными.

1. MPI_Comm_size (comm, size)

IN comm коммуникатор (дескриптор)

OUT size количество процессов в группе comm (целое)

Эта функция указывает число процессов в коммуникаторе. Для MPI_COMM_WORLD она указывает общее количество доступных процессов.

2. MPI_Comm_rank(comm, rank)

IN comm коммуникатор (дескриптор)

OUT rank номер вызывающего процесса в группе comm (целое)

Функция MPI_Comm_rank возвращает номер процесса в частной группе коммуникатора. Ее удобно использовать cовместно с MPI_Comm_size.

3. MPI_Comm_compare(comm1, comm2, result)

IN comm1 первый коммуникатор (дескриптор)

IN comm2 второй коммуникатор (дескриптор)

OUT result результат (целое)

Функция MPI_Comm_compare сравнивает контексты коммуникаторов. Результат MPI_IDENT имеет место тогда и только тогда, когда comm1 и comm2 являются дескрипторами для одного и того же объекта. Результат MPI_CONGRUENT имеет место в том случае, если исходные группы идентичны по компонентам и нумерации; в этом случае коммуникаторы отличаются только контекстом. Результат MPI_SIMILAR имеет место, если члены группы обоих коммуникаторов являются одинаковыми, но порядок их нумерации различен. В противном случае выдается результат MPI_UNEQUAL.

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

Ниже перечисленные функции являются коллективными и вызываются всеми процессами в группе, связанной с comm. В MPI для создания нового коммуникатора необходим исходный коммуникатор. Основным коммуникатором для всех MPI коммуникаторов является коммуникатор MPI_COMM_WORLD.

1. MPI_Comm_dup(comm, newcomm)

IN comm коммуникатор (дескриптор)

OUT newcomm копия comm (дескриптор)

Функция MPI_Comm_dup дублирует существующий коммуникатор comm, возвращает в аргументе newcomm новый коммуникатор с той же группой.

2. MPI_Comm_create(comm, group, newcomm)

IN comm коммуникатор (дескриптор)

IN group группа, являющаяся подмножеством группы comm (дескриптор)

OUT newcomm новый коммуникатор (дескриптор)

Функция создает новый коммуникатор newcomm с коммуникационной группой, определенной аргументом group и новым контекстом. Из comm в newcomm не передается никакой кэшированной информации. Функция возвращает MPI_COMM_NULL для процессов, не входящих в group. Запрос неверен, если не все аргументы в group имеют одинаковое значение или если group не является подмножеством группы, связанной с comm. Заметим, что запрос должен быть выполнен всеми процессами в comm, даже если они не принадлежат новой группе.

3. MPI_Comm_split(comm, color, key, newcomm)

IN comm коммуникатор (дескриптор)

IN color управление созданием подмножества (целое)

IN key управление назначением номеров целое)

OUT newcomm новый коммуникатор (дескриптор)

Эта функция делит группу, связанную с comm, на непересекающиеся подгруппы по одной для каждого значения color. Каждая подгруппа содержит все процессы одного цвета. В пределах каждой под группы процессы пронумерованы в порядке, определенном значением аргумента key, со связями, разделенными согласно их номеру в старой группе.

Для каждой подгруппы создается новый коммуникатор и возвращается в аргументе newcomm. Процесс может иметь значение цвета MPI_UNDEFINED, тогда переменная newcomm возвращает значение MPI_COMM_NULL. Это коллективная операция, но каждому процессу разрешается иметь различные значения для color и key.

Обращение к MPI_Comm_create (сomm, group, newcomm) эквивалентно обращению к MPI_Comm_split (comm, color, key, newcomm), где все члены group имеют color =0 и key = номеру в group, и все процессы, которые не являются членами group, имеют color = MPI_UNDEFINED.

Функция MPI_Comm_split допускает более общее разделение группы на одну или несколько подгрупп с необязательным переупорядочением. Этот запрос используют только интра-коммуникаторы. Значение color должно быть неотрицательно.

Управление группой

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

Функции доступа к группе

1. MPI_Group_size(MPI_Group group, int *size) позволяет определить размер группы.

IN group группа (дескриптор)

OUT size количество процессов в группе (целое)

2. MPI_Group_rank(MPI_Group group, int *rank) служит для определения номера процесса в группе.

IN group группа (дескриптор)

OUT rank номер процесса в группе или MPI_UNDEFINED, если процесс не является членом группы (целое)

3. MPI_Group_translate_ranks (MPI_Group group1, int n, int *ranks1, MPI_Group group2, int *ranks2)

IN group1 группа1 (дескриптор)

IN n число номеров в массивах ranks1 и ranks2 (целое)

IN ranks1 массив из номеров процессов в группе1

IN group2 группа2 (дескриптор)

OUT ranks2 массив соответствующих номеров процессов в группе2, MPI_UNDEFINED, если соответствие отсутствует.

Функция MPI_Group_translate_ranks определяет относительную нумерацию одинаковых процессов в двух различных группах. Например, если известны номера некоторых процессов в MPI_COMM_WORLD, то можно узнать их номера в подмножестве этой группы.

4. MPI_Group _compare(group1, group2, result)

group1 первая группа (дескриптор)

group2 вторая группа (дескриптор)

OUT result результат (целое)

Функция MPI_Group_compare сравнивает группы. Если члены группы и их порядок в обеих группах одинаковы, результат будет MPI_IDENT. Если члены группы одинаковы, но порядок различен, то результат будет MPI_SIMILAR. В остальных случаях - MPI_UNEQUAL.

Конструкторы групп

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

MPI не имеет механизма для формирования группы с нуля – группа может формироваться только на основе другой, предварительно определенной группы. Базовая группа, на основе которой определены все другие группы, является группой, связанной с коммуникатором MPI_COMM_WORLD (через функцию MPI_COMM_GROUP).

1. MPI_Comm_group(MPI_Comm comm, MPI_Group *group)

IN comm коммуникатор (дескриптор)

OUT group группа, соответствующая comm (дескриптор)

Функция MPI_Comm_group возвращает в group дескриптор группы из comm.

2. MPI_Group_union(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup)

IN group1 первая группа (дескриптор)

IN group2 вторая группа (дескриптор)

OUT newgroup объединенная группа (дескриптор)

Объединение (union) – содержит все элементы первой группы (group1) и следующие за ними элементы второй группы (group2), не входящие в первую группу.

3. MPI_Group_intersection(MPI_Group group1, MPI_Group group2,MPI_Group *newgroup)

IN group1 первая группа (дескриптор)

IN group2 вторая группа (дескриптор)

OUT newgroup группа, образованная пересечением (дескриптор)

Пересечение (intersect) – содержит все элементы group1, которые также находятся в group2 и упорядоченные, как в первой группе.

4. MPI_Group_difference(MPI_Group group1, MPI_Group group2,MPI_Group *newgroup)

IN group1 первая группа(дескриптор)

IN group2 вторая группа (дескриптор)

OUT newgroup исключенная группа (дескриптор)

Разность (difference) – содержит все элементы group1, которые не находятся в group2.

5. MPI_ Group_incl(group, n, ranks, newgroup)

IN group группа (дескриптор)

IN n количество элементов в массиве номеров (и размер newgroup, целое)

IN ranks номера процессов в group, перешедших в новую группу (массив целых)

OUT newgroup новая группа, полученная из прежней, упорядоченная согласно ranks

Cоздает новую группу, которая состоит из n процессов исходной группы group с номерами rank[0],..., rank[n-1]; процесс с номером i в newgroup есть процесс с номером ranks[i] в group. Если n = 0, то newgroup имеет значение MPI_GROUP_EMPTY.

6. MPI_Group _excl(group, n, ranks, newgroup)

IN group группа (дескриптор)

IN n количество элементов в массиве номеров (целое)

IN ranks массив целочисленных номеров в group, не входящих в newgroup

OUT newgroup новая группа, полученная из прежней, сохраняющая порядок, определенный group (дескриптор)

Создает новую группу, путем удаления из group процессов с номерами ranks[0] ,...ranks[n-1]. Если n = 0, то newgroup идентична group.

Деструктор групп - MPI_Group _free(group)

INOUT group идентификатор группы (дескриптор)

Функции доступа к коммуникаторам

Все следующие операции являются локальными.

1. MPI_Comm_size (comm, size)

IN comm коммуникатор (дескриптор)

OUT size количество процессов в группе comm (целое)

Эта функция указывает число процессов в коммуникаторе. Для MPI_COMM_WORLD она указывает общее количество доступных процессов.

2. MPI_Comm_rank(comm, rank)

IN comm коммуникатор (дескриптор)

OUT rank номер вызывающего процесса в группе comm (целое)

Функция MPI_Comm_rank возвращает номер процесса в частной группе коммуникатора. Ее удобно использовать cовместно с MPI_Comm_size.

3. MPI_Comm_compare(comm1, comm2, result)

IN comm1 первый коммуникатор (дескриптор)

IN comm2 второй коммуникатор (дескриптор)

OUT result результат (целое)

Функция MPI_Comm_compare сравнивает контексты коммуникаторов. Результат MPI_IDENT имеет место тогда и только тогда, когда comm1 и comm2 являются дескрипторами для одного и того же объекта. Результат MPI_CONGRUENT имеет место в том случае, если исходные группы идентичны по компонентам и нумерации; в этом случае коммуникаторы отличаются только контекстом. Результат MPI_SIMILAR имеет место, если члены группы обоих коммуникаторов являются одинаковыми, но порядок их нумерации различен. В противном случае выдается результат MPI_UNEQUAL.

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

Ниже перечисленные функции являются коллективными и вызываются всеми процессами в группе, связанной с comm. В MPI для создания нового коммуникатора необходим исходный коммуникатор. Основным коммуникатором для всех MPI коммуникаторов является коммуникатор MPI_COMM_WORLD.

1. MPI_Comm_dup(comm, newcomm)

IN comm коммуникатор (дескриптор)

OUT newcomm копия comm (дескриптор)

Функция MPI_Comm_dup дублирует существующий коммуникатор comm, возвращает в аргументе newcomm новый коммуникатор с той же группой.

2. MPI_Comm_create(comm, group, newcomm)

IN comm коммуникатор (дескриптор)

IN group группа, являющаяся подмножеством группы comm (дескриптор)

OUT newcomm новый коммуникатор (дескриптор)

Функция создает новый коммуникатор newcomm с коммуникационной группой, определенной аргументом group и новым контекстом. Из comm в newcomm не передается никакой кэшированной информации. Функция возвращает MPI_COMM_NULL для процессов, не входящих в group. Запрос неверен, если не все аргументы в group имеют одинаковое значение или если group не является подмножеством группы, связанной с comm. Заметим, что запрос должен быть выполнен всеми процессами в comm, даже если они не принадлежат новой группе.

3. MPI_Comm_split(comm, color, key, newcomm)

IN comm коммуникатор (дескриптор)

IN color управление созданием подмножества (целое)

IN key управление назначением номеров целое)

OUT newcomm новый коммуникатор (дескриптор)

Эта функция делит группу, связанную с comm, на непересекающиеся подгруппы по одной для каждого значения color. Каждая подгруппа содержит все процессы одного цвета. В пределах каждой под группы процессы пронумерованы в порядке, определенном значением аргумента key, со связями, разделенными согласно их номеру в старой группе.

Для каждой подгруппы создается новый коммуникатор и возвращается в аргументе newcomm. Процесс может иметь значение цвета MPI_UNDEFINED, тогда переменная newcomm возвращает значение MPI_COMM_NULL. Это коллективная операция, но каждому процессу разрешается иметь различные значения для color и key.

Обращение к MPI_Comm_create (сomm, group, newcomm) эквивалентно обращению к MPI_Comm_split (comm, color, key, newcomm), где все члены group имеют color =0 и key = номеру в group, и все процессы, которые не являются членами group, имеют color = MPI_UNDEFINED.

Функция MPI_Comm_split допускает более общее разделение группы на одну или несколько подгрупп с необязательным переупорядочением. Этот запрос используют только интра-коммуникаторы. Значение color должно быть неотрицательно.

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

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

Топология процессов

Топология – это логическая организация процессов в группе (внутри коммуникатора). Такую логическую организацию процессов имеет еще одно название - “виртуальная топология ”.

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

Общим видом топологии процессов является структура графа. Узлы такого графа представляют процессы, ребра соответствуют связям между процессами. Большая доля всех параллельных приложений использует топологию двумерных массивов. Эта структура полностью определяется количеством процессов по каждой координате (строки и столбцы). Координаты в декартовой системе нумеруются от 0. Например, соотношение между номером процесса в группе и координатами в решетке 2х2 для четырех процессов будет следующим:

coord (0,0): rank 0 сoord (0,1): rank 1

сoord (1,0): rank 2 сoord (1,1): rank 3

Конструктор декартовой топологии

1. MPI_Cart_create(MPI_Comm comm_old, int ndims, int *dims, int *periods, int reorder, MPI_Comm *comm_cart)

IN comm_old исходный коммуникатор (дескриптор)

IN ndims размерность создаваемой декартовой решетки (целое)

IN dims массив размера ndims, определяет количество процессов по каждой координате

IN periods массив логических элементов размера ndims, определяет, периодична (true) или нет (false) решетка в каждой размерности

IN reorder нумерация может быть сохранена (false) или переупорядочена (true)

OUT comm_cart коммуникатор новой декартовой топологии (дескриптор)

Функция MPI_Cart_create является коллективной, создает новый коммуникатор для описания декартовой топологии произвольной размерности. По каждой координате устанавливается признак периодичности. Если reorder = false, то номер каждого процесса в новой группе идентичен номеру в старой группе. Иначе функция может переупорядочить процессы (возможно, чтобы обеспечить хорошее наложение виртуальной топологии на физическую систему). Если полная размерность декартовой решетки меньше количества процессов в группе, то некоторые процессы возвращаются с результатом MPI_COMM_NULL. Вызов будет неверным, если он задает решетку большего размера, чем размер группы.

2. MPI_Dims_create(int nnodes, int ndims, int *dims)

IN nnodes количество узлов решетки (целое)

IN ndims число размерностей (координат) декартовой решетки (целое)

INOUT dims целочисленный массив размера ndims, определяет количество процессов по каждой координате

Задает количество процессов по каждой размерности (координате). Элементы массива dims описывают декартову решетку координат с числом размерностей ndims и общим количеством узлов nnodes.

Если размерность dims[i] - положительное число, функция не будет изменять число узлов в направлении i; будут изменены только те элементы, для которых dims[i] = 0. Отрицательные значения dims[i] неверны. Также будет неверно, если значение nnodes не кратно произведению dims[i]. Аргумент dims[i], установленный вызывающей программой, будет упорядочен по убыванию. Массив dims удобен для использования в функции MPI_Cart_create в качестве входного. Функция является локальной.

Пример: Массив dims перед вызовом вызов функции, dims после выхода из функции

(0,0) MPI_DIMS_CREATE(6, 2, dims) (3,2)

(0,0) MPI_DIMS_CREATE(7, 2, dims) (7,1)

Функции запроса декартовой топологии

1. MPI_Cartdim_get(MPI_Comm comm, int *ndims)

IN comm коммуникатор с декартовой топологией (дескриптор)

OUT ndims число размерностей в декартовой топологии системы (целое)

2. MPI_Cart_get(MPI_Comm comm, int maxdims, int *dims, int *periods, int *coords)

IN comm коммуникатор с декартовой топологией (дескриптор)

IN maxdims длина векторов dims, periods и coords (целое)

OUT dims число процессов по каждой декартовой размерности (целочисленный массив)

OUT periods периодичность ( true/ false) для каждой декартовой размерности (массив логических элементов)

OUT coords координаты вызываемых процессов в декартовой системе координат (целочисленный массив)

3. int MPI_Cart_rank(MPI_Comm comm, int *coords, int *rank)

IN comm коммуникатор с декартовой топологией (дескриптор)

IN coords целочисленный массив (размера ndims), описывающий декартовы координаты процесса

OUT rank номер указанного процесса (целое)

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

Для размерности i с periods(i) = true, если координата cords(i) выходит за границу диапазона – coords(i)<0 или coords(i)≥dims(i), она автоматически сдвигается назад к интервалу 0≤coords(i)<dims(i). Выход координат за пределы диапазона неверен для непериодических размерностей.

4. int MPI_Cart_coords(MPI_Comm comm, int rank, int maxdims, int *coords)

IN comm коммуникатор с декартовой топологией (дескриптор)

N rank номер процесса внутри группы comm (целое)

IN maxdims длина вектора coord (целое)

OUT coords целочисленный массив (размера ndims), содержащий декартовы координаты указанного процесса (целое)

MPI_Cart_coords используется для перевода номера процесса в группе в логическую координату.

5. Координаты декартова сдвига

Если топология это декартова структура, то MPI_SENDRECV часто используется вдоль какой-нибудь координаты, чтобы произвести сдвиг данных. На вход функции MPI_SENDRECV нужно подавать ранг источника для приёма и ранг приёмника для передачи. Вызов функции MPI_Cart_shift определяет эти аргументы.

MPI_Cart_shift(comm, direction, disp, rank_source, rank_dest)

IN comm - коммуникатор с декартовой структурой

IN direction - координата сдвига (integer)

IN disp - смещение (> 0: сдвиг вперёд, < 0: сдвиг назад) (integer)

OUT rank_source - ранг процесса источника (integer)

OUT rank_source - ранг процесса источника (integer)

direction определяет координату значение которой изменяется сдвигом. Координаты нумеруются от 0 до ndims-1, где ndims количество размерностей. В зависимости от периодичности декартовой группы по выделенной координате MPI_Cart_shift даёт циклический или конечный сдвиг. В случае конечного сдвига, в rank_source или rank_dest может быть возвращено значение MPI_PROC_NULL, которые отмечают, что источник или приёмник при сдвиге выходят за допустимые пределы.

Пример: На основе группы из 8 процессов создали декартову топологию: {2,4}, определим смещение disp =1

1. Решетка процессов не периодичная, direction=0 задает сдвиг по строкам (вдоль столбцов)

0 1 2 3 0 проц. получает от проц. -1 и отправляет проц. 4

1 проц. получает от проц. -1 и отправляет проц. 5 4 5 6 7

0 ----- -1 rank_source 4 rank_dest

1 ----- -1 5

2 ----- -1 6

3 ----- -1 7

4 ----- 0 -1

5 ----- 1 -1

6 ----- 2 -1

7 ----- 3 -1

2. Решетка процессов периодичная, direction=0 задает сдвиг по строкам (вдоль столбцов)

0 1 2 3 0 проц. получает от проц. 4 и отправляет проц. 4

1 проц. получает от проц. 5 и отправляет проц. 5 4 5 6 7

0 ----- 4 rank_source 4 rank_dest

1 ----- 5 5

2 ----- 6 6

3 ----- 7 7

4 ----- 0 0

5 ----- 1 1

6 ----- 2 2

7 ----- 3 3

3. Решетка процессов не периодичная, direction=1 задает сдвиг по столбцам (вдоль строк)

0 1 2 3 0 проц. получает от проц. -1 и отправляет проц. 1

1 проц. получает от проц. 0 и отправляет проц. 2 4 5 6 7

0 ----- -1 rank_source 1 rank_dest

1 ----- 0 2

2 ----- 1 3

3 ----- -1 7

4 ----- 2 -1

5 ----- -1 5

6 ----- 4 6

7 ----- 6 -1

4. Решетка процессов периодичная, direction=1 задает сдвиг по столбцам (вдоль строк)

0 1 2 3 0 проц. получает от проц. 3 и отправляет проц. 1

1 проц. получает от проц. 0 и отправляет проц. 2 4 5 6 7

0 ----- 3 rank_source 1 rank_dest

1 ----- 0 2

2 ----- 1 3

3 ----- 2 0

4 ----- 7 5

5 ----- 4 6

6 ----- 5 7

7 ----- 6 4

Конструктор универсальной (графовой) топологии

int MPI_Graph_create(MPI_Comm comm_old, int nnodes, int *index, int *edges, int reorder, MPI_Comm *comm_graph)

IN comm_old входной коммуникатор (дескриптор)

IN nnodes количество узлов графа (целое)

IN index массив целочисленных значений, описывающий степени вершин

IN edges массив целочисленных значений, описывающий ребра графа

IN reorder номера могут быть переупорядочены ( true) или нет ( false)

OUT comm_graph построенный коммуникатор с графовой топологией (дескриптор)

Функция MPI_Graph_create является коллективной, создает новый коммуникатор для описания графовой топологии. Если reorder = false, то номер каждого процесса в новой группе идентичен его номеру в старой группе. В противном случае функция может переупорядочить процессы. Если количество узлов графа nnodes меньше, чем размер группы коммуникатора, то процессы, не вошедшие в топологию возвращают значение MPI_COMM_NULL. Вызов будет неверным, если определен граф большего размера, чем размер группы исходного коммуникатора.

Структуру графа определяют три параметра: nnodes, index и edges. Nnodes – число узлов графа от 0 до nnodes-1. i-ый элемент массива index хранит общее число соседей первых i вершин графа. Списки соседей вершин 0, 1, ..., nnodes-1 хранятся в последовательности ячеек массива edges. Общее число элементов в index есть nnodes, а общее число элементов в edges равно числу ребер графа.

Функции запроса топологии графа

Следующие две функции: MPI_Graphdims_get и MPI_Graph_get через коммуникатор возвращают информацию о топологии, которая была создана функцией MPI_Graph_create.

1. int MPI_Graphdims_get(MPI_Comm comm, int *nnodes, int *nedges)

IN comm коммуникатор группы с графовой топологией (дескриптор)

OUT nnodes число вершин графа (целое, равно числу процессов в группе)

OUT nedges число ребер графа (целое)

Полученные значения массивов index и edges может быть использована для корректного определения размера векторов index и edges в функции MPI_Graph_get.

2. int MPI_Graph_get(MPI_Comm comm, int maxindex, int maxedges, int *index, int *edges)

IN comm коммуникатор с графовой топологией (дескриптор)

IN maxindex длина вектора index (целое)

IN maxedges длина вектора edges (целое)

OUT index целочисленный массив, содержащий структуру графа (подробнее в описании функции MPI_Graph_greate)

OUT edges целочисленный массив, содержащий структуру графа

3. int MPI_Graph_neighbors_count(MPI_Comm comm, int rank, int *nneighbors)

IN comm коммуникатор с графовой топологией (дескриптор)

IN rank номер процесса в группе comm (целое)

OUT nneighbors номера процессов, являющихся соседними указанному процессу (целочисленный массив)

4. int MPI_Graph_neighbors(MPI_Comm comm, int rank, int maxneighbors, int *neighbors)

IN comm коммуникатор с графовой топологией (дескриптор)

IN rank номер процесса в группе comm (целое)

IN maxneighbors размер массива neighbors (целое)

OUT neighbors номера процессов, соседних данному (целочисленный массив)

Функция запроса типа топологии

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

int MPI_Topo_test(MPI_Comm comm, int *status)

IN comm коммуникатор (дескриптор)

OUT status тип топологии коммуникатора comm (альтернатива)

MPI_Topo_test возвращает тип топологии, которую определяет коммуникатор.Выходное значение status имеет одно из следующих значений:

MPI_GRAPH топология графа

MPI_CART декартова топология

MPI_UNDEFINED топология не определена

  1. Технология программирования OpenMP. Основные конструкции OpenMP С реализации.

Технология программирования OpenMP. Основные конструкции OpenMP Си реализации.

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

Схематично процесс выполнения OpenMP программ можно представить следующим образом:

За основу берется текст последовательной программы, который разбивается на последовательные и параллельные области. В начальный момент времени порождается нить-мастер или "основная" нить, которая начинает выполнение программы.

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

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

Синтаксис OpenMP-директив:

#pragma omp имя_директивы [оператор [оператор] ...]

Все директивы OpenMP используют префикс #pragma omp.

  1. Директива parallel - определяет параллельную секцию

#pragma omp parallel [оператор [оператор] ...]

{

< код параллельной секции >

}

Участок кода, стоящий в фигурных скобках, будет выполняться параллельно. Для выполнения кода параллельной секции, порождается OMP_NUM_THREADS-1 нитей, где OMP_NUM_THREADS - это переменная окружения, значение которой задает пользователь до начала выполнения программы. Все нити исполняют код, заключенный между фигурными скобками. После выхода из параллельной секции автоматически происходит неявная синхронизация порожденных нитей. Когда все порожденные нити доходят до точки выхода из параллельной секции, нить-мастер продолжает выполнение последующей части программы, остальные нити уничтожаются.

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

На время выполнения параллельной области, изменить количество порожденных нитей нельзя, однако если пользователь установит значение переменной OMP_DYNAMIC=1, то с помощью функции OMP_SET_NUM_THREADS при входе в следующую параллельную секцию можно изменить значение переменной OMP_NUM_THREADS, а значит и число порождаемых нитей. Значение переменной OMP_DYNAMIC устанавливается функцией OMP_SET_DYNAMIC.

(Клауза) оператор - один из следующих:

  1. if(скалярное_выражение)

  2. private(список)

  3. firstprivate(список)

  4. Default(shared | none)

  5. shared(список)

  6. copyin(список)

  7. reduction(оператор: список)

1. Если значение выражения в if истинно, область выполняется параллельно, равно нулю (условие не выполняется), то область parallel выполняется последовательно.

2. private(список) - объявляет перечисленные в списке переменные локальными в каждой нити группы. При входе в параллельную область для каждой локальной переменной в каждой нити создается отдельный экземпляр переменной, доступный только этой нити, который не имеет никакой связи с оригинальной переменной вне параллельной области, т.е. при входе и выходе из параллельной области значение переменной не определено, поэтому при входе требуется инициализация. Изменение нитью значения своей локальной переменной, не влияет на изменение значения этой же локальной переменной в других нитях.

3. firstprivate(список) - объявляет перечисленные в списке переменные локальными в каждой нити группы. Локальные копии переменных при входе в параллельную область инициализируются значением оригинальной переменной, при выходе не определены.

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

Пример:

………….. // код программы

int i, j;

i =1; j =2;

#pragma omp parallel private(i) firstprivate(j)

{

i =3; // i - private переменная, значение при входе в параллельную область не

// определено, поэтому выполняется инициализация

j =i+j; // j - firstprivate переменная, значение при входе в параллельную область

// определено и равно 2

}

cout<<”i=”<<i<<” j=”<<j<<endl;

………….. // код программы

Переменные i и j при выходе из параллельной области сохранят свои оригинальные значения, т.е. значения до входа в параллельную область i=1, j=2.

Пример:

#pragma omp parallel

{

#pragma omp for lastprivate(i) //значение i не определено при входе,

for (i=0; i<10; i++) // поэтому выполняется инициализация

a[i] = b[i] + 1;

}

a[i-1]=b[i-1];

В значение i в конце параллельной области будет равно 10, как в случае последовательного выполнения цикла.

5. default(shared | none) позволяет установить область видимости переменных. По умолчанию устанавливается shared.

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

Переменные могут быть исключены из умалчиваемого определения путем использования директив private, firstprivate, lastprivate, reduction и shared, например:

#pragma omp parallel for default(shared) firstprivate(i) private(x) private(r) lastprivate(i)

6. shared(список) - объявляет перечисленные в списке переменные общими для всех нитей группы. Т.е. каждая общая переменная существует в одном экземпляре для всей программы и доступна для каждой нити под одним и тем же именем.

Пример: Каждая нить в параллельной области принимает решение, основываясь на номере нити, какую часть массива x обрабатывать.

#pragma omp parallel shared(x, npoints) private(iam, np, ipoints)

{

iam = omp_get_thread_num();

np = omp_get_num_threads();

ipoints = npoints / np;

subdomain(x, iam, ipoints); //функция обработки массива x

}

7. copyin(список) - выполняет присвоение одного и того же значения переменной, определенной в threadprivate в каждую нить параллельной области. Т.е. для каждой переменной, указанной в операторе copyin, значение переменной из основной нити при воде в параллельную область копируется в приватные копии переменных каждой нити. Ограничения, накладываемые на использование клаузы copyin следующее - переменные, указанные в copyin, должны быть переменными threadprivate.

8. reduction(операция: список) - выполняет редукцию переменных, которые перечислены в списке, с оператором op. Список переменных перечисляется через запятую.

Допустимые операции: +, -, *, /, &, |, ||, &&

Ограничения, накладываемые на использование директивы reduction:

1. Тип переменных должен быть приемлемым для операции редукции, не разрешены типы указателя и ссылки.

2.Переменные, которые указаны в клаузе reduction, не должны быть константами.

3.Переменные, которые указаны в клаузе reduction, должны быть разделяемыми в общем контексте программы.

Пример: В данном примере каждая нить имеет свою копию переменной редукции: a и y. После окончания работы выполняется операция редуцирования (в данном случае сложение) всех локальных копий переменных.

#pragma omp parallel for reduction(+: a, y)

for (i=0; i<n; i++) {

a += b[i];

y = sum(y, c[i]);

}

Переменные в операторе reduction должны быть shared в параллельной секции.

.

Пример:

/*ОШИБКА – private переменная y не может быть определена в операторе reduction*/

……. // код программы

#pragma omp parallel for private(y) reduction(+: y)

for (i=0; i<n; i++)

y += b[i];

……. // код программы

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

Пример:

/*ОШИБКА 2 – переменная x не может быть определена одновременно в операторах shared и reduction */

#pragma omp parallel for shared(x) reduction(+: x)

Пример. Правильное использование reduction

Программа вычисляет и записывает сумму значений элементов массива a в переменную x и сумму значений элементов массива b в переменную y

#include<stdio.h>

#include<omp.h>