Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

u_course

.pdf
Скачиваний:
39
Добавлен:
04.06.2015
Размер:
1.87 Mб
Скачать

Средства разработки параллельных программм

191

все описатели созданных в пункте 2 групп очищаются вызовами

функции MPI_Group_free(&group).

Заметим, что функция MPI_Comm_create() является коллективной, то есть должна быть вызвана всеми процессами, входящими в область связи с коммуникатором comm.

Существует также два способа создания коммуникатора непосредственно из существующих.

int MPI_Comm_dup(MPI_Comm comm, MPI_Comm *comm_new );

Функция копирует существующий коммуникатор comm в новый

comm_new.

int MPI_Comm_split(MPI_Comm comm, int split, int rank, MPI_Comm *comm_new );

Функция расщепляет группу, связанную с коммуникатором comm на непересекающиеся подгруппы. Количество подгрупп определяется количеством различных значений параметра split. Каждая подгруппа содержит все процессы, у которых значение split в вызове оказалось одинаковым. Внутри каждой подгруппы процессы ранжированы в порядке, определенном значением аргумента rank, со связями, отличными от их рангов в старой группе. Новая область связи создается для каждой подгруппы, и имя коммуникатора, возвращается в comm_new. Процесс может определять значение split равным MPIUNDEFINED, для процессов, не принадлежащих любой новой группе, тогда

в comm_new возвращается MPI_COMM_NULL.

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

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

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

Средства разработки параллельных программм

192

Имеется два типа коммуникаторов.

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

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

скоммуникаторами этого типа.

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

ОБРАМЛЯЮЩИЕИИНФОРМАЦИОННЫЕФУНКЦИИ

int MPI_Init (int* argc, char** argv);

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

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

int MPI_Finalize(void);

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

int MPI_Comm_size(MPI_Comm comm, int* size);

Функция определяет в параметр size общее число параллельных процессов в области связи, ассоциированной с коммуникатором comm.

int MPI_Comm_rank(MPI_comm comm, int* rank);

Функция возвращает в номер rank процесса его вызвавшего в области связи, ассоциированной с коммуникатором comm. Значение, возвращаемое по адресу &rank, лежит в диапазоне от 0 до size-1, где size – размер группы, связанной с коммуникатором comm.

Средства разработки параллельных программм

193

double MPI_Wtime(void);

Функция возвращает астрономическое время в секундах (вещественное число), прошедшее с некоторого момента в прошлом. Гарантируется, что этот момент не будет изменен за время существования процесса.

Ниже приведен пример простейшей MPI-программы.

#include <mpi.h> #include <stdio.h>

int main( int argc, char **argv )

{

int size, rank, i;

MPI_Init( &argc, &argv ); // инициализация MPI-библиотеки

MPI_Comm_size( MPI_COMM_WORLD, &size );

// определение количества

// запущенных ветвей

MPI_Comm_rank( MPI_COMM_WORLD, &rank );

// определение своего ранга

//ветвь с нулевым рангом сообщает количество запущенных процессов if( rank==0 ) printf("Total processes count = %d\n", size );

//все ветви сообщают свой ранг

printf("Hello! My rank in MPI_COMM_WORLD = %d\n", rank );

// Точка синхронизации

MPI_Barrier( MPI_COMM_WORLD );

//ветвь с рангом 0 сообщает аргументы командной строки,

//которая может содержать параметры, добавляемые загрузчиком MPIRUN if( rank == 0 )

for( puts("Command line of process 0:"), i=0; i<argc; i++ ) printf( "%d: \"%s\"\n", i, argv[i] );

MPI_Finalize(); // закрытие MPI-библиотеки return 0;

}

ПАРНЫЕФУНКЦИИПРИЕМА/ ПЕРЕДАЧИСООБЩЕНИЙ

Основные особенности, связанные с использованием функций двухточечного обмена, обсуждались в главе 6 (см., в частности, табл. 6.1). Ниже приведен синтаксис использования этих функций с привязкой к языку C.

Блокирующие операции обмена

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

Функция осуществляет посылку в автоматическом режиме сообщения с идентификатором msgtag, состоящего из count элементов типа datatype, процессу с номером dest. Все элементы сообщения расположены подряд в буфере buf. Значение count может быть нулем. Тип передаваемых элементов data-

Средства разработки параллельных программм

194

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

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

int MPI_Ssend(void* buf, int count, MPI_Datatype datatype,

int dest, int msgtag, MPI_Comm comm);

Функция осуществляет посылку сообщения в синхронном блокирующем режиме. Ее параметры совпадают с параметрами функции MPI_Send().

int MPI_Bsend(void* buf, int count, MPI_Datatype datatype,

int dest, int msgtag, MPI_Comm comm);

Функция осуществляет буферизированную посылку сообщения в блокирующем режиме. Ее параметры совпадают с параметрами функции MPI_Send(). Перед выполнением функции необходимо определить и присоединить буфер достаточного размера функцией MPI_Buffer_attach(). После выхода из функции следует освободить этот буфер функцией MPI_Buffer_attach(). Отметим, что буфер должен быть описан как массив размером не менее count+MPI_BSEND_OVERHEAD, его не следует использовать для других целей, например, в качестве буфера в самой функции MPI_Bsend().

int MPI_Buffer_attach(void* buffer, int size);

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

int MPI_Buffer_detach(void* buffer, int size);

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

int MPI_Rsend(void* buf, int count, MPI_Datatype datatype,

int dest, int msgtag, MPI_Comm comm);

Функция осуществляет посылку сообщения в блокирующем режиме по готовности. Ее параметры совпадают с параметрами функции MPI_Send().

int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int msgtag,

MPI_comm comm, MPI_Status *status);

Процедура осуществляет прием сообщения с идентификатором msgtag от процесса source с блокировкой. Число элементов в принимаемом сообщении не должно превосходить значения count. Если число принятых элементов

Средства разработки параллельных программм

195

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

В качестве номера процесса-отправителя можно указать предопределенную константу MPI_ANY_SOURCE – «можно получить сообщение от любого процесса». В качестве идентификатора принимаемого сообщения можно указать константу MPI_ANY_TAG – «можно получить сообщение с любым идентификатором».

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

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

#include <mpi.h> #include <stdio.h>

#define leng 20 //length of WR-string

int main( int argc, char **argv )

{

double t,t2; int i,rank,size;

char WR[leng]; MPI_Status status;

MPI_Init( &argc, &argv );

MPI_Comm_rank( MPI_COMM_WORLD, &rank ); MPI_Comm_size(MPI_COMM_WORLD,&size);

sprintf(WR,"Hello from %d",rank); // формирование сообщения

t=MPI_Wtime(); // фиксация времени «начала посылки», // локально для каждого процесса

MPI_Send(WR,leng,MPI_CHAR,size-(rank+1),rank,MPI_COMM_WORLD); MPI_Recv(WR,leng,MPI_CHAR,rank,size-(rank+1),MPI_COMM_WORLD,&status); t2=MPI_Wtime(); // фиксация времени «окончания приема»,

// локально для каждого процесса

Средства разработки параллельных программм

196

printf("\n From processor %d\n WR=%s\n",rank,WR);

// вывод сообщения

printf("\n From processor %d\n Time=%le\n",rank,(t2-t)/100); // вывод времени, // затраченного на обмен данным процессором

MPI_Finalize(); return 0;

}

Получение информации о сообщении

int MPI_Get_Count(MPI_Status *status, MPI_Datatype datatype, int *count);

Функция определяет число уже принятых (если обращение к функции произошло после обращения к MPI_Recv()) или принимаемых (если обращение к функции произошло после обращения к MPI_Probe() или MPI_IProbe()) элементов типа datatype сообщения со статусом status.

int MPI_Probe( int source, int msgtag, MPI_Comm comm, MPI_Status *status);

Функция обеспечивает получение информации о структуре ожидаемого сообщения с блокировкой. Возврата из подпрограммы не произойдет до тех пор, пока сообщение в области взаимодействия, связанной с коммуникатором comm с подходящим идентификатором msgtag и номером процессаотправителя source не будет доступно для получения. Атрибуты доступного сообщения можно определить с помощью параметра status. Следует обратить внимание, что подпрограмма определяет только факт прихода сообщения, но реально его не принимает. В качестве идентификатора сообщения и номера процесса-отправителя могут быть указаны предопределенные константы

MPI_ANY_TAG и MPI_ANY_SOURCE соответственно.

int MPI_Iprobe( int source, int msgtag, MPI_Comm comm,

int *flag, MPI_Status *status);

Функция обеспечивает получение информации о структуре ожидаемого сообщения без блокировки. В параметр flag возвращается знаачение 1, если сообщение в области взаимодействия, связанной с коммуникатором comm с подходящим идентификатором msgtag и номером процесса-отправителя source уже может быть принято (в этом случае ее действие полностью аналогично MPI_Probe()), и значение 0, если сообщения с указанными атрибутами еще нет.

Неблокирующие операции обмена

int MPI_Isend(void *buf, int count, MPI_Datatype datatype, int dest, int msgtag,

MPI_Comm comm, MPI_Request *request);

Функция осуществляет посылку сообщения в неблокирующем режиме. Ее параметры совпадают с параметрами функции MPI_Send(). Последний параметр request является выходным и используется функциями семейств MPI_Wait*() и MPI_Test*() для проверки окончания выполнения операции.

Средства разработки параллельных программм

197

int MPI_Issend(void *buf, int count, MPI_Datatype datatype, int dest, int msgtag,

MPI_Comm comm, MPI_Request *request);

Функция осуществляет посылку сообщения в неблокирующем синхронном режиме. Ее параметры совпадают с параметрами функции

MPI_Isend().

int MPI_Ibsend(void *buf, int count, MPI_Datatype datatype, int dest, int msgtag,

MPI_Comm comm, MPI_Request *request);

Функция осуществляет буферизированную посылку сообщения в неблокирующем режиме. Ее параметры совпадают с параметрами функции MPI_Isend(). Перед выполнением функции необходимо определить и присоединить буфер достаточного размера функцией MPI_Buffer_attach(). После выхода из функции следует освободить этот буфер функцией MPI_Buffer_attach().

int MPI_Irsend(void *buf, int count, MPI_Datatype datatype, int dest, int msgtag,

MPI_Comm comm, MPI_Request *request);

Функция осуществляет посылку сообщения в неблокирующем режиме по готовности. Ее параметры совпадают с параметрами функции MPI_Isend().

int MPI_IRecv(void *buf, int count, MPI_Datatype datatype, int source, int msgtag,

MPI_comm comm, MPI_Request *request);

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

функций семейств MPI_Wait*() и MPI_Test*().

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

Проверка выполнения обмена

int MPI_Wait(MPI_Request *request, MPI_Status *status);

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

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

Средства разработки параллельных программм

198

MPI_Waitsome() – ожидает завершения нескольких указанных в параметрах операций обмена, при этом если завершено несколько операций, то возвращается информация обо всех.

int MPI_Test( MPI_Request *request, int *flag, MPI_Status *status);

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

Существует еще несколько функций этого семейства. MPI_Testall() – осуществляет проверку завершения всех из указанных в параметрах операций обмена. MPI_Testany() – осуществляет проверку завершения какой-либо одной из нескольких указанных в параметрах операций обмена, при этом если завершено несколько операций, то возвращается информация об одной случайно выбранной. MPI_Testsome() – осуществляет проверку завершения нескольких указанных в параметрах операций обмена, при этом если завершено несколько операций, то возвращается информация обо всех.

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

#include <mpi.h> #include <stdio.h>

#define leng 20 // размер буфера для сообщения WR-string

int main( int argc, char **argv )

{

double t,t2; int i,rank,size;

char WR[leng],WR1[leng]; // буферы для сообщения

MPI_Status status;

MPI_Request req1,req2; // статус сообщения для проверки MPI_Waite()-функций

MPI_Init( &argc, &argv );

MPI_Comm_rank( MPI_COMM_WORLD, &rank ); MPI_Comm_size(MPI_COMM_WORLD,&size);

sprintf(WR,"Hello from %d",rank); // формирование сообщения

Средства разработки параллельных программм

199

t=MPI_Wtime(); // фиксация времени «начала посылки», // локально для каждого процесса

MPI_Irecv(WR1,leng,MPI_CHAR,rank, size-(rank+1),MPI_COMM_WORLD,&req2);

MPI_Isend(WR,leng,MPI_CHAR,size-(rank+1),rank,MPI_COMM_WORLD,&req1); MPI_Wait(&req1,&status);

MPI_Wait(&req2,&status);

t2=MPI_Wtime(); // фиксация времени «окончания приема», // локально для каждого процесса

printf("\n From processor %d\n WR=%s\n",rank,WR); // вывод сообщения printf("\n From processor %d\n Time=%le\n",rank,(t2-t)/100); // вывод времени,

// затраченного на обмен данным процессором

MPI_Finalize(); return 0;

}

Следующий пример демонстрирует корректный буферизованный об-

мен.

#include <mpi.h> #include <stdio.h>

#define BUFSIZE 2000 // размер буфера для буферизованного обмена #define leng 20 // размер буфера для сообщения WR-string

int main( int argc, char **argv )

{

double t,t2;

int i,zero,rank,size;

char WR[leng],WR1[leng]; // буферы для сообщения char *buffer; // буфер для буферизованного обмена

MPI_Status status;

MPI_Request req1,req2; // статус сообщения для проверки MPI_Waite()-функций

MPI_Init( &argc, &argv );

MPI_Comm_rank( MPI_COMM_WORLD, &rank ); MPI_Comm_size(MPI_COMM_WORLD,&size);

buffer=(char*)malloc(BUFSIZE); MPI_Buffer_attach(buffer,BUFSIZE); // выделение буфера

sprintf(WR,"Hello from %d",rank);

t=MPI_Wtime(); MPI_Ibsend(WR,leng,MPI_CHAR,size-(rank+1),rank,MPI_COMM_WORLD,&req1); MPI_Irecv(WR1,leng,MPI_CHAR, rank,

size-(rank+1),MPI_COMM_WORLD,&req2); MPI_Wait(&req1,&status);

MPI_Wait(&req2,&status); t2=MPI_Wtime();

printf("\n From processor %d\n WR=%s\n",rank,WR1); printf("\n From processor %d\n Time=%le\n\n",rank,(t2-t)/100); MPI_Buffer_detach(&buffer,&zero); // освобождение буфера

Средства разработки параллельных программм

200

MPI_Finalize(); return 0;

}

Отложенные обмены

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

int MPI_Send_Init(void *buf, int count, MPI_Datatype datatype, int dest,

int msgtag, MPI_Comm comm, MPI_Request *request);

Функция обеспечивает формирование запроса на выполнение пересылки данных. Все параметры точно такие же, как и у функции MPI_Isend(), однако в отличие от нее пересылка не начинается до вызова функции MPI_Startall().

int MPI_Recv_Init(void *buf, int count, MPI_Datatype datatype, int source,

int msgtag, MPI_Comm comm, MPI_Request *request);

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

MPI_Startall().

int MPI_Start_All (int count, MPI_Request *requests);

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

MPI_Wait() или MPI_Test().

Совмещение операций посылки и передачи

int MPI_Sendrecv(void *sbuf, int scount, MPI_Datatype stype, int dest, int stag, void *rbuf, int rcount,

MPI_Datatype rtype, int source, MPI_DAtatype rtag,

MPI_Comm comm, MPI_Status *status);

Функция объединяет в едином запросе посылку и прием сообщений. Принимающий и отправляющий процессы могут являться одним и тем же процессом. Сообщение, отправленное операцией MPI_Sendrecv(), может быть принято обычным образом, и точно также операция MPI_Sendrecv() может

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]