Главная страница

Программирование для многопроцессорных систем в стандарте MPI - Шпаковский Г.И., Серикова Н.В.. Программирование для многопроцессорных систем в стандарте MPI -. Организация вычислений в многопроцессорных системах


Скачать 1.61 Mb.
НазваниеОрганизация вычислений в многопроцессорных системах
АнкорПрограммирование для многопроцессорных систем в стандарте MPI - Шпаковский Г.И., Серикова Н.В..pdf
Дата15.03.2018
Размер1.61 Mb.
Формат файлаpdf
Имя файлаПрограммирование для многопроцессорных систем в стандарте MPI - .pdf
ТипКонтрольные вопросы
#16702
КатегорияИнформатика. Вычислительная техника
страница11 из 26
1   ...   7   8   9   10   11   12   13   14   ...   26
Пример 4.9.
Аналогичен примеру 4.7 на передающей стороне, но на приемной стороне устанавливается страйд между принимаемыми бло- ками, изменяющийся от блока к блоку (рис. 4.6).
Рис. 4.6. Корневой процесс собирает множество 100-i целых чисел из столбца i массива 100
×150, и каждое множество размещает с переменным страйдом [i]
MPI_Comm comm; int gsize,sendarray[100][150],*sptr; int root, *rbuf, *stride, myrank, bufsize;
MPI_Datatype stype; int *displs,i,*rcounts,offset;
MPI_Comm_size( comm, &gsize);
MPI_Comm_rank( comm, &myrank ); stride = (int *)malloc(gsize*sizeof(int));
/* сначала устанавливаются вектора displs и rcounts */ displs = (int *)malloc(gsize*sizeof(int)); rcounts = (int *)malloc(gsize*sizeof(int)); offset = 0; for (i=0; i{ displs[i] = offset; offset += stride[i]; rcounts[i] = 100-i;
}
/* теперь легко получается требуемый размер буфера для rbuf */ bufsize = displs[gsize-1]+rcounts[gsize-1]; rbuf = (int *)malloc(bufsize*sizeof(int));
/* создается тип данных для посылаемого столбца */
MPI_Type_vector( 100-myrank, 1, 150, MPI_INT, &stype);
MPI_Type_commit( &stype ); sptr = &sendarray[0][myrank];
MPI_Gatherv(sptr,1,stype,rbuf, rcounts, displs, MPI_INT,root,comm);
150 150 150 100 100 100 100 100 100 99 98 100
Все процессы
Корневой процесс rbuf
Страйд [i]

119
Пример 4.10.
В этом примере процесс
i
посылает
num
чисел типа
int
из
i
-го столбца массива 100× 150 чисел типа
int
. Усложнение состоит в том, что различные значения
num
неизвестны корневому процессу, поэтому требуется выполнить отдельную операцию
gather
, чтобы найти их. Данные на приемной стороне размещаются непрерывно.
MPI_Comm comm; int gsize,sendarray[100][150],*sptr,root, *rbuf, stride, myrank, disp[2], blocklen[2];
MPI_Datatype stype,types[2]; int *displs,i,*rcounts,num;
MPI_Comm_size( comm, &gsize);
MPI_Comm_rank( comm, &myrank );
/* сначала собираются nums для root */ rcounts = (int *)malloc(gsize*sizeof(int));
MPI_Gather( &num, 1, MPI_INT, rcounts, 1, MPI_INT, root, comm);
/* root теперь имеет правильные rcounts, это позволяет установить displs[] так, чтобы данные на приемной стороне размещались непрерывно */ displs = (int *)malloc(gsize*sizeof(int)); displs[0] = 0; for (i=1; i /* создается буфер получения */ rbuf=(int*)malloc(gsize*(displs[gsize-1]+rcounts[gsize-1])*sizeof(int));
/* создается тип данных для одного int с расширением на полную строку */ disp[0] = 0; disp[1] = 150*sizeof(int); type[0] = MPI_INT; type[1] = MPI_UB; blocklen[0] = 1; blocklen[1] = 1;
MPI_Type_struct( 2, blocklen, disp, type, &stype );
MPI_Type_commit( &stype ); sptr = &sendarray[0][myrank];
MPI_Gatherv(sptr,num,stype,rbuf, rcounts, displs, MPI_INT,root, comm);
4.2.4. Рассылка
Операция
MPI_SCATTER
обратна операции
MPI_GATHER
. Ре- зультат ее выполнения таков, как если бы корневой процесс выполнил
n
операций посылки:
MPI_Send(senbuf + i * extent(sendtype), sendcount, sendtype, i, …),
и каждый процесс выполнит приtм:
MPI_Recv(recvbuf, recvcount, recvtype, i, …).

120
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 коммуникатор (дескриптор) int MPI_Scatter (void* sendbuf, int sendcount, MPI_Datatype sendtype, void* recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm)
MPI_SCATTER (SENDBUF, SENDCOUNT, SENDTYPE, RECVBUF,
RECVCOUNT, RECVTYPE, ROOT, COMM, IERROR)
SENDBUF(*), RECVBUF(*)
INTEGER SENDCOUNT, SENDTYPE, RECVCOUNT, RECVTYPE, ROOT,
COMM, IERROR void MPI::Intracomm::Scatter(const void* sendbuf, int sendcount, const Datatype& sendtype, void* recvbuf, int recvcount, const Datatype& recvtype, int root) const
Буфер отправки игнорируется всеми некорневыми процессами.
Сигнатура типа, связанная с
sendcount, sendtype
, должна быть оди- наковой для корневого процесса и всех других процессов. Это требу- ет, чтобы количество посланных и полученных данных совпадало по- парно для корневого и каждого другого процессов. Корневой процесс использует все аргументы функции, а другие процессы используют только аргументы
recvbuf, recvcount, recvtype, root, comm
. Аргумен- ты
root
и
comm
должны быть одинаковыми во всех процессах. Опи- санные в функции
MPI_SCATTER
количества и типы данных не должны являться причиной того, чтобы любая ячейка корневого про- цесса записывалfсь бы более одного раза. Такой вызов является не- верным.
Операция
MPI_SCATERV
обратна операции
MPI_GATHERV
Аргумент
sendbuf
игнорируется во всех некорневых процессах. Сиг- натура типа, связанная с
sendcount [i], sendtype
в главном процессе, должна быть той же, что и сигнатура, связанная с
recvcount, recvtype
в процессе
i
. Это требует, чтобы количество посланных и полученных данных совпадало попарно для корневого и каждого другого процес-

121
сов. Разрешается различие в картах типов между отправителями и по- лучателями. Корневой процесс использует все аргументы функции, а другие процессы используют только аргументы
recvbuf, recvcount,
recvtype, root, comm
. Аргументы
root
и
comm
должны быть одинако- выми во всех процессах. Описанные в функции
MPI_SCATTER
ко- личества и типы данных не должны приводить к тому, чтобы любая область корневого процесса записывалfсь бы более одного раза.
MPI_SCATTERV(sendbuf, sendcounts, displs, sendtype, recvbuf, recvcount,
recvtype, root, comm)
IN sendbuf адрес буфера посылки (альтернатива, используется только кор- невым процессом)
IN sendcounts целочисленный массив (размера группы), определяющий число элементов, для отправки каждому процессу
IN displs целочисленный массив (размера группы). Элемент i указывает смещение (относительно sendbuf, из которого берутся данные для процесса )
IN sendtype тип элементов посылающего буфера (дескриптор)
OUT recvbuf адрес принимающего буфера (альтернатива)
IN recvcount число элементов в посылающем буфере (целое)
IN recvtype тип данных элементов принимающего буфера (дескриптор)
IN root номер посылающего процесса (целое)
IN comm коммуникатор (дескриптор) int MPI_Scatterv(void* sendbuf, int *sendcounts, int *displs, MPI_Datatype sendtype, void* recvbuf, recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm)
MPI_SCATTERV(SENDBUF, SENDCOUNTS, DISPLS, SENDTYPE, RECVBUF,
RECVCOUNT, RECVTYPE, ROOT, COMM, IERROR)
SENDBUF(*), RECVBUF(*)
INTEGER SENDCOUNTS(*), DISPLS(*), SENDTYPE, RECVCOUNT,
RECVTYPE, ROOT, COMM, IERROR void MPI::Intracomm::Scatterv(const void* sendbuf, const int sendcounts[], const int displs[], const Datatype& sendtype, void* recvbuf, int recvcount, const Datatype& recvtype, int root) const
Пример 4.11.
Обратен примеру 4.2,
MPI_SCATTER
рассылает 100 чисел из корневого процесса каждому процессу в группе (рис. 4.7).
MPI_Comm comm; int gsize,*sendbuf; int root, rbuf[100];
MPI_Comm_size( comm, &gsize); sendbuf = (int *)malloc(gsize*100*sizeof(int));
MPI_Scatter( sendbuf, 100, MPI_INT, rbuf, 100, MPI_INT, root, comm);

122
Рис. 4.7. Корневой процесс рассылает по 100 целых чисел каждому процессу в группе
Пример 4.12.
Обратен примеру 4.5. Корневой процесс рассылает множества из 100 чисел типа
int
остальным процессам, но множества размещены в посылающем буфере с шагом
stride
, поэтому нужно ис- пользовать
MPI_SCATTERV
. Полагаем
stride
≥ 100
(рис. 4.8).
Рис. 4.8. Корневой процесс рассылает по 100 целых чисел, передавая данные со страйдом
MPI_Comm comm; int gsize,*sendbuf, root, rbuf[100], i, *displs, *scounts;
MPI_Comm_size( comm, &gsize); sendbuf = (int *)malloc(gsize*stride*sizeof(int)); displs = (int *)malloc(gsize*sizeof(int)); scounts = (int *)malloc(gsize*sizeof(int)); for (i=0; i{ displs[i] = i*stride; scounts[i] = 100;
}
MPI_Scatterv( sendbuf, scounts, displs, MPI_INT, rbuf, 100, MPI_INT, root, comm);
Все процессы
100 100 100 100 100 100 100
Корневой процесс sendbuf
Все процессы
100 100 100 100 100 100 100
Корневой процесс sendbuf
Страйд

123
Пример 4.13.
Обратен примеру 4.9, на стороне корневого процесса используется изменяющийся stride между блоками чисел, на прини- маемой стороне производится прием в
i
-й столбец С-массива размера
100×150 (рис. 4.9).
MPI_Comm comm; int gsize,recvarray[100][150],*rptr; int root, *sendbuf, myrank, bufsize, *stride;
MPI_Datatype rtype; int i, *displs, *scounts, offset;
MPI_Comm_size( comm, &gsize);
MPI_Comm_rank( comm, &myrank ); stride = (int *)malloc(gsize*sizeof(int));
/* stride[i] для i = 0 до gsize-1 – множество различных значений */ displs = (int *)malloc(gsize*sizeof(int)); scounts = (int *)malloc(gsize*sizeof(int)); offset = 0; for (i=0; i{ displs[i] = offset; offset += stride[i]; scounts[i] = 100 – i;
} /* создается тип данных для посылаемого столбца */
MPI_Type_vector( 100-myrank, 1, 150, MPI_INT, &rtype);
MPI_Type_commit( &rtype ); rptr = &recvarray[0][myrank];
MPI_Scatterv( sendbuf, scounts, displs, MPI_INT, rptr, 1, rtype, root, comm);
Рис. 4.9. Корневой процесс рассылает блоки из 100-i целых чисел в столбец i массива 100
×150 . На стороне отправки блоки размещены со страйдом stride[i]
150 150 150 100 100 100 100 100 99 98 100
Корневой процесс sendbuf
Страйд [i]
Все процессы
100

124
4.2.5. Сбор для всех процессов
MPI_ALLGATHER( sendbuf, sendcount, sendtype, recvbuf, recvcount,
recvtype, comm)
IN sendbuf начальный адрес посылающего буфера (альтернатива)
IN sendcount количество элементов в буфере (целое)
IN sendtype тип данных элементов в посылающем буфере (дескриптор)
OUT recvbuf адрес принимающего буфера (альтернатива)
IN recvcount количество элементов, полученных от любого процесса (це- лое)
IN recvtype тип данных элементов принимающего буфера (дескриптор)
IN comm коммуникатор (дескриптор) int MPI_Allgather(void* sendbuf,int sendcount, MPI_Datatype sendtype, void* recvbuf, int recvcount, MPI_Datatype recvtype, MPI_Comm comm)
MPI_ALLGATHER(SENDBUF, SENDCOUNT, SENDTYPE, RECVBUF,
RECVCOUNT, RECVTYPE, COMM, IERROR)
SENDBUF(*), RECVBUF(*)
INTEGER SENDCOUNT, SENDTYPE, RECVCOUNT, RECVTYPE, COMM,
IERROR void MPI::Intracomm::Allgather(const void* sendbuf, int sendcount, const Datatype& sendtype, void* recvbuf, int recvcount, const Datatype& recvtype) const
Функцию
MPI_ALLGATHER
можно представить как функцию
MPI_GATHER
, где результат принимают все процессы, а не только главный. Блок данных, посланный
j
-м процессом? принимается каж- дым процессом и помещается в
j-
й блок буфера
recvbuf.
Результат выполнения вызова
MPI_ALLGATHER(...)
такой же, как если бы все процессы выполнили
n
вызовов
MPI_GATHER(sendbuf, sendcount, sendtype,
recvbuf, recvcount, recvtype root, comm),
для
root=0,…,n-1.
Правила использования
MPI_ALLGATHER
соот- ветствуют правилам для
MPI_GATHER
. Сигнатура типа, связанная с
sendcount, sendtype
,
должна быть одинаковой во всех процессах.
MPI_ALLGATHERV
можно представить как
MPI_GATHERV
, но при ее использовании результат получают все процессы, а не толь- ко один корневой.
j
-й блок данных, посланный каждым процессом, принимается каждым процессом и помещается в
j-
й блок буфера
recvbuf.
Эти блоки не обязаны быть одинакового размера.

125
MPI_ALLGATHERV( sendbuf, sendcount, sendtype, recvbuf, recvcounts, displs,
recvtype, comm)
IN sendbuf начальный адрес посылающего буфера (альтернатива)
IN sendcount количество элементов в посылающем буфере (целое)
IN sendtype тип данных элементов в посылающем буфере (дескриптор)
OUT recvbuf адрес принимающего буфера (альтернатива)
IN recvcounts целочисленный массив (размера группы), содержащий коли- чество элементов, полученных от каждого процесса
IN displs целочисленный массив (размера группы). Элемент i представ- ляет смещение области (относительно recvbuf), где помещают- ся принимаемые данные от процесса i
IN recvtype тип данных элементов принимающего буфера (дескриптор)
IN comm коммуникатор (дескриптор) int MPI_Allgatherv(void* sendbuf, int sendcount, MPI_Datatype sendtype, void* recvbuf, int *recvcounts, int *displs, MPI_Datatype recvtype, MPI_Comm comm)
MPI_ALLGATHERV(SENDBUF, SENDCOUNT, SENDTYPE, RECVBUF,
RECVCOUNTS, DISPLS, RECVTYPE, COMM, IERROR)
SENDBUF(*), RECVBUF(*)
INTEGER SENDCOUNT, SENDTYPE, RECVCOUNTS(*), DISPLS(*),
RECVTYPE, COMM, IERROR void MPI::Intracomm::Allgatherv(const void* sendbuf, int sendcount, const Datatype& sendtype, void* recvbuf, const int recvcounts[], const int displs[], const Datatype& recvtype) const
Сигнатура типа, связанного с
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_ALLGATHERV
соответствуют правилам для
MPI_GATHERV
Пример 4.14.
Сбор 100 чисел типа
int
от каждого процесса в группе для каждого процесса.
MPI_Comm comm; int gsize,sendarray[100], *rbuf;
MPI_Comm_size( comm, &gsize); rbuf = (int *)malloc(gsize*100*sizeof(int));
MPI_Allgather( sendarray, 100, MPI_INT, rbuf, 100, MPI_INT, comm);

126
После исполнения вызова каждый процесс содержит конкатена- цию данных всей группы.
4.2.6. Функция all-to-all Scatter/Gather
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 коммуникатор (дескриптор) int MPI_Alltoall(void* sendbuf, int sendcount, MPI_Datatype sendtype, void* recvbuf, int recvcount, MPI_Datatype recvtype, MPI_Comm comm)
MPI_ALLTOALL(SENDBUF, SENDCOUNT, SENDTYPE, RECVBUF,
RECVCOUNT, RECVTYPE, COMM, IERROR)
SENDBUF(*), RECVBUF(*)
INTEGER SENDCOUNT, SENDTYPE, RECVCOUNT, RECVTYPE, COMM,
IERROR void MPI::Intracomm::Alltoall(const void* sendbuf, int sendcount, const Datatype& sendtype, void* recvbuf, int recvcount, const Datatype& recvtype) const
Результат выполнения функции
MPI_ALLTOALL
такой же, как если бы каждый процесс выполнил посылку данных каждому про- цессу (включая себя) вызовом:
MPI_Send(sendbuf + i * sendcount * extent(sendtype),
sendcount, sendtype, i, ...),
и принял данные от всех остальных процессов путем вызова:
MPI_Recv(recvbuf + i* recvcount* extent(recvtype), recvcount, i,…).
Сигнатура типа, связанная с
sendcount, sendtype
в каждом процес- се должна быть такой же, как и в любом другом процессе. Это требу-

127
ет, чтобы количество посланных данных было равно количеству по- лученных данных между каждой парой процессов, карты типа могут отличаться. Все аргументы используются всеми процессами. Аргу- мент
comm
должен иметь одинаковое значение во всех процессах.
MPI_ALLTOALLV
обладает большей гибкостью, чем функция
MPI_ALLTOALL,
поскольку размещение данных на передающей стороне определяется аргументом
sdispls
, а на стороне приема – неза- висимым аргументом
rdispls
j-
й блок, посланный процессом
i
, при- нимается процессом
j
и помещается в
i
-й блок
recvbuf.
Эти блоки не обязаны быть одного размера. Сигнатура типа, связанная с
sendcount[j], sendtype
в процессе
i
, должна быть такой же и для про- цесса
j
. Это подразумевает, что количество посланных данных должно быть равно количеству полученных данных для каждой пары процес- сов. Карты типа для отправителя и приемника могут отличаться.
MPI_ALLTOALLV(sendbuf, sendcounts, sdispls, sendtype, recvbuf,
recvcounts, rdispls, recvtype, comm)
IN sendbuf начальный адрес посылающего буфера (альтернатива)
IN sendcounts целочисленный массив (размера группы), определяющий ко- личество посылаемых каждому процессу элементов
IN sdispls целочисленный массив (размера группы). Элемент j содержит смещение области (относительно sendbuf), из которой берутся данные для процесса j
IN sendtype тип данных элементов посылающего буфера (дескриптор)
OUT recvbuf адрес принимающего буфера (альтернатива)
IN recvcounts целочисленный массив (размера группы), содержит число элементов, которые могут быть приняты от каждого процесса
IN rdispls целочисленный массив (размера группы). Элемент i определя- ет смещение области (относительно recvbuf), в которой раз- мещаются данные, получаемые из процесса i
IN recvtype тип данных элементов принимающего буфера (дескриптор)
IN comm коммуникатор (дескриптор) int MPI_Alltoallv(void* sendbuf, int *sendcounts, int *sdispls, MPI_Datatype sendtype, void* recvbuf,int *recvcounts,int *rdispls, MPI_Datatype recvtype, MPI_Comm comm)
MPI_ALLTOALLV(SENDBUF, SENDCOUNTS, SDISPLS, SENDTYPE,
RECVBUF, RECVCOUNTS, RDISPLS, RECVTYPE, COMM, IERROR)
SENDBUF(*), RECVBUF(*)
INTEGER SENDCOUNTS(*), SDISPLS(*), SENDTYPE, RECVCOUNTS(*),
RDISPLS(*), RECVTYPE, COMM, IERROR

128
void MPI::Intracomm::Alltoallv(const void* sendbuf, const int sendcounts[], const int sdispls[], const Datatype& sendtype, void* recvbuf, const int recvcounts[], const int rdispls[], const Datatype& recvtype) const
Результат выполнения
MPI_ALLTOALLV
такой же, как если бы процесс посылал сообщение всем остальным процессам с помощью функции
MPI_Send(sendbuf + displs[i] * extent(sendtype),
sendcounts[i], sendtype, i,…)
и принимал сообщение от всех остальных процессов, вызывая
MPI_Recv(recvbuf + displs[i] * extent(recvtype), recvcounts[i],
recvtype, i,…).
Все аргументы используются всеми процессами. Значение аргу- мента
comm
должно быть одинаковым во всех процессах.
4.3
. ГЛОБАЛЬНЫЕ ОПЕРАЦИИ РЕДУКЦИИ
Функции в этом разделе предназначены для выполнения операций глобальной редукции (суммирование, нахождение максимума, логи- ческое И, и т.д.) для всех элементов группы. Операция редукции мо- жет быть одной из предопределенного списка операций или опреде- ляться пользователем. Функции глобальной редукции имеют несколь- ко разновидностей: операции, возвращающие результат в один узел; функции, возвращающие результат во все узлы; операции просмотра.
4.3.1. Функция Reduce
Функция
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 коммуникатор (дескриптор)

129
int MPI_Reduce(void* sendbuf, void* recvbuf, int count, MPI_Datatype datatype,
MPI_Op op, int root, MPI_Comm comm)
MPI_REDUCE(SENDBUF, RECVBUF, COUNT, DATATYPE, OP, ROOT, COMM,
IERROR)
SENDBUF(*), RECVBUF(*)
INTEGER COUNT, DATATYPE, OP, ROOT, COMM, IERROR void MPI::Intracomm::Reduce(const void* sendbuf, void* recvbuf, int count, const Datatype& datatype, const Op& op, int root) const
Буфер ввода определен аргументами
sendbuf, count
и
datatype;
буфер вывода определен параметрами
recvbuf, count
и
datatype;
оба буфера имеют одинаковое число элементов одинакового типа. Функ- ция вызывается всеми членами группы с одинаковыми аргументами
count, datatype, op, root
и
comm.
Таким образом, все процессы имеют входные и выходные буфера одинаковой длины и с элементами одно- го типа. Каждый процесс может содержать один элемент или после- довательность элементов, в последнем случае операция выполняется над всеми элементами в этой последовательности. Например, если выполняется операция
MPI_MAX
и посылающий буфер содержит два элемента – числа с плавающей точкой (
count = 2, datatype =
MPI_FLOAT
), то
recvbuf(1) = sendbuf(1)
и
recvbuf(2) =
sendbuf(2).
4.3.2. Предопределенные операции редукции
Имя
Значение
MPI_MAX максимум
MPI_MIN минимум
MPI_SUM сумма
MPI_PROD произведение
MPI_LAND логическое И
MPI_BAND поразрядное И
MPI_LOR логическое ИЛИ
MPI_BOR поразрядное ИЛИ
MPI_LXOR логическое исключающее ИЛИ
MPI_BXOR поразрядное исключающее ИЛИ
MPI_MAXLOC максимальное значение и местонахождения
MPI_MINLOC минимальное значение и местонахождения
Группы основных типов данных MPI:
C integer:
MPI_INT,MPI_LONG,MPI_SHORT,
MPI_UNSIGNED_SHORT,MPI_UNSIGNED,
MPI_UNSIGNED_LONG

130
Fortran integer:
MPI_INTEGER
Floating point:
MPI_FLOAT,MPI_DOUBLE,MPI_REAL,
MPI_DOUBLE_PRECISION,MPI_LONG_DOUBLE
Logical: MPI_LOGICAL
Complex: MPI_COMPLEX
Byte: MPI_BYTE
Типы данных для каждой операции:
Op
Разрешённые типы
MPI_MAX, MPI_MIN
C integer, Fortran integer, Floating point
MPI_SUM, MPI_PROD
C integer, Fortran integer, Floating point, Complex
MPI_LAND, MPI_LOR,
MPI_LXOR
C integer, Logical
MPI_BAND, MPI_BOR,
MPI_BXOR
C integer, Fortran integer, Byte
Пример 4.15.
Процедура вычисляет скалярное произведение двух векторов, распределенных в группе процессов, и возвращает результат в нулевой узел.
SUBROUTINE PAR_BLAS1(m, a, b, c, comm)
REAL a(m), b(m) ! локальная часть массива
REAL c ! результат (на узле ноль)
REAL sum
INTEGER m, comm, i, ierr
! локальная сумма sum = 0.0
DO i = 1, m sum = sum + a(i)*b(i)
END DO
! глобальная сумма
CALL MPI_REDUCE(sum, c, 1, MPI_REAL, MPI_SUM, 0, comm, ierr)
RETURN
Пример 4.16
Процедура вычисляет произведение вектора на массив, которые распределены в группе процессов, и возвращает результат в нулевой узел.
SUBROUTINE PAR_BLAS2(m, n, a, b, c, comm)
REAL a(m), b(m,n) ! локальная часть массива
REAL c(n) ! результат
REAL sum(n)

131
INTEGER n, comm, i, j, ierr
! локальная сумма
DO j= 1, n sum(j) = 0.0
DO i = 1, m sum(j) = sum(j) + a(i)*b(i,j)
END DO
END DO
! глобальная сумма
CALL MPI_REDUCE(sum, c, n, MPI_REAL, MPI_SUM, 0, comm, ierr)
RETURN
4.3.3. MINLOС и MAXLOС
Оператор
MPI_MINLOC
используется для расчета глобального минимума и соответствующего ему индекса.
MPI_MAXLOC
анало- гично считает глобальный максимум и индекс. Обе операции ассоциа- тивны и коммутативны. Если каждый процесс предоставляет значение и свой номер в группе, то операция редукции с
op = MPI_MAXLOC
возвратит значение максимума и номер первого процесса с этим зна- чением. Аналогично,
MPI_MINLOC
может быть использована для получения минимума и его индекса.
Чтобы использовать
MPI_MINLOC
и
MPI_MAXLOC
в операции редукции, нужно обеспечить аргумент
datatype,
который представля- ет пару (значение и индекс).
MPI предоставляет девять таких предо- пределенных типов данных:
Name Description
Fortran:
MPI_2REAL пара переменных типа REAL
MPI_2DOUBLE_PRECISION пара переменных типа DOUBLE PRECISION
MPI_2INTEGER пара переменных типа INTEGERs
C:
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_2REAL
аналогичен тому, как если бы он был определен следующим образом:
MPI_TYPE_CONTIGOUS(2, MPI_REAL, MPI_2REAL).

132
Аналогичными выражениями задаются
MPI_2INTEGER,
MPI_2DOUBLE_PRECISION
и
MPI_2INT.
Тип данных
MPI_FLOAT_INT
аналогичен тому, как если бы он был объявлен следующей последовательностью инструкций. type[0] = MPI_FLOAT type[1] = MPI_INT disp[0] = 0 disp[1] = sizeof(float) block[0] = 1 block[1] = 1
MPI_TYPE_STRUCT(2, block, disp, type, MPI_FLOAT_INT)
Подобные выражения относятся и к функциям
MPI_LONG_INT
и
MPI_DOUBLE_INT.
Пример 4.17.
Каждый процесс имеет массив 30 чисел типа double.
Для каждой из 30 областей надо вычислить значение и номер процес- са, содержащего наибольшее значение.
/* каждый процесс имеет массив из чисел двойной точности: ain[30]*/ double ain[30], aout[30]; int ind[30]; struct { double val; int rank;
} in[30], out[30]; int i, myrank, root;
MPI_Comm_rank(MPI_COMM_WORLD, &myrank); for (i=0; i<30; ++i) { in[i].val = ain[i]; in[i].rank = myrank;
}
MPI_Reduce(in,out,30,MPI_DOUBLE_INT,MPI_MAXLOC,root,comm );
/* в этой точке результат помещается на корневой процесс */ if (myrank == root) { /* читаются выходные номера */ for (i=0; i<30; ++i) { aout[i] = out[i].val; ind[i] = out[i].rank; /* номер обратно преобразуется в целое */
}

133
Пример 4.18.
Каждый процесс имеет не пустой массив чисел. Требу- ется найти минимальное глобальное число, номер процесса, храняще- го его, его индекс в этом процессе.
#define LEN 1000 float val[LEN]; /* локальный массив значений */ int count; /* локальное количество значений */ int myrank, minrank, minindex; float minval; struct { float value; int index;
} in, out;
/* локальный minloc */ in.value = val[0]; in.index = 0; for (i=1; i < count; i++) if (in.value > val[i])
{ in.value = val[i]; in.index = i;
}
/* глобальный minloc */
MPI_Comm_rank(MPI_COMM_WORLD, &myrank); in.index = myrank*LEN + in.index;
MPI_Reduce(in, out, 1, MPI_FLOAT_INT, MPI_MINLOC, root, comm );
/* в этой точке результат помещается на корневой процесс */ if (myrank == root)
{ minval = out.value; minrank = out.index / LEN; minindex = out.index % LEN;
}
4.3.4. Функция All-Reduce
MPI имеет варианты каждой из операций редукции, где результат возвращается всем процессам группы. MPI требует, чтобы все процес- сы, участвующие в этих операциях, получили идентичные результаты.
MPI_ALLREDUCE( sendbuf, recvbuf, count, datatype, op, comm)
IN sendbuf начальный адрес посылающего буфера (альтернатива)
OUT recvbuf начальный адрес принимающего буфера (альтернатива)
IN count количество элементов в посылающем буфере (целое)
IN datatype тип данных элементов посылающего буфера ()
IN op операция (дескриптор)
IN comm коммуникатор (дескриптор)

134
int MPI_Allreduce(void* sendbuf, void* recvbuf, int count, MPI_Datatype datatype,
MPI_Op op, MPI_Comm comm)
MPI_ALLREDUCE(SENDBUF, RECVBUF, COUNT, DATATYPE, OP, COMM,
IERROR)
SENDBUF(*), RECVBUF(*)
INTEGER COUNT, DATATYPE, OP, COMM, IERROR void MPI::Intracomm::Allreduce(const void* sendbuf, void* recvbuf, int count, const Datatype& datatype, const Op& op) const
Функция
MPI_ALLREDUCE
отличается от
MPI_REDUCE
тем, что результат появляется в принимающем буфере всех членов группы.
Пример 4.19.
Процедура вычисляет произведение вектора и массива, которые распределены по всем процессам группы, и возвращает ответ всем узлам.
SUBROUTINE PAR_BLAS2(m, n, a, b, c, comm)
! локальная часть массива
REAL a(m), b(m,n)
! результат
REAL c(n)
REAL sum(n)
INTEGER n, comm, i, j, ierr
! локальная сумма
DO j= 1, n sum(j) = 0.0
DO i = 1, m sum(j) = sum(j) + a(i)*b(i,j)
END DO
END DO
! глобальная сумма
CALL MPI_ALLREDUCE(sum,c,n,MPI_REAL,MPI_SUM,comm,ierr)
! возвращение результата всем узлам
RETURN
4.3.5. Функция Reduce-Scatter
MPI имеет варианты каждой из операций редукции, когда резуль- тат рассылается всем процессам в группе в конце операции.
Функция
MPI_REDUCE_SCATTER
сначала производит поэле- ментную редукцию вектора из
count = ∑
i
recvcount[i]
элементов в по- сылающем буфере, определенном
sendbuf, count
и
datatype
Далее полученный вектор результатов разделяется на
n
непересекающихся

135
сегментов, где
n
– число членов в группе. Сегмент
i
содержит
recvcount[i]
элементов.
i-
й сегмент посылается
i-
му процессу и хра- нится в приемном буфере, определяемом
recvbuf, recvcounts[i]
и
datatype
MPI_REDUCE_SCATTER(sendbuf, recvbuf, recvcounts, datatype, op, comm)
IN sendbuf начальный адрес посылающего буфера (альтернатива)
OUT recvbuf начальный адрес принимающего буфера (альтернатива)
IN recvcounts целочисленный массив, определяющий количество элементов результата, распределенных каждому процессу. Массив должен быть идентичен во всех вызывающих процессах
IN datatype тип данных элементов буфера ввода (дескриптор)
IN op операция (дескриптор)
IN comm коммуникатор (дескриптор) int MPI_Reduce_scatter(void* sendbuf, void* recvbuf, int *recvcounts,
MPI_Datatype datatype, MPI_Op op, MPI_Comm comm)
MPI_REDUCE_SCATTER(SENDBUF, RECVBUF, RECVCOUNTS,
DATATYPE, OP, COMM, IERROR)
SENDBUF(*), RECVBUF(*)
INTEGER RECVCOUNTS(*), DATATYPE, OP, COMM, IERROR void Intracomm::Reduce_scatter(const void* sendbuf, void* recvbuf, int recvcounts[], const Datatype& datatype, const Op& op) const
4.3.6. Функция Scan
MPI_SCAN(sendbuf, recvbuf, count, datatype, op, comm )
IN sendbuf начальный адрес посылающего буфера (альтернатива)
OUT recvbuf начальный адрес принимающего буфера (альтернатива)
IN count количество элементов в принимающем буфере (целое)
IN datatype тип данных элементов в принимающем буфере (дескриптор)
IN op операция (дескриптор)
IN comm коммуникатор (дескриптор) int MPI_Scan(void* sendbuf, void* recvbuf, int count, MPI_Datatype datatype,
MPI_Op op, MPI_Comm comm )
MPI_SCAN(SENDBUF, RECVBUF, COUNT, DATATYPE, OP, COMM, IERROR)
SENDBUF(*), RECVBUF(*)
INTEGER COUNT, DATATYPE, OP, COMM, IERROR void Intracomm::Scan(const void* sendbuf, void* recvbuf, int count, const Datatype& datatype, const Op& op) const
Функция
MPI_SCAN
используется, чтобы выполнить префиксную редукцию данных, распределенных в группе. Операция возвращает в

136
приемный буфер процесса
i
редукцию значений в посылающих буфе- рах процессов с номерами
0, ..., i
(включительно). Тип поддерживае- мых операций, их семантика и ограничения на буфера посылки и приема – такие же, как и для
MPI_REDUCE
4.4. КОРРЕКТНОСТЬ
Пример 4.20.
Следующий отрезок программы неверен.
switch(rank) { case 0:
MPI_Bcast(buf1, count, type, 0, comm);
MPI_Bcast(buf2, count, type, 1, comm); break; case 1:
MPI_Bcast(buf2, count, type, 1, comm);
MPI_Bcast(buf1, count, type, 0, comm); break;
}
Предполагается, что группа
comm
есть {
0,1
}. Два процесса вы- полняют две операции широковещания в обратном порядке. Если операция синхронизирующая, произойдёт взаимоблокирование. Кол- лективные операции должны быть выполнены в одинаковом порядке во всех элементах группы.
Пример 4.21.
Следующий отрезок программы неверен. switch(rank) { case 0:
MPI_Bcast(buf1, count, type, 0, comm0);
MPI_Bcast(buf2, count, type, 2, comm2); break; case 1:
MPI_Bcast(buf1, count, type, 1, comm1);
MPI_Bcast(buf2, count, type, 0, comm0); break; case 2:
MPI_Bcast(buf1, count, type, 2, comm2);
MPI_Bcast(buf2, count, type, 1, comm1); break;
}
Предположим, что группа из
comm0
есть
{0,1},
группа из
comm1 –
{1, 2}
и группа из
comm2 – {2,0}.
Если операция широковещания син-

137
хронизирующая, то имеется циклическая зависимость: широковеща- ние в
comm2
завершается только после широковещания в
comm0
; широковещание в
comm0
завершается только после широковещания в
comm1
; и широковещание в
comm1
завершится только после широ- ковещания в
comm2
. Таким образом, будет иметь место дедлок. Кол- лективные операции должны быть выполнены в таком порядке, чтобы не было циклических зависимостей.
Пример 4.22.
Следующий отрезок программы неверен. switch(rank) { case 0:
MPI_Bcast(buf1, count, type, 0, comm);
MPI_Send(buf2, count, type, 1, tag, comm); break; case 1:
MPI_Recv(buf2, count, type, 0, tag, comm, status);
MPI_Bcast(buf1, count, type, 0, comm); break;
}
Процесс с номером 0 выполняет широковещательную рассылку
(
bcast
), сопровождаемую блокирующей посылкой данных (
send
).
Процесс с номером 1 выполняет блокирующий приём (
receive
), кото- рый соответствует посылке с последующей широковещательной пе- редачей, соответствующей широковещательной операции процесса с номером 0. Такая программа может вызвать дедлок. Операция широ- ковещания на процессе с номером 0 может вызвать блокирование, по- ка процесс с номером 1 не выполнит соответствующую широковеща- тельную операцию, так что посылка не будет выполняться. Процесс 0 будет неопределенно долго блокироваться на приеме, в этом случае никогда не выполнится операция широковещания. Относительный порядок выполнения коллективных операций и операций парного об- мена должен быть такой, чтобы даже в случае синхронизации не бы- ло дедлока.
Пример 4.23.
Правильная, но недетерминированная программа. switch(rank) { case 0:
MPI_Bcast(buf1, count, type, 0, comm);
MPI_Send(buf2, count, type, 1, tag, comm); break; case 1:

138
MPI_Recv(buf2, count, type, MPI_ANY_SOURCE, tag, comm, status);
MPI_Bcast(buf1, count, type, 0, comm);
MPI_Recv(buf2, count, type, MPI_ANY_SOURCE, tag, comm, status); break; case 2:
MPI_Send(buf2, count, type, 1, tag, comm);
MPI_Bcast(buf1, count, type, 0, comm); break;
}
Все три процесса участвуют в широковещании (broadcast). Про- цесс 0 посылает сообщение процессу 1 после операции широковеща- ния, а процесс 2 посылает сообщение процессу 1 перед операцией широковещания. Процесс 1 принимает данные перед и после опера- ции широковещания, с произвольным номером процесса-отправителя.
У этой программы существует два возможных варианта выполне- ния, с разными соответствиями между отправлением и получением.
Заметим, что второй вариант выполнения имеет специфику, заклю- чающуюся в том, что посылка выполненная после операции широко- вещания получена в другом узле перед операцией широковещания.
Этот пример показывает, что нельзя полагаться на специфические эф- фекты синхронизации. Программа, которая работает правильно толь- ко тогда, когда выполнение происходит по первой схеме (только когда операция широковещания выполняет синхронизацию), неверна.
КОНТРОЛЬНЫЕ ВОПРОСЫ И ЗАДАНИЯ К ГЛАВЕ 4
Контрольные вопросы к 4.1
1. Дайте определение локальной и коллективной операций.
2. Участвуют ли в коллективных операциях все процессы приложения?
3. Должна ли быть вызвана функция, соответствующая коллективной операции, каждым процессом, быть может, со своим набором параметров?
4. В коллективных операциях участвуют все процессы коммуникатора?
5. Что такое корневой процесс?
6. Означает ли возврат процесса из функции, реализующей коллективную опе- рацию, что операция завершена?
7. В чем преимущество использования коллективных операций перед парными?
8. Приведите пример некорректного использования коллективных операций, приводящий к дедлоку.
9. Можно ли использовать в качестве аргумента коллективной функции интер- коммуникатор?
Контрольные вопросы к 4.2
1. Какие коллективные операции используются для синхронизации процессов?

139 2. Означает ли вызов функции MPI_Barrier, что вызывающий процесс блокиру- ется, пока все процессы приложения не вызовут ее?
3. Можно ли в качестве номера корневого процесса в функции широковеща- тельной передачи MPI_Bcast указывать номер любого процесса коммуника- тора?
4. Означает ли возврат из функции MPI_Bcast, что содержимое буфера обмена скопировано во все процессы?
5. Как изменить код программы в примере 4.1. для осуществления широковеща- тельной передачи 200 вещественных чисел от процесса 2 каждому процессу в группе works?
6. В каком порядке располагает сообщения корневой процесс при выполнении операции сборки данных MPI_Gather?
7. Какие из аргументов функции MPI_Gather не используются в процессах, не являющихся корневыми?
8. Сколько сообщений от каждого процесса может принимать корневой процесс при выполнении операции MPI_Gatherv?
9. В чем состоит особенность размещения данных в корневом процессе при вы- полнении операции MPI_Gatherv?
10. В чем различие использования функции MPI_Gather в примерах 4.2, 4.3 и 4.4?
11. Почему неверна программа в примере 4.5, если stride < 100?
12. Как в примере 4.6 осуществить посылку от каждого процесса 100 элементов n-го столбца, где n<100?
13. Поясните, почему из каждого процесса получено различное количество дан- ных в примере 4.7?
14. Сравните реализации программы примеров 4.7, 4.8 и 4.9. Какой из примеров предпочтительней? Почему?
15. Можно ли выполнить задание примера 4.10, не используя функцию
MPI_Gather? Предложите варианты решения.
16. Может ли начальный адрес буфера рассылки совпадать с адресом буфера процесса-получателя при вызове функции MPI_Scatter?
17. Разрешается ли изменять количества данных, посылаемых каждому процессу в функции MPI_Scatter? А в функции MPI_Scatterv?
18. В чем различие между двумя вызовами
MPI_Scatter( sendbuf, 100, MPI_INT, rbuf, 100, MPI_INT, root, comm); и MPI_Bcast ( sendbuf, 100, MPI_INT, root, comm)?
19. Как изменить код программы в примере 4.11 для осуществления передачи 200 вещественных чисел от процесса 2 каждому процессу в группе works?
20. Что произойдет при реализации программы из примера 4.12, если stride<100?
21. Как изменится код программы из примера 4.13, если на принимаемой сторо- не производится прием в 0-й столбец С-массива?
22. Будет ли различие в результатах для корневого процесса при использовании
MPI_Gather и MPI_Allgather с одинаковыми параметрами?
22. Сколько процессов получают данные при использовании MPI_Allgather? А при использовании MPI_Allgatherv?
23. Каким набором посылок и приемов (MPI_Send и MPI_Recv) можно заменить вызов функции MPI_Alltoall? А – MPI_Alltoallv?

140
Контрольные вопросы к 4.3
1. В каком порядке производится операция редукции над данными из разных процессов при вызове функции MPI_Reduce?
2. Гарантирована ли однозначность результата в стандарте MPI?
3. Какие типы данных можно использовать для операций MPI_MINLOC и
MPI_MAXLOC в операциях редукции?
4. Сколько процессов получают данные при использовании MPI_Allreduce?
5. Предложите вариант эквивалентной по результату выполнения замены опера- ции MPI_Allreduce двумя операциями: MPI_Reduce и MPI_Bcast.
6. Сколько процессов получают данные при использовании MPI_Reduce?
А при – MPI_Reduce_scatter?
7. Предложите вариант эквивалентной замены операции MPI_Scan набором по- сылок и приемов (MPI_Send и MPI_Recv).
Задания для самостоятельной работы
4.1
. Напишите программу, которая читает целое значение с терминала и по- сылает это значение всем MPI–процессам. Каждый процесс должен печатать свой номер и полученное значение. Значения следует читать, пока не появится на вхо- де отрицательное целое число.
4.2
. Напишите программу, которая реализует параллельный алгоритм для за- дачи нахождения скалярного произведение двух векторов.
4.3.
Напишите программу, которая реализует параллельный алгоритм для произведения вектора на матрицу. Будем считать, что матрица и вектор генери- руются в нулевом процессе, затем рассылаются всем процессам. Каждый процесс считает n/size элементов результирующего вектора, где n – количество строк мат- рицы, size – число процессов приложения.
4.4
. Напишите программу пересылки по num чисел типа int из i-го столбца массива 100*150 от каждого процесса в корневой.
4.5.
Напишите программу, которая читает целое значение и значение двой- ной точности с терминала и посылает одной командой MPI_Bcast эти значения всем MPI–процессам. Каждый процесс должен печатать свой номер и полученные значения. Значения следует читать, пока не появится на входе отрицательное це- лое число. Используйте возможности MPI для создания новых типов данных:
Type_struct.
4.6.
Выполните задание 4.5, используя MPI_Pack и MPI_Unpack для обеспе- чения коммуникации различных типов данных.
4.7
. Напишите программу для измерения времени передачи вещественных данных двойной точности от одного процесса другому. Выполните задание при условии, что каждый процесс передает и принимает от процесса, находящегося на расстоянии size/2, где имеется size процессов в MPI_COMM_WORLD. Лучшее решение будет получено при использовании MPI_SendRecv, MPI_Barrier, чтобы гарантировать, что различные пары стартуют почти одновременно, однако воз- можны другие решения. Для усреднения накладных расходов следует: повторить достаточное количество операций пересылок для получения времени в пределах

141
долей секунды (образцовое решение делает 100000/size итераций для целых size), повторить тестирование несколько раз (например, 10) и усреднить результаты.
4.8.
Напишите программу для измерения времени, необходимого для выпол- нения MPI_Allreduce на MPI_COMM_WORLD. Как изменяются характеристики для MPI_Allreduce при изменении размера MPI_COMM_WORLD?
4.9.
Напишите программу для измерения времени, необходимого для выпол- нения MPI_Barrier на MPI_COMM_WORLD. Как изменяются характеристики для
MPI_Barrier при изменении размера MPI_COMM_WORLD?
4.10
. Напишите программу для определения объема буферизации, необходи- мого для выполнения MPI_Send. Это означает, что нужно написать программу, которая определяет, насколько большого объема сообщение может быть послано без включения соответствующего приема в процессе назначения.
4.11
. Пусть A(n,m) – матрица, созданная в процессе 0. Например, может быть прочитана из памяти или уже была вычислена. Пусть имеем 4 процесса и процесс 0 посылает части этой матрицы другим процессам. Процессор 1 получает
A(i,j) для i=n/2+1,...,n, и j=1,...,m/2. Процессор 2 получает A(i,j) для i=1,...,n/2 и j=m/2+1,...,m и процессор 3 получает A(i,j) для i=n/2+1,...,n and j=m/2,...,m . Это двумерная декомпозиция А на четыре процесса. Напишите программу рассылки частей матрицы по процессам, используйте MPI_Scatterv, чтобы послать данные из процессора 0 всем другим процессам (включая процесс 0).
4.12
. Пусть имеем двумерный массив X размера maxn*maxn. Эта структура есть двумерная регулярная сетка точек, которую разделим на слои, каждый из которых будет обрабатывать отдельный процесс. Пусть вычисления, которые не- обходимо выполнить, нуждаются в смежных значениях. Это означает, что для вычисления нового значения x[i][j] необходимо знать: x[i][j+1], x[i][j-1], x[i+1][j], x[i-1][j]. Последние два могут быть проблемой, если они находятся в смежных процессах. Чтобы разрешить это, определим теневые точки в каждом процессе, которые будут содержать эти смежные точки. Элементы среды, которые исполь- зуются, чтобы сохранять данные из других процессов, называются «теневыми».
Напишите программу для правильного заполнения теневых точек в каждом процессе. Для простоты предположите:
1) maxn = 12 и количество процессов =4;
2) каждый процесс заполняет свою часть массива собственным номером в коммуникаторе, а теневые точки значениями -1. После обмена с соседними процессами необходимо проверить правильность заполнения теневых точек;
3) область непериодическая, то есть верхний процесс (номер = size-1) только по- сылает и принимает данные от нижележащего процесса (номер = size–2), а са- мый нижний процесс (номер = 0) передает и принимает данные только от про- цесса выше него (номер = 1).
4.13.
Выполните задание 4.12, используя неблокируемые парные обмены вместо блокируемых. Замените MPI_Send и MPI_Recv процедурами MPI_ISend и
MPI_IRecv и используйте MPI_Wait или MPI_Waitall для теста на окончание не- блокируемой операции.
4.14.
Выполните задание 4.12, заменив в решении вызовы MPI_Send и
MPI_Recv двумя вызовами MPI_SendRecv. Первый вызов должен сдвинуть дан- ные вверх, то есть послать данные процессору, который расположен выше и при-

142
нять данные от процессора, расположенного ниже. Второе обращение к
MPI_SendRecv должно быть обратным первому: послать данные нижележащему процессору и получить данные от процессора, расположенного выше.
4.15.
Выполните задание 4.12, используя MPI_SendRecv для обмена данными с соседними процессами. Это означает, что обмениваются процессы 0 и 1, 2 и 3 и так далее.
4.16
. В этом задании необходимо решить уравнение Лапласа на сетке двух измерений методом итераций Якоби. Любой текст численного анализа показыва- ет, что итерации будут вычислять аппроксимацию для решения уравнения Лапла- са, причем новое значение xnew замещается средним значением точек вокруг не- го для внутренних точек, а граничные значения остаются фиксированными. while (not converged) { for (i,j) xnew[i][j] = (x[i+1][j] + x[i-1][j] + x[i][j+1] + x[i][j-1])/4; for (i,j) x[i][j] = xnew[i][j]; }
На практике это означает, что если сетка имеет размер n*n, тогда значения x[0][j], x[n-1][j], x[i][0], x[i][n-1] не изменяются. Для проверки сходимости выбе- рем следующий критерий: diffnorm = 0; for (i,j) diffnorm += (xnew[i][j] – x[i][j]) * (xnew[i][j] – x[i][j]); diffnorm = sqrt(diffnorm).
Когда diffnorm станет меньше 1.0e-2, будем считать, что результат достигнут.
Напишите программу исполнения этой аппроксимации. Для простоты рас- смотрите сетку 12*12 на 4 процессах, заполненную значениями предыдущего примера: это значения равные -1 на границах, значения, равные номеру процесса во внутренних точках сетки.
4.17.
Выполните задание 4.16, модифицируя его так, чтобы вычисленное ре- шение собиралось в процессоре 0, который затем выводит этот результат.
4.18.
Во многих случаях невозможно разделить параллельную структуру данных так, чтобы каждый процесс имел одинаковое количество данных. Это да- же может быть нежелательным, когда сумма работы, которую необходимо сде- лать, является переменной. Выполните задание 4.17, изменив код так, чтобы каж- дый процесс мог иметь различное число строк разделяемой сетки.
4.19.
Выполните задание 4.17 при условии, что распределяемый массив за- полняется значениями из файла.
4.20
. Напишите программу, в которой каждый процесс генерирует часть мат- рицы псевдослучайных чисел и передает все части в матрицу главному процессу.
4.21
. Напишите программу нахождения максимального значения и его ин- декс из массива чисел равномерно распределенного по процессам.

143
1   ...   7   8   9   10   11   12   13   14   ...   26


написать администратору сайта