Лекция 4 Параллельное программирование. MPI. Коллективные операции передачи данных Методология параллельных систем и вычислений. Доцент М.А. Сокольская План. 1. 2. 3. 4. 5. 2 Обобщенная передача данных от всех процессов одному процессу Обобщенная передача данных от одного процесса всем процессам Общая передача данных от всех процессов всем процессам. Дополнительные операции редукции данных Пример. Умножение матрицы на вектор Коллективные операции передачи данных… Определение. Под коллективными операциями в MPI понимаются операции данных, в которых принимают участие все процессы используемого коммуникатора. Если какой-то процесс коммуникатора завершил коллективную операцию, это не означает, что операцию завершили остальные процессы. 3 Синхронизация процессов MPI_Barrier(MPI_COMM comm, MPI_INT err); Процедура барьерной синхронизации процессов. Работа всех процессов коммуникатора comm блокируется до тех пор, пока все процессы коммуникатора не вызовут эту функцию 4 Распределение данных Распределение данных – ведущий процесс (root) передает процессам различающиеся данные int MPI_Scatter(void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int rcount, MPI_Datatype rtype, int root, MPI_Comm comm), где - sbuf, scount, stype - параметры передаваемого сообщения (scount определяет количество элементов, передаваемых на каждый процесс), - rbuf, rcount, rtype - параметры сообщения, принимаемого в процессах, - root – ранг процесса, выполняющего рассылку данных, - comm - коммуникатор, в рамках которого выполняется передача данных. 5 Распределение данных Вызов MPI_Scatter при выполнении рассылки данных должен быть обеспечен в каждом процессе коммуникатора, MPI_Scatter передает всем процессам сообщения одинакового размера. Если размеры сообщений для процессов могут быть разными, следует использовать функцию MPI_Scatterv. 0 0 0 1 1 1 root 0 1 2 p-1 root а) до начала операции root p-1 p-1 6 p-1 б) после завершения операции Пример использования MPI_Scatter(); … int rbuf [100], Size; int root, *array; MPI_Comm_size(MPI_COMM_WORLD, &Size); array=new int [Size*100]; MPI_Scatter(array, 100, MPI_INT, rbuf, 100, MPI_INT, root, MPI_COMM_WORLD); 7 Сбор данных Сбор данных - передача данных от всех процессоров одному процессу является обратной к операции распределения данных int MPI_Gather(void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int rcount, MPI_Datatype rtype, int root, MPI_Comm comm), где - sbuf, scount, stype - параметры передаваемого сообщения, - rbuf, rcount, rtype - параметры принимаемого сообщения, - root – ранг процесса, выполняющего сбор данных, - comm - коммуникатор, в рамках которого выполняется передача данных. 8 Сбор данных MPI_Gather определяет коллективную операцию, и ее вызов при выполнении сбора данных должен быть обеспечен в каждом процессе коммуникатора 0 0 0 1 1 1 root 0 1 2 p-1 root а) после завершения операции root p-1 p-1 9 p-1 б) до начала операции Сбор и рассылка данных 10 MPI_Gather собирает данные на одном процессе. Для получения всех собираемых данных на каждом процессе нужно использовать функцию сбора и рассылки: int MPI_Allgather(void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int rcount, MPI_Datatype rtype, MPI_Comm comm) В случае, когда размеры передаваемых процессами сообщений могут быть различны, для передачи данных необходимо использовать функции MPI_Gatherv и MPI_Allgatherv. Общая передача данных от всех процессов всем процессам… 0 0 00 0 1 0 (p-1) 1 (p-1) 10 11 (p-1) 0 01 11 (p-1) 1 i 0 i 1 (p-1) i i (p-1)(p-1) а) до начала операции i i (p-1) (p-1)0 (p-1)1 11 1 1 p-1 00 10 0 i 1 i p-1 0(p-1) 1(p-1) (p-1) (p-1) б) после завершения операции Дополнительные операции редукции данных… 12 MPI_Reduce обеспечивает получение результатов редукции данных только на одном процессе, Функция MPI_Allreduce редукции и рассылки выполняет рассылку между процессами всех результатов операции редукции: int MPI_Allreduce(void *sendbuf, void *recvbuf,int count, MPI_Datatype type, MPI_Op op, MPI_Comm comm) Дополнительные операции редукции данных… Возможность управления распределением этих данных между процессами предоставляется функцией MPI_Reduce_scatter, Функция MPI_Scan производит операцию сбора и обработки данных, при которой обеспечивается получение и всех частичных результатов редуцирования int MPI_Scan(void *sendbuf, void *recvbuf,int count, MPI_Datatype type, MPI_Op op,MPI_Comm comm) 13 Дополнительные операции редукции данных… При выполнении функции MPI_Scan элементы получаемых сообщений представляют собой результаты обработки соответствующих элементов передаваемых процессами сообщений, при этом для получения результатов на процессе с рангом i, 0 i<n, используются данные от процессов, ранг которых меньше или равен i 0 x00 x01 x02 x0,n-1 0 y00 y01 y02 y0,n-1 1 x10 x11 x12 x1,n-1 1 y10 y11 y12 y1,n-1 i yi0 yi1 yi2 yi,n-1 p-1 yn-1,0 yn-1,1 i xi0 xi1 xi2 14 xn-1,0 xn-1,1 xi,n-1 p-1 xn-1,n-1 а) до начала операции yn-1,n-1 б) после завершения операции Умножение матрицы на вектор a0,1 , ..., a0,n 1 b0 c0 a0, 0 , ... b1 c1 b c a , a , ..., a m 1,1 m 1, n 1 n 1 m 1 m 1, 0 Задача умножения матрицы на вектор может быть сведена к выполнению m независимых операций умножения строк матрицы A на вектор b n ci ai , b ai j bj , 0 i m 15 j 1 Способы распределения данных: ленточная схема Непрерывное (последовательное) распределение горизонтальные полосы 16 вертикальные полосы A ( A0 , A1 ,..., A p 1 )T , A ( A0 , A1 ,..., A p 1 ), Ai (ai0 , ai1 ,..., aik 1 ), Ai ( i0 , i1 ,..., ik 1 ) , i j ik j , 0 j k , k m / p i j il j , 0 j l , l n / p ( ai , 0i m, ст роки м ат рицыA) ( i , 0 i т, ст олбцым ат рицыA) Последовательный алгоритм for ( i = 0; i < m; i++ ) { c [ i ] = 0; for ( j = 0; j < n; j++ ) { c [ i ] += A [ i ][ j ] * b [ j ]; } } 17 Умножение матрицы на вектор Распределение данных – ленточная схема (разбиение матрицы по строкам) Базовая подзадача - операция скалярного умножения одной строки матрицы на вектор 18 Выделение информационных зависимостей 19 Базовая подзадача для выполнения вычисления должна содержать строку матрицы А и копию вектора b. После завершения вычислений каждая базовая подзадача будет содержать один из элементов вектора результата c Для объединения результатов расчетов и получения полного вектора c на каждом из процессоров вычислительной системы необходимо выполнить операцию обобщенного сбора данных Программная реализация Главная функция программы реализует логику работы алгоритма, последовательно вызывая необходимые подпрограммы. void main (int argc, char *argv[ ]) { double *pMatrix; //исходная матрица double *pVector; //исходный вектор double *pResult; //результат умножения int Size; //размеры исходных матрицы и вектора double *pProcRows; //часть матрицы на процессе double *pProcResult; //результаты вычислений процесса int RowNum; double Start, Finish, Duration; int ProcNum, ProcRank; 20 Программная реализация 21 MPI_Init (&argc, &argv); MPI_Comm_size (MPI_COMM_WORLD, &ProcNum); MPI_Comm_rank (MPI_COMM_WORLD, &ProcRank); //выделение памяти и инициализация исходных //данных ProcInit (pMatrix, pVector, pResult, pProcRows, pProcResult, Size, RowNum); //распределение исходных данных между //процессорами DataDistrib (pMatrix, pProcRows, pVector, Size, RowNum); Программная реализация //параллельное выполнение умножения матрицы на вектор ParalResCalc (pProcRows, pVector, pProcResult, Size, RowNum); //сбор результирующего вектора на всех процессах ResReplication (pProcResult, pResult, Size, RowNum); //завершение процесса вычислений ProcTermination (pMatrix, pVector, pResult, pProcRows, pProcResult); MPI_Finalize (); 22 } Функция ProcInit 23 Задает размер и элементы для матрицы A и вектора b void ProcInit (double *pMatrix, double *pVector, double *pResult, double *pProcRows, double *pProcResult, int Size, int RowNum) { int RestRows; //кол-во строк матрицы, которые еще не //распределены int i; if (ProcRank == 0) { do { Функция ProcInit printf(“\nВведите размер матрицы: ”); scanf(“%d”, &Size); if (Size < ProcNum) printf (“Малый размер\n”); } while (Size < ProcNum); 24 } MPI_Bcast(&Size, 1, MPI_INT, 0, MPI_COMM_WORLD); RestRows = Size; for (i = 0; i < ProcRank; i++) RestRows = RestRows – RestRows/(ProcNum - 1) RowNum = RestRows / (ProcNum - ProcRank); Функция ProcInit pVector = new double [Size]; pResult = new double [Size]; pProcRows = new double [RowNum * Size]; pProcResult = new double [RowNum]; if (ProcRank == 0) { pMatrix = new double [Size * Size]; RandomDataInit (pMatrix, pVector, Size); //функцию задать самостоятельно } } 25 Функция DataDistrib 26 Осуществляет рассылку вектора b и распределение строк исходной матрицы A по процессам. void DataDistrib (double *pMatrix, double *pProcRows, double *pVector, int Size, int RowNum) { int *pSendNum; //кол-во элементов, посылаемых //процессу int *pSendInd; //индекс первого элемента данных, //посылаемого процессу int RestRows = Size; //кол-во строк матрицы, которые //еще не распределены MPI_Bcast (pVector, Size, MPI_DOUBLE, 0, MPI_COMM_WORLD); Функция DataDistrib 27 //выделение памяти для хранения временных //объектов pSendInd = new int [ProcNum]; pSendNum = new int [ProcNum]; //определение положения строк матрицы, //предназначенных каждому процессу RowNum = Size / ProcNum; pSendNum [ 0 ] = RowNum + Size; pSendInd [ 0 ] = 0; for (int i = 1; i < ProcNum; i++) { RestRows = RestRows – RowNum; Функция DataDistrib RowNum = RestRows / (ProcNum - i); pSendNum [ i ] = RowNum * Size; pSendInd [ i ] = pSendInd [ i-1 ] + pSendNum [ i-1 ]; }; //рассылка строк матрицы MPI_Scatterv (pMatrix, PSendNum, pSendInd, MPI_DOUBLE, pProcRows, pSendNum[ProcRank], MPI_DOUBLE, 0, MPI_COMM_WORLD); //освобождение памяти delete [] pSendNum; delete [] pSendInd; 28 } Функция ParalResCalc 29 Вычисление части результирующего вектора void ParalResCalc (double *pProcRows, double *pVector, double *pProcResult, int Size, int RowNum) { int i, j; for (i = 0; i < RowNum; i++) { pProcResult [i] = 0; for (j = 0; j < Size; j++) pProcResult [i] = pProcResult [ i ] + pProcRows[i * Size + j] * pVector[ j ]; } } Функция ResReplication Объединяет блоки результирующего вектора c, полученного на разных процессах и копирует вектор результата на все процессы. void ResReplication (double *pProcResult, double *pResult, int Size, int RowNum) { int *pReceiveNum; //кол-во элементов, посылаемых процессом int *pReceiveInd; //индекс элемента данных в результирующем векторе int RestRows = Size; //кол-во строк матрицы, которые еще не распределены int i; 30 Функция ResReplication //выделение памяти для временных объектов pReceiveInd = new int [ProcNum]; pReceiveNum = new int [ProcNum]; //определение положения блоков результирующего вектора pReceiveInd [ 0 ] = 0; pReceiveNum [ 0 ] = Size / ProcNum; for (i = 1; i < ProcNum; i++) { RestRows = RestRows – pReceiveNum [i-1]; pReceiveNum [i] = RestRows / (ProcNum - 1); pReceiveInd [i] = pReceiveInd [i-1] + pReceiveNum [i-1] } 31 Функция ResReplication //сбор всего вектора на всех процессах MPI_Allgatherv (pProcResult, pReceiveNum[ProcRank], MPI_DOUBLE, pResult, pReceiveNum, pReceiveInd, MPI_DOUBLE, MPI_COMM_WORLD); //освобождение памяти delete [] pReceiveNum; delete [] pReceiveInd; } 32 Самостоятельно 1. Дописать функции для примера a) b) 2. 33 RandomDataInit (pMatrix, pVector, Size) ProcTermination (pMatrix, pVector, pResult, pProcRows, pProcResult). Запустить доработанную программу на кластере. Итоги Мы рассмотрели: Функции коллективной передачи данных, функции управления группами процессов и коммуникаторами, пример параллельной программы умножения матрицы на вектор. 34