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

[ООП] / Лекции / Lecture_6

.pdf
Скачиваний:
33
Добавлен:
07.02.2016
Размер:
196.22 Кб
Скачать

Лекция 6

Обмен сообщениями в MPI

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

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

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

Мы уже рассматривали функцию передачи MPI_Send(). Кроме нее существуют еще функции, которые можно условно обозначить так:

MPI_[I][R,S,B]Send();

При этом в языке С только первый символ после префикса MPI_ записывается с помощью символа верхнего регистра, например MPI_Irsend(). Символы в квадратных скобках обозначают:

I – Immediate, неблокирующий обмен;

R – Ready, передача "по готовности";

S – Synchronous, синхронная передача;

B – Buffering, буферизованная передача.

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

2

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

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

Буферизованная передача считается законченной, если данные переданы в системный буфер.

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

 

 

 

 

 

 

a)

 

б)

Рис.6.1 – Блокирующий (а) и неблокирующий (б) обмены сообщениями

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

Функции управления дополнительным буфером для буферизованного обмена:

MPI_Buffer_attach(void *buf,int size);

MPI_Buffer_detach(void *buf,int size);

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

Высокопроизводительные вычислительные системы и параллельное программирование. Кудерметов Р.К.

3

MPI_[I]Probe(int source,int tag,MPI_Comm comm, MPI_Status *status);

Функция блокировки процесса до завершения приема или передачи в неблокирующем режиме:

MPI_Wait(MPI_Request *request,MPI_Status *status);

где параметр request – идентификатор операции обмена, специальная структура, заполняемая неблокирующими операциями обмена.

Функция неблокирующей проверки завершения приема или передачи:

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

где параметр flag устанавливается в единицу, если операция, request заданная идентификатором операции обмена, выполнена.

Имеется функция совместного приема-передачи:

MPI_Sendresv(void *sbuf,int scount,MPI_Datatype stype,int dest, int stag,void rbuf, int rcount, MPI_Datatype,int source,int rtag, MPI_Comm comm, MPI_Status *status);

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

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

ВMPI определены следующие виды операций коллективного обмена:

широковещательная рассылка;

сбор данных;

распределение данных;

операции приведения и сканирования.

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

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

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

MPI_Bcast(void *buf,int count,MPI_Datatype type,int root,MPI_Comm comm);

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

Высокопроизводительные вычислительные системы и параллельное программирование. Кудерметов Р.К.

4

Рис. 6.2 – Работа функции MPI_Bcast()

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

MPI_Scatter(void *sbuf,int scount,MPI_Datatype stype,void *rbuf,int rcount,MPI_Datatype rtype,int root,MPI_Comm comm);

MPI_Gather(void *sbuf,int scount,MPI_Datatype stype,void *rbuf,int rcount,MPI_Datatype rtype,int root,MPI_Comm comm);

Функция распределения MPI_Scatter() рассылает равные части буфера sbuf процесса root всем процессам. При этом содержимое буфера процесса root разбивается на равные части, каждая из которых состоит из scount элементов. Первая часть остается в отправляется в нулевой процесс, вторая в первый и т.д. Аргументы, относящиеся к передающей части списка аргументов функции, имеют силу только для процесса root. На рис. 6.3. показано действие функции MPI_Scatter().

Рис.6.3 – Работа функции MPI_Scatter()

Функция MPI_Gather() имеет обратное действие по сравнению с функцией MPI_Scatter(), т.е. она принимает и располагает по порядку принятые из передающих процессов данные (рис.6.4). При этом параметры приема действительны только для принимающего процесса.

Высокопроизводительные вычислительные системы и параллельное программирование. Кудерметов Р.К.

5

Рис. 6.4 – Работа функции MPI_Gather()

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

MPI_Reduce(void *buf, void *result, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm);

Здесь op - операция приведения, которая может иметь предопределенные значения, такие как MPI_SUM, MPI_PROD, MPI_LAND, MPI_BAND, MPI_MAX и т.п. Всего 12 операций. Кроме того можно определить собственные операции приведения с помощью функции MPI_Op_create(). Работа функции понятна из рис. 6.5.

Рис. 6.5 – Работа функции MPI_Reduce()

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

Рис.6.6 – Работа функции MPI_Scan()

Высокопроизводительные вычислительные системы и параллельное программирование. Кудерметов Р.К.

6

Синтаксис функции имеет вид:

MPI_Scan(void *sbuf,void rbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm);

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

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

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

{

float *x,*y;

float sum=0.0,total=0.0; int i,j,myrank,P,n,M; int err;

MPI_Init(&argc,&argv);

MPI_Comm_size(MPI_COMM_WORLD,&P);

MPI_Comm_rank(MPI_COMM_WORLD,&myrank);

if(myrank==0)

{

puts("Enter size of array"); scanf("%d",&n);

x=(float *)malloc(n*sizeof(float)); if(x==NULL) MPI_Abort(MPI_COMM_WORLD,err);

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

x[i]=(float)i;//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 M=n/P;

}

MPI_Bcast(&M,1,MPI_INT,0,MPI_COMM_WORLD); if(myrank!=0)

{

x=(float *)malloc(M*sizeof(float)); if(x==NULL) MPI_Abort(MPI_COMM_WORLD,err);

}

y=(float *)calloc(M,sizeof(float)); if(y==NULL) MPI_Abort(MPI_COMM_WORLD,err);

MPI_Scatter(x,M,MPI_FLOAT,x,M,MPI_FLOAT,0,MPI_COMM_WORLD);

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

sum+=x[i]; MPI_Barrier(MPI_COMM_WORLD);

MPI_Reduce(&sum,&total,1,MPI_FLOAT,MPI_SUM,0,MPI_COMM_WORLD); MPI_Reduce(x,y,M,MPI_FLOAT,MPI_SUM,0,MPI_COMM_WORLD); if(myrank==0)

{

printf("Reduce total=%.0f\n",total); //Reduce 120 printf("Reduce: ");

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

printf("y[%d]=%.0f ",i,y[i]); //Reduce 24 28 32 36 printf("\n");

}

MPI_Gather(&sum,1,MPI_FLOAT,y,1,MPI_FLOAT,0,MPI_COMM_WORLD);

Высокопроизводительные вычислительные системы и параллельное программирование. Кудерметов Р.К.

7

if(myrank==0)

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

printf("Gather: ");

 

 

 

 

 

 

 

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

",i,y[i]); //Gather

6

22

38

54

 

printf("y[%d]=%.0f

 

printf("\n");

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

MPI_Scan(x,y,M,MPI_FLOAT,MPI_SUM,MPI_COMM_WORLD);

 

 

 

 

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

 

 

 

 

 

 

 

if(myrank==i)

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

printf("Scan:\n");

 

 

 

 

 

 

 

printf("#%d: ",myrank);

 

 

 

 

 

 

for(j=0;j<M;j++)

",j,y[j]);//

Scan

#0

0

1

2

3

printf("y[%d]=%.0f

printf("\n");

//

 

#1

4

6

8

10

}

//

 

#2

12

15

18

21

free(y);

//

 

#3

24

28

32

36

free(x);

 

 

 

 

 

 

 

MPI_Finalize(); return 0;

}

Высокопроизводительные вычислительные системы и параллельное программирование. Кудерметов Р.К.

Соседние файлы в папке Лекции