Программирование для многопроцессорных систем в стандарте MPI - Шпаковский Г.И., Серикова Н.В.. Программирование для многопроцессорных систем в стандарте MPI -. Организация вычислений в многопроцессорных системах
Скачать 1.61 Mb.
|
Пример 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 } /* теперь легко получается требуемый размер буфера для 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 /* создается тип данных для одного 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) 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) 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 } 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 } /* создается тип данных для посылаемого столбца */ 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) 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) 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) 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) 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) 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) 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) 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) 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 . Напишите программу нахождения максимального значения и его ин- декс из массива чисел равномерно распределенного по процессам. |