Матрицы и матричные операции широко используются при
математическом моделировании самых разнообразных процессов,
явлений и систем.
С учетом значимости эффективного выполнения матричных расчетов
многие стандартные библиотеки программ содержат процедуры для
различных матричных операций. Объем программного обеспечения для
обработки матриц постоянно увеличивается – разрабатываются новые
экономные структуры хранения для матриц специального типа
(треугольных, ленточных, разреженных и т.п.), создаются различные
высокоэффективные машинно-зависимые реализации алгоритмов,
проводятся теоретические исследования для поиска более быстрых
Являясь вычислительно трудоемкими,
В данной лекции обсуждаются методы параллельных вычислений для
операции матрично-векторного умножения, в следующей лекции
(лекция 7) излагается более
общий случай – задача
При изложении следующего материала будем полагать, что рассматриваемые матрицы являются плотными ( dense ), то есть число нулевых элементов в них незначительно по сравнению с общим количеством элементов матриц.
Для многих
Наиболее общие и широко используемые способы разделения матриц состоят в разбиении данных на полосы (по вертикали или горизонтали) или на прямоугольные фрагменты ( блоки ).
1. Ленточное разбиение матрицы. При ленточном
( block-striped ) разбиении каждому
процессору выделяется то или иное подмножество строк
( rowwise или горизонтальное разбиение ) или столбцов
( columnwise или вертикальное разбиение ) матрицы
(рис. 6.1). Разделение строк
и столбцов на полосы в большинстве случаев происходит на непрерывной
( последовательной ) основе. При таком
подходе для горизонтального разбиения по строкам, например,
матрица A представляется в виде
(см. рис. 6.1)$$A=(A_0, A_1, \ldots, A_{p-1})^T, A_i=(a_{i_0},a_{i_1},\ldots,a_{i_{k-1}}),
i_j = ik+j, 0 \le j < k, k=m/p ,$$
где ai=(ai1,ai2,...,ain), 0<=i<m,
есть i -я строка матрицы A
(предполагается, что количество строк m кратно числу
процессоров p, т.е. m = kxp ). Во
всех алгоритмах матричного умножения и умножения матрицы на
вектор, которые будут рассмотрены в этой и следующей лекциях,
применяется разделение данных на непрерывной основе.
Другой возможный подход к формированию полос состоит в
применении той или иной схемы чередования
( цикличности ) строк или столбцов. Как
правило, для чередования используется число процессоров p – в этом случае при горизонтальном разбиении
матрица A принимает вид$$A=(A_0, A_1, \ldots, A_{p-1})^T, A_i=(a_{i_0},a_{i_1},\ldots,a_{i_{k-1}}),
i_j = i+jp, 0 \le j < k, k=m/p ,$$
Циклическая схема формирования полос может оказаться полезной для лучшей балансировки вычислительной нагрузки процессоров (например, при решении системы линейных уравнений с использованием метода Гаусса – см. лекцию 8).
2. Блочное разбиение матрицы. При блочном ( chessboard
block ) разделении матрица делится на прямоугольные наборы
элементов – при этом, как правило, используется разделение на
непрерывной основе. Пусть количество процессоров составляет p = sxq, количество строк матрицы является
кратным s, а количество столбцов – кратным q, то есть m = kxs и n = lxq. Представим исходную матрицу A в виде набора прямоугольных блоков следующим
образом:$$A =
\begin{pmatrix}
A_{00} A_{02} \ldots A_{0q-1} \\
\ldots \\
A_{s-11} A_{s-12} \ldots A_{s-1q-1}
\end{pmatrix} ,$$
где Aij — блок матрицы, состоящий из элементов:$$A =
\begin{pmatrix}
a_{i_0 j_0} a_{i_0 j_1} \ldots a_{i_0 j_{l-1}} \\
\ldots \\
a_{i_{k-1} j_0} a_{i_{k-1} j_1} \ldots a_{i_{k-1} j_{l-1}}
\end{pmatrix} ,
\begin{aligned}
i_{\nu} = ik+\nu, 0 \le \nu < k,\ k=m/s, \\
j_u = jl+u, 0 \le u \le l,\ l=n/q.
\end{aligned}$$
При таком подходе целесообразно, чтобы вычислительная система
имела физическую или, по крайней мере, логическую топологию
процессорной решетки из s строк и q
столбцов. В этом случае при разделении данных на непрерывной
основе процессоры, соседние в структуре решетки, обрабатывают
смежные блоки исходной матрицы. Следует отметить, однако, что и
для блочной схемы может быть применено циклическое чередование
строк и столбцов.
В данной лекции рассматриваются три параллельных алгоритма для умножения квадратной матрицы на вектор. Каждый подход основан на разном типе распределения исходных данных (элементов матрицы и вектора) между процессорами. Разделение данных меняет схему взаимодействия процессоров, поэтому каждый из представленных методов существенным образом отличается от двух остальных.
(рис 6.1) Способы распределения элементов матрицы между процессорами вычислительной системы
В результате умножения матрицы А размерности m x n и вектора b, состоящего из n элементов, получается вектор c
размера m, каждый i -й элемент которого
есть результат i -й строки
матрицы А (обозначим эту строчку ai ) и вектора b:$$c_i = (a_i, b) = \sum_{j=1}^n a_{ij}b_j, 1 \le i \le m.$$
Тем самым получение результирующего вектора c
предполагает повторение m однотипных операций по
умножению строк матрицы A и вектора b.
Каждая такая операция включает умножение элементов строки матрицы
и вектора b ( n операций) и последующее
суммирование полученных произведений ( n-1 операций).
Общее количество необходимых скалярных операций есть величина
T1=mx(2n-1)
Последовательный алгоритм умножения матрицы на вектор может быть представлен следующим образом.
Алгоритм 6.1. Последовательный алгоритм умножения матрицы на вектор
// Алгоритм 6.1
// Поcледовательный алгоритм умножения матрицы на вектор
for (i = 0; i < m; i++){
c[i] = 0;
for (j = 0; j < n; j++){
c[i] += A[i][j]*b[j]
}
}
Матрично-векторное умножение – это последовательность
вычисления скалярных произведений. Поскольку каждое вычисление
скалярного произведения векторов длины n требует
выполнения n операций умножения и n-1
операций сложения, его трудоемкость порядка O(n).
Для выполнения матрично-векторного умножения необходимо
осуществить m операций вычисления скалярного
произведения, таким образом, алгоритм имеет трудоемкость порядка O(mn).
При выполнении параллельных алгоритмов умножения матрицы на
вектор, кроме матрицы А, необходимо разделить еще
вектор b и вектор результата c.
Элементы векторов можно продублировать, то
есть скопировать все элементы вектора на все процессоры,
составляющие многопроцессорную вычислительную систему, или
разделить между процессорами. При блочном
разбиении вектора из n элементов каждый процессор
обрабатывает непрерывную последовательность из k
элементов вектора (мы предполагаем, что размерность вектора n нацело делится на число процессоров, т.е. n= kxp ).
Поясним, почему дублирование векторов b и c между процессорами является допустимым решением
(далее для простоты изложения будем полагать, что m=n ). Векторы b и с
состоят из n элементов, т.е. содержат столько же
данных, сколько и одна строка или один столбец матрицы. Если
процессор хранит строку или столбец матрицы и одиночные элементы
векторов b и с, то общее число
сохраняемых элементов имеет порядок O(n). Если
процессор хранит строку (столбец) матрицы и все элементы векторов b и с, то общее число сохраняемых
элементов также порядка O(n). Таким образом, при
дублировании и при разделении векторов требования к объему памяти
из одного класса сложности.
Рассмотрим в качестве первого примера организации параллельных
Для выполнения базовой подзадачи скалярного произведения
процессор должен содержать соответствующую строку матрицы А и копию вектора b. После завершения
вычислений каждая базовая подзадача определяет один из элементов
вектора результата c.
Для объединения результатов расчета и получения полного
вектора c на каждом из процессоров вычислительной системы
необходимо выполнить операцию обобщенного сбора данных (см. лекцию 4), в которой каждый
процессор передает свой вычисленный элемент вектора c всем остальным процессорам. Этот шаг можно
выполнить, например, с использованием функции MPI_Allgather из библиотеки
В общем виде схема информационного взаимодействия подзадач в ходе выполняемых вычислений показана на рис. 6.2.
(рис 6.2) Организация вычислений при выполнении параллельного алгоритма умножения матрицы на вектор, основанного на разделении матрицы по строкам
В процессе умножения плотной матрицы на вектор количество
вычислительных операций для получения скалярного произведения
одинаково для всех базовых подзадач. Поэтому в случае когда число
процессоров p меньше числа базовых подзадач m, мы можем объединить базовые подзадачи таким
образом, чтобы каждый процессор выполнял несколько таких задач,
соответствующих непрерывной последовательности строк матрицы А. В этом случае по окончании вычислений каждая
базовая подзадача определяет набор элементов результирующего
вектора с.
Распределение подзадач между процессорами вычислительной системы может быть выполнено произвольным образом.
Для анализа
Второй тип оценок направлен на формирование как можно более
точных соотношений для предсказания времени выполнения
алгоритмов. Получение таких оценок проводится, как правило, при
помощи уточнения выражений, полученных на первом этапе. Для этого
в имеющиеся соотношения вводятся параметры, задающие длительность
выполнения операций, строятся оценки трудоемкости
коммуникационных операций, указываются все необходимые константы.
Точность получаемых выражений проверяется при помощи
вычислительных экспериментов, по результатам которых время
выполненных расчетов сравнивается с теоретически предсказанными
оценками длительностей вычислений. Как результат, оценки
подобного типа имеют, как правило, более сложный вид, но
позволяют более точно оценивать
Рассмотрим трудоемкость алгоритма умножения матрицы на вектор.
В случае если матрица А квадратная ( m=n ), последовательный
алгоритм умножения матрицы на вектор имеет сложность T1=n2. В
случае параллельных вычислений каждый процессор производит
умножение только части (полосы) матрицы A на вектор b, размер
этих полос равен n/p строк. При вычислении скалярного
произведения одной строки матрицы и вектора необходимо произвести n операций умножения и (n-1) операций сложения. Следовательно,
вычислительная трудоемкость параллельного алгоритма определяется
выражением:$$T_p=n^2/p.$$
С учетом этой оценки показатели
Построенные выше оценки времени вычислений выражены в
количестве операций и, кроме того, определены без учета затрат на
выполнение
(здесь и далее операция $$\lcei\ \rceil$$ есть округление до целого в большую сторону).
Оценка трудоемкости операции обобщенного сбора данных уже
выполнялась в лекции 4 (см. п.
4.3.4). Как уже отмечалась ранее, данная операция может быть
выполнена за $$\lceil$$ log2p $$\rceil$$ w есть размер одного элемента вектора c в байтах), на второй итерации этот объем
увеличивается вдвое и оказывается равным $$2w\lceil n/p \rceil$$ и т.д. Как результат,
длительность выполнения операции сбора данных при использовании
модели Хокни может быть определена при помощи следующего
выражения$$T_p(comm) = \sum_{i=1}^{\lceil \log_2 p \rceil}
(\alpha + 2^{i-1}w\lceil n/p \rceil / \beta) =
\alpha \lceil \log_2 p \rceil +
w \lceil n/p \rceil (2^{\lceil \log_2 p \rceil} - 1)/\beta,$$
где $$\alpha$$ – латентность сети передачи данных, $$\beta$$ –
пропускная способность сети. Таким образом, общее время
выполнения параллельного алгоритма составляет$$T_p=(n/p)\cdot (2n-1) \cdot \tau + \alpha \cdot \log_2 p +w(n/p)(p-1)/ \beta$$
(для упрощения выражения в (6.8) предполагалось, что значения n/p и log2p являются
целыми).
Представим возможный вариант параллельной программы умножения матрицы на вектор с использованием алгоритма разбиения матрицы по строкам. При этом реализация отдельных модулей не приводится, если их отсутствие не оказывает влияние на понимании общей схемы параллельных вычислений.
1. Главная функция программы. Реализует логику работы алгоритма, последовательно вызывает необходимые подпрограммы.
// Программа 6.1
// Умножение матрицы на вектор – ленточное горизонтальное разбиение
// (исходный и результирующий векторы дублируются между процессами)
void main(int argc, char* argv[]) {
double* pMatrix; // Первый аргумент – исходная матрица
double* pVector; // Второй аргумент – исходный вектор
double* pResult; // Результат умножения матрицы на вектор
int Size; // Размеры исходных матрицы и вектора
double* pProcRows;
double* pProcResult;
int RowNum;
double Start, Finish, Duration;
MPI_Init(argc, argv);
MPI_Comm_size(MPI_COMM_WORLD, ProcNum);
MPI_Comm_rank(MPI_COMM_WORLD, ProcRank);
// Выделение памяти и инициализация исходных данных
ProcessInitialization(pMatrix, pVector, pResult, pProcRows,
pProcResult, Size, RowNum);
// Распределение исходных данных между процессами
DataDistribution(pMatrix, pProcRows, pVector, Size, RowNum);
// Параллельное выполнение умножения матрицы на вектор
ParallelResultCalculation(pProcRows, pVector, pProcResult,
Size, RowNum);
// Сбор результирующего вектора на всех процессах
ResultReplication(pProcResult, pResult, Size, RowNum);
// Завершение процесса вычислений
ProcessTermination(pMatrix, pVector, pResult, pProcRows,
pProcResult);
MPI_Finalize();
}
2. Функция ProcessInitialization. Эта функция задает
размер и элементы для матрицы A и вектора b. Значения для матрицы A и вектора b определяются в функции RandomDataInitialization.
// Функция для выделения памяти и инициализации исходных данных
void ProcessInitialization (double* pMatrix, double* pVector,
double* pResult, double* pProcRows, double* pProcResult,
int Size, int RowNum) {
int RestRows; // Количество строк матрицы, которые еще
// не распределены
int i;
if (ProcRank == 0) {
do {
printf("\nВведите размер матрицы: ");
scanf("%d", Size);
if (Size < ProcNum) {
printf("Размер матрицы должен превышать количество
процессов! \n ");
}
}
while (Size < ProcNum);
}
MPI_Bcast(Size, 1, MPI_INT, 0, MPI_COMM_WORLD);
RestRows = Size;
for (i=0; i<ProcRank; i++)
RestRows = RestRows-RestRows/(ProcNum-i);
RowNum = RestRows/(ProcNum-ProcRank);
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];
RandomDataInitialization(pMatrix, pVector, Size);
}
}
3. Функция DataDistribution. Осуществляет рассылку
вектора b и распределение строк исходной матрицы A по процессам вычислительной системы. Следует
отметить, что когда количество строк матрицы n не
является кратным числу процессоров p, объем
пересылаемых данных для процессов может оказаться разным и для
передачи сообщений необходимо использовать функцию MPI_Scatterv библиотеки
// Функция для распределения исходных данных между процессами
void DataDistribution(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);
// Выделение памяти для хранения временных объектов
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 -= RowNum;
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;
}
Следует отметить, что такое разделение действий генерации исходных данных и их рассылки между процессами может быть неоправданным в реальных параллельных вычислениях при большом объеме данных. Широко используемый подход в таких случаях состоит в организации передачи сообщений процессам сразу же после того, как данные процессов будут сгенерированы. Снижение затрат памяти для хранения данных может быть достигнуто также и за счет организации генерации данных в последнем процессе (при таком подходе память для пересылаемых данных и для данных процесса может быть одной и той же).
4. Функция ParallelResultCaculation. Данная функция
производит умножение на вектор тех строк матрицы, которые
распределены на данный процесс, и таким образом получается блок
результирующего вектора с.
// Функция для вычисления части результирующего вектора
void ParallelResultCalculation(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] += pProcRows[i*Size+j]*pVector[j];
}
}
5. Функция ResultReplication. Объединяет блоки
результирующего вектора с, полученные на разных
процессах, и копирует вектор результата на все процессы
вычислительной системы.
// Функция для сбора результирующего вектора на всех процессах
void ResultReplication(double* pProcResult, double* pResult,
int Size, int RowNum) {
int *pReceiveNum; // Количество элементов, посылаемых процессом
int *pReceiveInd; // Индекс элемента данных в результирующем
// векторе
int RestRows=Size; // Количество строк матрицы, которые еще не
// распределены
int i;
// Выделение памяти для временных объектов
pReceiveNum = new int [ProcNum];
pReceiveInd = new int [ProcNum];
// Определение положения блоков результирующего вектора
pReceiveInd[0] = 0;
pReceiveNum[0] = Size/ProcNum;
for (i=1; i<ProcNum; i++) {
RestRows -= pReceiveNum[i-1];
pReceiveNum[i] = RestRows/(ProcNum-i);
pReceiveInd[i] = pReceiveInd[i-1]+pReceiveNum[i-1];
}
// Сбор всего результирующего вектора на всех процессах
MPI_Allgatherv(pProcResult, pReceiveNum[ProcRank],
MPI_DOUBLE, pResult, pReceiveNum, pReceiveInd,
MPI_DOUBLE, MPI_COMM_WORLD);
// Освобождение памяти
delete [] pReceiveNum;
delete [] pReceiveInd;
}
Рассмотрим результаты
Определение параметров теоретических зависимостей (величин $$\tau , w, \alpha , \beta$$ ) осуществлялось следующим
образом. Для оценки длительности $$\tau$$ базовой скалярной операции
проводилось решение задачи умножения матрицы на вектор при помощи
последовательного алгоритма и полученное таким образом время
вычислений делилось на общее количество выполненных операций – в
результате подобных экспериментов для величины $$\tau$$ было
получено значение 1,93 нсек. Эксперименты, выполненные для
определения параметров сети передачи данных, показали значения
латентности $$\alpha$$ и пропускной способности $$\beta$$
соответственно 47 мкс и 53,29 Мбайт/с. Все вычисления
производились над числовыми значениями типа double,
т.е. величина w равна 8 байт.
Результаты
| Размер матрицы | Последовательный алгоритм | Параллельный алгоритм | |||||
|---|---|---|---|---|---|---|---|
| 2 процессора | 4 процессора | 8 процессоров | |||||
| Время | Ускорение | Время | Ускорение | Время | Ускорение | ||
| 1000 | 0,0041 | 0,0021 | 1,8798 | 0,0017 | 2,4089 | 0,0175 | 0,2333 |
| 2000 | 0,016 | 0,0084 | 1,8843 | 0,0047 | 3,3388 | 0,0032 | 4,9443 |
| 3000 | 0,031 | 0,0185 | 1,6700 | 0,0097 | 3,1778 | 0,0059 | 5,1952 |
| 4000 | 0,062 | 0,0381 | 1,6263 | 0,0188 | 3,2838 | 0,0244 | 2,5329 |
| 5000 | 0,11 | 0,0574 | 1,9156 | 0,0314 | 3,4993 | 0,0150 | 7,3216 |
Сравнение экспериментального времени $$T'_p$$
выполнения параллельного алгоритма и теоретического времени Tp, вычисленного в соответствии с
выражением (6.8), представлено в
таблице 6.2 и в графическом
виде на рис. 6.3 и
6.4.
| Размер объектов | 2 процессора | 4 процессора | 8 процессоров | |||
|---|---|---|---|---|---|---|
Tp |
T'p |
Tp |
T'p |
Tp |
T'p | |
| 1000 | 0,0069 | 0,0021 | 0,0108 | 0,0017 | 0,0152 | 0,0175 |
| 2000 | 0,0132 | 0,0084 | 0,0140 | 0,0047 | 0,0169 | 0,0032 |
| 3000 | 0,0235 | 0,0185 | 0,0193 | 0,0097 | 0,0196 | 0,0059 |
| 4000 | 0,0379 | 0,0381 | 0,0265 | 0,0188 | 0,0233 | 0,0244 |
| 5000 | 0,0565 | 0,0574 | 0,0359 | 0,0314 | 0,0280 | 0,0150 |

(рис 6.4) График зависимости экспериментального T'p и теоретического Tp времени выполнения параллельного алгоритма на двух процессорах от объема исходных данных (ленточное разбиение матрицы по строкам)(рис 6.3) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма умножения матрицы на вектор (ленточное разбиение по строкам) для разных размеров матриц
Рассмотрим теперь другой подход к параллельному умножению матрицы на вектор, основанный на разделении исходной матрицы на непрерывные наборы (вертикальные полосы) столбцов.
При таком способе разделения данных в качестве базовой
подзадачи может быть выбрана операция умножения столбца матрицы А на один из элементов вектора b. Для
организации вычислений в этом случае каждая базовая подзадача i, 0<=i<n, должна содержать i -й
столбец матрицы А и i -е элементы bi и ci векторов b и с.
Параллельный алгоритм умножения матрицы на вектор начинается с
того, что каждая базовая задача i выполняет
умножение своего столбца матрицы А на элемент bi, в итоге в каждой подзадаче получается
вектор c'(i) промежуточных результатов. Далее для
получения элементов результирующего вектора с подзадачи должны
обменяться своими промежуточными данными между собой (элемент j, 0<=j<n, c'(i) подзадачи i, 0<=i<n, должен
быть передан подзадаче j ). Данная обобщенная
передача данных ( all-to-all communication
или total exchange ) является наиболее
общей коммуникационной процедурой и может быть реализована при
помощи функции MPI_Alltoall библиотеки i, 0<=i<n, будет содержать n
частичных значений c'i(j), 0<=j<n,
сложением которых и определяется элемент ci вектора результата с (см.
рис. 6.5).
(рис 6.5) Организация вычислений при выполнении параллельного алгоритма умножения матрицы на вектор с использованием разбиения матрицы по столбцам
Выделенные базовые подзадачи характеризуются одинаковой
вычислительной трудоемкостью и равным объемом передаваемых
данных. В случае когда количество столбцов матрицы превышает
число процессоров, базовые подзадачи можно укрупнить, объединив в
рамках одной подзадачи несколько соседних столбцов (в этом случае
исходная матрица A разбивается на ряд вертикальных
полос). При соблюдении равенства размера полос такой способ
агрегации вычислений обеспечивает равномерность распределения
вычислительной нагрузки по процессорам, составляющим
многопроцессорную вычислительную систему.
Как и в предыдущем алгоритме, распределение подзадач между процессорами вычислительной системы может быть выполнено произвольным образом.
Пусть, как и ранее, матрица А является
квадратной, то есть m=n. На первом этапе вычислений
каждый процессор умножает принадлежащие ему столбцы матрицы А на элементы вектора b, после
умножения полученные значения суммируются для каждой строки
матрицы А в отдельности$$c'_s(i)= \sum_{j=j_0}^{j_{l-1}} a_{sj} b_j, \; 0 \le s < n ,$$
( j0 и jl-1 есть
начальный и конечный индексы столбцов базовой подзадачи i, 0<=i<n). Поскольку размеры полосы матрицы А и блока вектора b равны n/p, то трудоемкость таких вычислений может
оцениваться как T'= n2/p операций. После
обмена данными между подзадачами на втором этапе вычислений
каждый процессор суммирует полученные значения для своего блока
результирующего вектора c. Количество суммируемых
значений для каждого элемента ci вектора c совпадает с числом процессоров p,
размер блока вектора c на одном процессоре равен n/p, и, тем самым, число выполняемых операций для
второго этапа оказывается равным T''=n. С учетом
полученных соотношений показатели
Теперь рассмотрим более точные соотношения для оценки времени выполнения параллельного алгоритма. С учетом ранее проведенных рассуждений время выполнения вычислительных операций алгоритма может быть оценено при помощи выражения$$T_p(cacl)=[n \cdot (2 \cdot \lceil n/p \rceil - 1) + n] \cdot \tau$$ (здесь, как и ранее, $$\tau$$ есть время выполнения одной элементарной скалярной операции).
Для выполнения операции обобщенной передачи данных рассмотрим
два возможных способа реализации (см. также
лекцию 3). Первый способ
обеспечивается алгоритмом, согласно которому каждый процессор
последовательно передает свои данные всем остальным процессорам
вычислительном системы. Предположим, что процессоры могут
одновременно отправлять и принимать сообщения и между любой парой
процессоров имеется прямая линия связи, тогда оценка трудоемкости
(время исполнения) такого алгоритма обобщенной передачи данных
может быть определена как$$T_p^1(comm) = (p-1)(\alpha +w \lceil {n/p}\rceil / \beta).$$
(напомним, что $$\alpha$$ – латентность сети передачи данных, $$\beta$$
– пропускная способность сети, w – размер элемента
данных в байтах).
Второй способ выполнения операции обмена данными рассмотрен в
лекции 3, когда топология
вычислительной сети может быть представлена в виде гиперкуба. Как
было показано, выполнение такого алгоритма может быть
осуществлено за $$\lceil log_2p\rceil$$
шагов, на каждом из которых каждый процессор передает и получает
сообщение из n/2 элементов. Как результат, время
С учетом (6.11) – (6.13) общее время выполнения параллельного алгоритма умножения матрицы на вектор при разбиении данных по столбцам выражается следующими соотношениями.
| Размер матрицы | Последовательный алгоритм | Параллельный алгоритм | |||||
|---|---|---|---|---|---|---|---|
| 2 процессора | 4 процессора | 8 процессоров | |||||
| Время | Ускорение | Время | Ускорение | Время | Ускорение | ||
| 1000 | 0,0041 | 0,0022 | 1,8352 | 0,0015 | 3,1538 | 0,0008 | 4,9409 |
| 2000 | 0,016 | 0,0085 | 1,8799 | 0,0046 | 3,4246 | 0,0029 | 5,4682 |
| 3000 | 0,031 | 0,019 | 1,6315 | 0,0095 | 3,2413 | 0,0055 | 5,5456 |
| 4000 | 0,062 | 0,0331 | 1,8679 | 0,0168 | 3,6714 | 0,0090 | 6,8599 |
| 5000 | 0,11 | 0,0518 | 2,1228 | 0,0265 | 4,1361 | 0,0136 | 8,0580 |
Сравнение экспериментального времени $$T_p^*$$
выполнения эксперимента и времени Tp,
вычисленного по соотношениям (6.14), (6.15), представлено в
таблице 6.4 и на рис.
6.6 и
6.7. Теоретическое время $$T_p^1$$ вычисляется согласно (6.14), а теоретическое
время $$T_p^2$$ – в соответствии с (6.15).
(рис 6.6) График зависимости теоретического и экспериментального времени выполнения параллельного алгоритма на четырех процессорах от объема исходных данных (ленточное разбиение матрицы по столбцам)| Размер матрицы | 2 процессора | 4 процессора | 8 процессоров | ||||||
|---|---|---|---|---|---|---|---|---|---|
| $$T_p^*$$ | $$T_p^1$$ | $$T_p^2$$ | $$T_p^*$$ | $$T_p^1$$ | $$T_p^2$$ | $$T_p^*$$ | $$T_p^1$$ | $$T_p^2$$ | |
| 1000 | 0,0022 | 0,0021 | 0,0021 | 0,0013 | 0,0013 | 0,0014 | 0,0008 | 0,0011 | 0,0015 |
| 2000 | 0,0085 | 0,0082 | 0,0080 | 0,0046 | 0,0044 | 0,0044 | 0,0029 | 0,0027 | 0,0031 |
| 3000 | 0,019 | 0,0177 | 0,0177 | 0,0095 | 0,0094 | 0,0094 | 0,0055 | 0,0054 | 0,0056 |
| 4000 | 0,0331 | 0,0313 | 0,0313 | 0,0168 | 0,0163 | 0,0162 | 0,0090 | 0,0090 | 0,0091 |
| 5000 | 0,0518 | 0,0487 | 0,0487 | 0,0265 | 0,0251 | 0,0251 | 0,0136 | 0,0135 | 0,0136 |
(рис 6.7) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма умножения матрицы на вектор (ленточное разбиение матрицы по столбцам) для разных размеров матриц
Рассмотрим теперь параллельный алгоритм умножения матрицы на вектор, который основан на ином способе разделения данных – на разбиении матрицы на прямоугольные фрагменты ( блоки ).
Блочная схема разбиения матриц подробно рассмотрена в
подразделе 6.1. При таком способе разделения данных исходная
матрица A представляется в виде набора прямоугольных
блоков:$$A =
\begin{pmatrix}
A_{00} A_{02} \ldots A_{0q-1} \\
\ldots \\
A_{s-11} A_{s-12} \ldots A_{s-1q-1}
\end{pmatrix} ,$$
где Aij, 0<=i<s, 0<=j<q, есть блок матрицы:$$A =
\begin{pmatrix}
a_{i_0 j_0} a_{i_0 j_1} \ldots a_{i_0 j_{l-1}} \\
\ldots \\
a_{i_{k-1} j_0} a_{i_{k-1} j_1} \ldots a_{i_{k-1} j_{l-1}}
\end{pmatrix} ,
\begin{aligned}
i_{\nu} = ik+\nu, 0 \le \nu < k,\ k=m/s, \\
j_u = jl+u, 0 \le u \le l,\ l=n/q.
\end{aligned}$$
(здесь, как и ранее, предполагается, что p=sxq, количество строк матрицы является
кратным s, а количество столбцов – кратным q, то есть m=kxs и n=lxq ).
При использовании блочного представления матрицы A базовые подзадачи целесообразно определить на
основе вычислений, выполняемых над матричными блоками. Для
нумерации подзадач могут применяться индексы располагаемых в
подзадачах блоков матрицы A, т.е. подзадача (i,j) содержит блок Aij.
Помимо блока матрицы A каждая подзадача должна
содержать также и блок вектора b. При этом для
блоков одной и той же подзадачи должны соблюдаться определенные
правила соответствия – операция умножения блока матрицы A ij может быть выполнена только, если
блок вектора b'(i,j) имеет вид$$b'(i,j)=(b'_0(i,j),\ldots,b'_l-1(i,j)),\quad \text{где} \quad
b'_u(i,j)=b_{j_u} , \; j_u=jl+u, \; 0\le u < l, \; l=n/q.$$
Рассмотрим общую схему параллельных вычислений для операции
умножения матрицы на вектор при блочном разделении исходных
данных. После перемножения блоков матрицы A и
вектора b каждая подзадача (i,j) будет
содержать вектор c'(i,j),
определяемый в соответствии с выражениями$$c'_\nu(i,j)=\sum_{u=0}^{l-1} a_{i_{\nu} j_u} b_{j_u}, \;
i_{\nu}=i+\nu, \; 0 \le \nu < k, \; k=m/s, \; j_u=jl+u, \;
0 \le u \le l, l=n/q$$
Поэлементное суммирование векторов A позволяет получить результирующий вектор c$$c_{\eta} = \sum_{j=0}^{q-1} c'_{\nu}(i,j), \; 0 \le \eta < m, \;
i=\eta/s, \; \nu = \eta - i \cdots .$$
Для размещения вектора c применим ту же схему,
что и для исходного вектора b: организуем вычисления
таким образом, чтобы при завершении расчетов вектор c
располагался поблочно в каждой из вертикальных полос блоков
матрицы A (тем самым, каждый блок вектора c должен
быть продублирован по каждой горизонтальной полосе). Выполнение
всех необходимых действий для этого – суммирование частичных
результатов и дублирование блоков результирующего вектора — может
быть обеспечено при помощи функции MPI_Allreduce
библиотеки
Общая схема выполняемых вычислений для умножения матрицы на вектор при блочном разделении данных показана на рис. 6.8.
(рис 6.8) Общая схема параллельного алгоритма умножения матрицы на вектор при блочном разделении данных: a) исходное распределение результатов, б) распределение векторов частичных результатов, в) распределение блоков результирующего вектора cРассмотрев представленную схему параллельных вычислений, можно
сделать вывод, что информационная зависимость базовых подзадач
проявляется только на этапе суммирования результатов перемножения
блоков матрицы A и блоков вектора b.
Выполнение таких расчетов может быть выполнено по обычной
Размер блоков матрицы А может быть подобран таким образом,
чтобы общее количество базовых подзадач совпадало с числом
процессоров p. Так, например, если определить размер блочной
решетки как p=sxq, то
k=m/s, l=n/q,
где k и l есть количество строк и
столбцов в блоках матрицы А. Такой способ
определения размера блоков приводит к тому, что объем вычислений
в каждой подзадаче является равным, и, тем самым, достигается
полная балансировка вычислительной нагрузки между
процессорами.
Возможность выбора остается при определении размеров блочной
структуры матрицы А. Большое количество блоков по горизонтали
приводит к возрастанию числа итераций в операции редукции
результатов блочного умножения, а увеличение размера блочной
решетки по вертикали повышает объем передаваемых данных между
процессорами. Простое, часто применяемое решение состоит в
использовании одинакового количества блоков по вертикали и
горизонтали, т.е.$$s=q=\sqrt{p}$$
Следует отметить, что блочная схема разделения данных является
обобщением всех рассмотренных в данной лекции подходов.
Действительно, при q=1 блоки сводятся к
горизонтальным полосам матрицы А, при s=1 исходные данные разбиваются на вертикальные
полосы.
При решении вопроса распределения подзадач между процессорами должна учитываться возможность эффективного выполнения операции редукции. Возможный вариант подходящего способа распределения состоит в выделении для подзадач одной и той же горизонтальной полосы блоков множества процессоров, структура сети передачи данных между которыми имеет вид гиперкуба или полного графа.
Выполним анализ А является квадратной, т.е. m=n. Будем предполагать также, что процессоры,
составляющие многопроцессорную вычислительную систему, образуют
прямоугольную решетку p=sxq ( s –
количество строк в процессорной решетке, q –
количество столбцов).
Общий анализ
Для уточнения полученных соотношений оценим более точно
количество вычислительных операций алгоритма и учтем затраты на
выполнение
Общее время умножения блоков матрицы А и вектора b может быть определено как$$T_p(calc) = \lceil n/s \rceil \cdot (2 \cdot \lceil n/q \rceil - 1) \cdot \tau .$$
Операция редукции данных может быть выполнена с использованием
log2q итераций передачи сообщений размера $$w\lceil n/s \rceil$$. Как результат, оценка
коммуникационных затрат параллельного алгоритма при использовании
модели Хокни может быть определена при помощи следующего
выражения$$T_p(comm)=(\alpha +w \lceil n/s \rceil / \beta) \lceil log_2 q \rceil$$
Таким образом, общее время выполнения параллельного алгоритма умножения матрицы на вектор при блочном разделении данных составляет$$T_p = \lceil n/s \rceil \cdot (2 \cdot \lceil n/q \rceil - 1) \cdot \tau + (\alpha + w \lceil n/s \rceil / \beta) \lceil \log_2 q \rceil .$$
Сравнение экспериментального времени $$T^*_p$$
выполнения эксперимента и теоретического времени T
p, вычисленного в соответствии с выражением (6.19),
представлено в таблице 6.5 и на
рис. 6.10.
| Размер матриц | Последовательный алгоритм | Параллельный алгоритм | |||
|---|---|---|---|---|---|
| 4 процессора | 9 процессоров | ||||
| Время | Ускорение | Время | Ускорение | ||
| 1000 | 0,0041 | 0,0028 | 1,4260 | 0,0011 | 3,7998 |
| 2000 | 0,016 | 0,0099 | 1,6127 | 0,0095 | 3,2614 |
| 3000 | 0,031 | 0,0214 | 1,4441 | 0,0095 | 3,2614 |
| 4000 | 0,062 | 0,0381 | 1,6254 | 0,0175 | 3,5420 |
| 5000 | 0,11 | 0,0583 | 1,8860 | 0,0263 | 4,1755 |
(рис 6.9) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма умножения матрицы на вектор (блочное разбиение матрицы) для разных размеров матриц| Размер матриц | Последовательный алгоритм | Параллельный алгоритм | ||
|---|---|---|---|---|
| $$T_p$$ | $$T_p^*$$ | $$T_p$$ | $$T_p^*$$ | |
| 1000 | 0,0025 | 0,0028 | 0,0012 | 0,0011 |
| 2000 | 0,0095 | 0,0099 | 0,0043 | 0,0042 |
| 3000 | 0,0212 | 0,0214 | 0,0095 | 0,0095 |
| 4000 | 0,0376 | 0,0381 | 0,0168 | 0,0175 |
| 5000 | 0,0586 | 0,0583 | 0,0262 | 0,0263 |
(рис 6.10) График зависимости экспериментального и теоретического времени проведения эксперимента на четырех процессорах от объема исходных данных (блочное разбиение матрицы)
В данной лекции на примере задачи умножения матрицы на вектор рассматриваются возможные схемы разделения матриц между процессорами многопроцессорной вычислительной системы, которые могут быть использованы для организации параллельных вычислений при выполнении матричных операций. В числе излагаемых схем способы разбиения матриц на полосы (по вертикали или горизонтали) или на прямоугольные наборы элементов ( блоки ).
Далее в лекции с использованием рассмотренных способов
разделения матриц подробно излагаются три возможных варианта
параллельного выполнения операции умножения матрицы на вектор.
Первый алгоритм основан на разделении матрицы между процессорами
по строкам, второй – на разделении матрицы по столбцам, а третий
– на блочном разделении данных. Каждый алгоритм представлен в
соответствии с общей схемой, описанной в
лекции 4, — вначале
определяются базовые подзадачи, затем выделяются информационные
зависимости подзадач, далее обсуждается масштабирование и
распределение подзадач между процессорами. В завершение для
каждого алгоритма проводится анализ
Полученные показатели
На рис. 6.11 на общем
графике представлены показатели
(рис 6.11) Показатели ускорения рассмотренных параллельных алгоритмов умножения по результатам вычислительных экспериментов с матрицами размера 2000x2000 и векторами из 2000 элементов
Задача умножения матрицы на вектор часто используется как
демонстрационный пример параллельного программирования и, как
результат, широко рассматривается в литературе. В качестве
дополнительного учебного материала могут быть рекомендованы
работы [, , ]. Широкое обсуждение вопросов параллельного
выполнения
При рассмотрении вопросов
Матрицы и матричные операции широко используются при
математическом моделировании самых разнообразных процессов,
явлений и систем.
С учетом значимости эффективного выполнения матричных расчетов
многие стандартные библиотеки программ содержат процедуры для
различных матричных операций. Объем программного обеспечения для
обработки матриц постоянно увеличивается – разрабатываются новые
экономные структуры хранения для матриц специального типа
(треугольных, ленточных, разреженных и т.п.), создаются различные
высокоэффективные машинно-зависимые реализации алгоритмов,
проводятся теоретические исследования для поиска более быстрых
Являясь вычислительно трудоемкими,
В данной лекции обсуждаются методы параллельных вычислений для
операции матрично-векторного умножения, в следующей лекции
(лекция 7) излагается более
общий случай – задача
При изложении следующего материала будем полагать, что рассматриваемые матрицы являются плотными ( dense ), то есть число нулевых элементов в них незначительно по сравнению с общим количеством элементов матриц.
Для многих
Наиболее общие и широко используемые способы разделения матриц состоят в разбиении данных на полосы (по вертикали или горизонтали) или на прямоугольные фрагменты ( блоки ).
1. Ленточное разбиение матрицы. При ленточном
( block-striped ) разбиении каждому
процессору выделяется то или иное подмножество строк
( rowwise или горизонтальное разбиение ) или столбцов
( columnwise или вертикальное разбиение ) матрицы
(рис. 6.1). Разделение строк
и столбцов на полосы в большинстве случаев происходит на непрерывной
( последовательной ) основе. При таком
подходе для горизонтального разбиения по строкам, например,
матрица A представляется в виде
(см. рис. 6.1)$$A=(A_0, A_1, \ldots, A_{p-1})^T, A_i=(a_{i_0},a_{i_1},\ldots,a_{i_{k-1}}),
i_j = ik+j, 0 \le j < k, k=m/p ,$$
где ai=(ai1,ai2,...,ain), 0<=i<m,
есть i -я строка матрицы A
(предполагается, что количество строк m кратно числу
процессоров p, т.е. m = kxp ). Во
всех алгоритмах матричного умножения и умножения матрицы на
вектор, которые будут рассмотрены в этой и следующей лекциях,
применяется разделение данных на непрерывной основе.
Другой возможный подход к формированию полос состоит в
применении той или иной схемы чередования
( цикличности ) строк или столбцов. Как
правило, для чередования используется число процессоров p – в этом случае при горизонтальном разбиении
матрица A принимает вид$$A=(A_0, A_1, \ldots, A_{p-1})^T, A_i=(a_{i_0},a_{i_1},\ldots,a_{i_{k-1}}),
i_j = i+jp, 0 \le j < k, k=m/p ,$$
Циклическая схема формирования полос может оказаться полезной для лучшей балансировки вычислительной нагрузки процессоров (например, при решении системы линейных уравнений с использованием метода Гаусса – см. лекцию 8).
2. Блочное разбиение матрицы. При блочном ( chessboard
block ) разделении матрица делится на прямоугольные наборы
элементов – при этом, как правило, используется разделение на
непрерывной основе. Пусть количество процессоров составляет p = sxq, количество строк матрицы является
кратным s, а количество столбцов – кратным q, то есть m = kxs и n = lxq. Представим исходную матрицу A в виде набора прямоугольных блоков следующим
образом:$$A =
\begin{pmatrix}
A_{00} A_{02} \ldots A_{0q-1} \\
\ldots \\
A_{s-11} A_{s-12} \ldots A_{s-1q-1}
\end{pmatrix} ,$$
где Aij — блок матрицы, состоящий из элементов:$$A =
\begin{pmatrix}
a_{i_0 j_0} a_{i_0 j_1} \ldots a_{i_0 j_{l-1}} \\
\ldots \\
a_{i_{k-1} j_0} a_{i_{k-1} j_1} \ldots a_{i_{k-1} j_{l-1}}
\end{pmatrix} ,
\begin{aligned}
i_{\nu} = ik+\nu, 0 \le \nu < k,\ k=m/s, \\
j_u = jl+u, 0 \le u \le l,\ l=n/q.
\end{aligned}$$
При таком подходе целесообразно, чтобы вычислительная система
имела физическую или, по крайней мере, логическую топологию
процессорной решетки из s строк и q
столбцов. В этом случае при разделении данных на непрерывной
основе процессоры, соседние в структуре решетки, обрабатывают
смежные блоки исходной матрицы. Следует отметить, однако, что и
для блочной схемы может быть применено циклическое чередование
строк и столбцов.
В данной лекции рассматриваются три параллельных алгоритма для умножения квадратной матрицы на вектор. Каждый подход основан на разном типе распределения исходных данных (элементов матрицы и вектора) между процессорами. Разделение данных меняет схему взаимодействия процессоров, поэтому каждый из представленных методов существенным образом отличается от двух остальных.
(рис 6.1) Способы распределения элементов матрицы между процессорами вычислительной системы
В результате умножения матрицы А размерности m x n и вектора b, состоящего из n элементов, получается вектор c
размера m, каждый i -й элемент которого
есть результат i -й строки
матрицы А (обозначим эту строчку ai ) и вектора b:$$c_i = (a_i, b) = \sum_{j=1}^n a_{ij}b_j, 1 \le i \le m.$$
Тем самым получение результирующего вектора c
предполагает повторение m однотипных операций по
умножению строк матрицы A и вектора b.
Каждая такая операция включает умножение элементов строки матрицы
и вектора b ( n операций) и последующее
суммирование полученных произведений ( n-1 операций).
Общее количество необходимых скалярных операций есть величина
T1=mx(2n-1)
Последовательный алгоритм умножения матрицы на вектор может быть представлен следующим образом.
Алгоритм 6.1. Последовательный алгоритм умножения матрицы на вектор
// Алгоритм 6.1
// Поcледовательный алгоритм умножения матрицы на вектор
for (i = 0; i < m; i++){
c[i] = 0;
for (j = 0; j < n; j++){
c[i] += A[i][j]*b[j]
}
}
Матрично-векторное умножение – это последовательность
вычисления скалярных произведений. Поскольку каждое вычисление
скалярного произведения векторов длины n требует
выполнения n операций умножения и n-1
операций сложения, его трудоемкость порядка O(n).
Для выполнения матрично-векторного умножения необходимо
осуществить m операций вычисления скалярного
произведения, таким образом, алгоритм имеет трудоемкость порядка O(mn).
При выполнении параллельных алгоритмов умножения матрицы на
вектор, кроме матрицы А, необходимо разделить еще
вектор b и вектор результата c.
Элементы векторов можно продублировать, то
есть скопировать все элементы вектора на все процессоры,
составляющие многопроцессорную вычислительную систему, или
разделить между процессорами. При блочном
разбиении вектора из n элементов каждый процессор
обрабатывает непрерывную последовательность из k
элементов вектора (мы предполагаем, что размерность вектора n нацело делится на число процессоров, т.е. n= kxp ).
Поясним, почему дублирование векторов b и c между процессорами является допустимым решением
(далее для простоты изложения будем полагать, что m=n ). Векторы b и с
состоят из n элементов, т.е. содержат столько же
данных, сколько и одна строка или один столбец матрицы. Если
процессор хранит строку или столбец матрицы и одиночные элементы
векторов b и с, то общее число
сохраняемых элементов имеет порядок O(n). Если
процессор хранит строку (столбец) матрицы и все элементы векторов b и с, то общее число сохраняемых
элементов также порядка O(n). Таким образом, при
дублировании и при разделении векторов требования к объему памяти
из одного класса сложности.
Рассмотрим в качестве первого примера организации параллельных
Для выполнения базовой подзадачи скалярного произведения
процессор должен содержать соответствующую строку матрицы А и копию вектора b. После завершения
вычислений каждая базовая подзадача определяет один из элементов
вектора результата c.
Для объединения результатов расчета и получения полного
вектора c на каждом из процессоров вычислительной системы
необходимо выполнить операцию обобщенного сбора данных (см. лекцию 4), в которой каждый
процессор передает свой вычисленный элемент вектора c всем остальным процессорам. Этот шаг можно
выполнить, например, с использованием функции MPI_Allgather из библиотеки
В общем виде схема информационного взаимодействия подзадач в ходе выполняемых вычислений показана на рис. 6.2.
(рис 6.2) Организация вычислений при выполнении параллельного алгоритма умножения матрицы на вектор, основанного на разделении матрицы по строкам
В процессе умножения плотной матрицы на вектор количество
вычислительных операций для получения скалярного произведения
одинаково для всех базовых подзадач. Поэтому в случае когда число
процессоров p меньше числа базовых подзадач m, мы можем объединить базовые подзадачи таким
образом, чтобы каждый процессор выполнял несколько таких задач,
соответствующих непрерывной последовательности строк матрицы А. В этом случае по окончании вычислений каждая
базовая подзадача определяет набор элементов результирующего
вектора с.
Распределение подзадач между процессорами вычислительной системы может быть выполнено произвольным образом.
Для анализа
Второй тип оценок направлен на формирование как можно более
точных соотношений для предсказания времени выполнения
алгоритмов. Получение таких оценок проводится, как правило, при
помощи уточнения выражений, полученных на первом этапе. Для этого
в имеющиеся соотношения вводятся параметры, задающие длительность
выполнения операций, строятся оценки трудоемкости
коммуникационных операций, указываются все необходимые константы.
Точность получаемых выражений проверяется при помощи
вычислительных экспериментов, по результатам которых время
выполненных расчетов сравнивается с теоретически предсказанными
оценками длительностей вычислений. Как результат, оценки
подобного типа имеют, как правило, более сложный вид, но
позволяют более точно оценивать
Рассмотрим трудоемкость алгоритма умножения матрицы на вектор.
В случае если матрица А квадратная ( m=n ), последовательный
алгоритм умножения матрицы на вектор имеет сложность T1=n2. В
случае параллельных вычислений каждый процессор производит
умножение только части (полосы) матрицы A на вектор b, размер
этих полос равен n/p строк. При вычислении скалярного
произведения одной строки матрицы и вектора необходимо произвести n операций умножения и (n-1) операций сложения. Следовательно,
вычислительная трудоемкость параллельного алгоритма определяется
выражением:$$T_p=n^2/p.$$
С учетом этой оценки показатели
Построенные выше оценки времени вычислений выражены в
количестве операций и, кроме того, определены без учета затрат на
выполнение
(здесь и далее операция $$\lcei\ \rceil$$ есть округление до целого в большую сторону).
Оценка трудоемкости операции обобщенного сбора данных уже
выполнялась в лекции 4 (см. п.
4.3.4). Как уже отмечалась ранее, данная операция может быть
выполнена за $$\lceil$$ log2p $$\rceil$$ w есть размер одного элемента вектора c в байтах), на второй итерации этот объем
увеличивается вдвое и оказывается равным $$2w\lceil n/p \rceil$$ и т.д. Как результат,
длительность выполнения операции сбора данных при использовании
модели Хокни может быть определена при помощи следующего
выражения$$T_p(comm) = \sum_{i=1}^{\lceil \log_2 p \rceil}
(\alpha + 2^{i-1}w\lceil n/p \rceil / \beta) =
\alpha \lceil \log_2 p \rceil +
w \lceil n/p \rceil (2^{\lceil \log_2 p \rceil} - 1)/\beta,$$
где $$\alpha$$ – латентность сети передачи данных, $$\beta$$ –
пропускная способность сети. Таким образом, общее время
выполнения параллельного алгоритма составляет$$T_p=(n/p)\cdot (2n-1) \cdot \tau + \alpha \cdot \log_2 p +w(n/p)(p-1)/ \beta$$
(для упрощения выражения в (6.8) предполагалось, что значения n/p и log2p являются
целыми).
Представим возможный вариант параллельной программы умножения матрицы на вектор с использованием алгоритма разбиения матрицы по строкам. При этом реализация отдельных модулей не приводится, если их отсутствие не оказывает влияние на понимании общей схемы параллельных вычислений.
1. Главная функция программы. Реализует логику работы алгоритма, последовательно вызывает необходимые подпрограммы.
// Программа 6.1
// Умножение матрицы на вектор – ленточное горизонтальное разбиение
// (исходный и результирующий векторы дублируются между процессами)
void main(int argc, char* argv[]) {
double* pMatrix; // Первый аргумент – исходная матрица
double* pVector; // Второй аргумент – исходный вектор
double* pResult; // Результат умножения матрицы на вектор
int Size; // Размеры исходных матрицы и вектора
double* pProcRows;
double* pProcResult;
int RowNum;
double Start, Finish, Duration;
MPI_Init(argc, argv);
MPI_Comm_size(MPI_COMM_WORLD, ProcNum);
MPI_Comm_rank(MPI_COMM_WORLD, ProcRank);
// Выделение памяти и инициализация исходных данных
ProcessInitialization(pMatrix, pVector, pResult, pProcRows,
pProcResult, Size, RowNum);
// Распределение исходных данных между процессами
DataDistribution(pMatrix, pProcRows, pVector, Size, RowNum);
// Параллельное выполнение умножения матрицы на вектор
ParallelResultCalculation(pProcRows, pVector, pProcResult,
Size, RowNum);
// Сбор результирующего вектора на всех процессах
ResultReplication(pProcResult, pResult, Size, RowNum);
// Завершение процесса вычислений
ProcessTermination(pMatrix, pVector, pResult, pProcRows,
pProcResult);
MPI_Finalize();
}
2. Функция ProcessInitialization. Эта функция задает
размер и элементы для матрицы A и вектора b. Значения для матрицы A и вектора b определяются в функции RandomDataInitialization.
// Функция для выделения памяти и инициализации исходных данных
void ProcessInitialization (double* pMatrix, double* pVector,
double* pResult, double* pProcRows, double* pProcResult,
int Size, int RowNum) {
int RestRows; // Количество строк матрицы, которые еще
// не распределены
int i;
if (ProcRank == 0) {
do {
printf("\nВведите размер матрицы: ");
scanf("%d", Size);
if (Size < ProcNum) {
printf("Размер матрицы должен превышать количество
процессов! \n ");
}
}
while (Size < ProcNum);
}
MPI_Bcast(Size, 1, MPI_INT, 0, MPI_COMM_WORLD);
RestRows = Size;
for (i=0; i<ProcRank; i++)
RestRows = RestRows-RestRows/(ProcNum-i);
RowNum = RestRows/(ProcNum-ProcRank);
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];
RandomDataInitialization(pMatrix, pVector, Size);
}
}
3. Функция DataDistribution. Осуществляет рассылку
вектора b и распределение строк исходной матрицы A по процессам вычислительной системы. Следует
отметить, что когда количество строк матрицы n не
является кратным числу процессоров p, объем
пересылаемых данных для процессов может оказаться разным и для
передачи сообщений необходимо использовать функцию MPI_Scatterv библиотеки
// Функция для распределения исходных данных между процессами
void DataDistribution(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);
// Выделение памяти для хранения временных объектов
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 -= RowNum;
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;
}
Следует отметить, что такое разделение действий генерации исходных данных и их рассылки между процессами может быть неоправданным в реальных параллельных вычислениях при большом объеме данных. Широко используемый подход в таких случаях состоит в организации передачи сообщений процессам сразу же после того, как данные процессов будут сгенерированы. Снижение затрат памяти для хранения данных может быть достигнуто также и за счет организации генерации данных в последнем процессе (при таком подходе память для пересылаемых данных и для данных процесса может быть одной и той же).
4. Функция ParallelResultCaculation. Данная функция
производит умножение на вектор тех строк матрицы, которые
распределены на данный процесс, и таким образом получается блок
результирующего вектора с.
// Функция для вычисления части результирующего вектора
void ParallelResultCalculation(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] += pProcRows[i*Size+j]*pVector[j];
}
}
5. Функция ResultReplication. Объединяет блоки
результирующего вектора с, полученные на разных
процессах, и копирует вектор результата на все процессы
вычислительной системы.
// Функция для сбора результирующего вектора на всех процессах
void ResultReplication(double* pProcResult, double* pResult,
int Size, int RowNum) {
int *pReceiveNum; // Количество элементов, посылаемых процессом
int *pReceiveInd; // Индекс элемента данных в результирующем
// векторе
int RestRows=Size; // Количество строк матрицы, которые еще не
// распределены
int i;
// Выделение памяти для временных объектов
pReceiveNum = new int [ProcNum];
pReceiveInd = new int [ProcNum];
// Определение положения блоков результирующего вектора
pReceiveInd[0] = 0;
pReceiveNum[0] = Size/ProcNum;
for (i=1; i<ProcNum; i++) {
RestRows -= pReceiveNum[i-1];
pReceiveNum[i] = RestRows/(ProcNum-i);
pReceiveInd[i] = pReceiveInd[i-1]+pReceiveNum[i-1];
}
// Сбор всего результирующего вектора на всех процессах
MPI_Allgatherv(pProcResult, pReceiveNum[ProcRank],
MPI_DOUBLE, pResult, pReceiveNum, pReceiveInd,
MPI_DOUBLE, MPI_COMM_WORLD);
// Освобождение памяти
delete [] pReceiveNum;
delete [] pReceiveInd;
}
Рассмотрим результаты
Определение параметров теоретических зависимостей (величин $$\tau , w, \alpha , \beta$$ ) осуществлялось следующим
образом. Для оценки длительности $$\tau$$ базовой скалярной операции
проводилось решение задачи умножения матрицы на вектор при помощи
последовательного алгоритма и полученное таким образом время
вычислений делилось на общее количество выполненных операций – в
результате подобных экспериментов для величины $$\tau$$ было
получено значение 1,93 нсек. Эксперименты, выполненные для
определения параметров сети передачи данных, показали значения
латентности $$\alpha$$ и пропускной способности $$\beta$$
соответственно 47 мкс и 53,29 Мбайт/с. Все вычисления
производились над числовыми значениями типа double,
т.е. величина w равна 8 байт.
Результаты
| Размер матрицы | Последовательный алгоритм | Параллельный алгоритм | |||||
|---|---|---|---|---|---|---|---|
| 2 процессора | 4 процессора | 8 процессоров | |||||
| Время | Ускорение | Время | Ускорение | Время | Ускорение | ||
| 1000 | 0,0041 | 0,0021 | 1,8798 | 0,0017 | 2,4089 | 0,0175 | 0,2333 |
| 2000 | 0,016 | 0,0084 | 1,8843 | 0,0047 | 3,3388 | 0,0032 | 4,9443 |
| 3000 | 0,031 | 0,0185 | 1,6700 | 0,0097 | 3,1778 | 0,0059 | 5,1952 |
| 4000 | 0,062 | 0,0381 | 1,6263 | 0,0188 | 3,2838 | 0,0244 | 2,5329 |
| 5000 | 0,11 | 0,0574 | 1,9156 | 0,0314 | 3,4993 | 0,0150 | 7,3216 |
Сравнение экспериментального времени $$T'_p$$
выполнения параллельного алгоритма и теоретического времени Tp, вычисленного в соответствии с
выражением (6.8), представлено в
таблице 6.2 и в графическом
виде на рис. 6.3 и
6.4.
| Размер объектов | 2 процессора | 4 процессора | 8 процессоров | |||
|---|---|---|---|---|---|---|
Tp |
T'p |
Tp |
T'p |
Tp |
T'p | |
| 1000 | 0,0069 | 0,0021 | 0,0108 | 0,0017 | 0,0152 | 0,0175 |
| 2000 | 0,0132 | 0,0084 | 0,0140 | 0,0047 | 0,0169 | 0,0032 |
| 3000 | 0,0235 | 0,0185 | 0,0193 | 0,0097 | 0,0196 | 0,0059 |
| 4000 | 0,0379 | 0,0381 | 0,0265 | 0,0188 | 0,0233 | 0,0244 |
| 5000 | 0,0565 | 0,0574 | 0,0359 | 0,0314 | 0,0280 | 0,0150 |

(рис 6.4) График зависимости экспериментального T'p и теоретического Tp времени выполнения параллельного алгоритма на двух процессорах от объема исходных данных (ленточное разбиение матрицы по строкам)(рис 6.3) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма умножения матрицы на вектор (ленточное разбиение по строкам) для разных размеров матриц
Рассмотрим теперь другой подход к параллельному умножению матрицы на вектор, основанный на разделении исходной матрицы на непрерывные наборы (вертикальные полосы) столбцов.
При таком способе разделения данных в качестве базовой
подзадачи может быть выбрана операция умножения столбца матрицы А на один из элементов вектора b. Для
организации вычислений в этом случае каждая базовая подзадача i, 0<=i<n, должна содержать i -й
столбец матрицы А и i -е элементы bi и ci векторов b и с.
Параллельный алгоритм умножения матрицы на вектор начинается с
того, что каждая базовая задача i выполняет
умножение своего столбца матрицы А на элемент bi, в итоге в каждой подзадаче получается
вектор c'(i) промежуточных результатов. Далее для
получения элементов результирующего вектора с подзадачи должны
обменяться своими промежуточными данными между собой (элемент j, 0<=j<n, c'(i) подзадачи i, 0<=i<n, должен
быть передан подзадаче j ). Данная обобщенная
передача данных ( all-to-all communication
или total exchange ) является наиболее
общей коммуникационной процедурой и может быть реализована при
помощи функции MPI_Alltoall библиотеки i, 0<=i<n, будет содержать n
частичных значений c'i(j), 0<=j<n,
сложением которых и определяется элемент ci вектора результата с (см.
рис. 6.5).
(рис 6.5) Организация вычислений при выполнении параллельного алгоритма умножения матрицы на вектор с использованием разбиения матрицы по столбцам
Выделенные базовые подзадачи характеризуются одинаковой
вычислительной трудоемкостью и равным объемом передаваемых
данных. В случае когда количество столбцов матрицы превышает
число процессоров, базовые подзадачи можно укрупнить, объединив в
рамках одной подзадачи несколько соседних столбцов (в этом случае
исходная матрица A разбивается на ряд вертикальных
полос). При соблюдении равенства размера полос такой способ
агрегации вычислений обеспечивает равномерность распределения
вычислительной нагрузки по процессорам, составляющим
многопроцессорную вычислительную систему.
Как и в предыдущем алгоритме, распределение подзадач между процессорами вычислительной системы может быть выполнено произвольным образом.
Пусть, как и ранее, матрица А является
квадратной, то есть m=n. На первом этапе вычислений
каждый процессор умножает принадлежащие ему столбцы матрицы А на элементы вектора b, после
умножения полученные значения суммируются для каждой строки
матрицы А в отдельности$$c'_s(i)= \sum_{j=j_0}^{j_{l-1}} a_{sj} b_j, \; 0 \le s < n ,$$
( j0 и jl-1 есть
начальный и конечный индексы столбцов базовой подзадачи i, 0<=i<n). Поскольку размеры полосы матрицы А и блока вектора b равны n/p, то трудоемкость таких вычислений может
оцениваться как T'= n2/p операций. После
обмена данными между подзадачами на втором этапе вычислений
каждый процессор суммирует полученные значения для своего блока
результирующего вектора c. Количество суммируемых
значений для каждого элемента ci вектора c совпадает с числом процессоров p,
размер блока вектора c на одном процессоре равен n/p, и, тем самым, число выполняемых операций для
второго этапа оказывается равным T''=n. С учетом
полученных соотношений показатели
Теперь рассмотрим более точные соотношения для оценки времени выполнения параллельного алгоритма. С учетом ранее проведенных рассуждений время выполнения вычислительных операций алгоритма может быть оценено при помощи выражения$$T_p(cacl)=[n \cdot (2 \cdot \lceil n/p \rceil - 1) + n] \cdot \tau$$ (здесь, как и ранее, $$\tau$$ есть время выполнения одной элементарной скалярной операции).
Для выполнения операции обобщенной передачи данных рассмотрим
два возможных способа реализации (см. также
лекцию 3). Первый способ
обеспечивается алгоритмом, согласно которому каждый процессор
последовательно передает свои данные всем остальным процессорам
вычислительном системы. Предположим, что процессоры могут
одновременно отправлять и принимать сообщения и между любой парой
процессоров имеется прямая линия связи, тогда оценка трудоемкости
(время исполнения) такого алгоритма обобщенной передачи данных
может быть определена как$$T_p^1(comm) = (p-1)(\alpha +w \lceil {n/p}\rceil / \beta).$$
(напомним, что $$\alpha$$ – латентность сети передачи данных, $$\beta$$
– пропускная способность сети, w – размер элемента
данных в байтах).
Второй способ выполнения операции обмена данными рассмотрен в
лекции 3, когда топология
вычислительной сети может быть представлена в виде гиперкуба. Как
было показано, выполнение такого алгоритма может быть
осуществлено за $$\lceil log_2p\rceil$$
шагов, на каждом из которых каждый процессор передает и получает
сообщение из n/2 элементов. Как результат, время
С учетом (6.11) – (6.13) общее время выполнения параллельного алгоритма умножения матрицы на вектор при разбиении данных по столбцам выражается следующими соотношениями.
| Размер матрицы | Последовательный алгоритм | Параллельный алгоритм | |||||
|---|---|---|---|---|---|---|---|
| 2 процессора | 4 процессора | 8 процессоров | |||||
| Время | Ускорение | Время | Ускорение | Время | Ускорение | ||
| 1000 | 0,0041 | 0,0022 | 1,8352 | 0,0015 | 3,1538 | 0,0008 | 4,9409 |
| 2000 | 0,016 | 0,0085 | 1,8799 | 0,0046 | 3,4246 | 0,0029 | 5,4682 |
| 3000 | 0,031 | 0,019 | 1,6315 | 0,0095 | 3,2413 | 0,0055 | 5,5456 |
| 4000 | 0,062 | 0,0331 | 1,8679 | 0,0168 | 3,6714 | 0,0090 | 6,8599 |
| 5000 | 0,11 | 0,0518 | 2,1228 | 0,0265 | 4,1361 | 0,0136 | 8,0580 |
Сравнение экспериментального времени $$T_p^*$$
выполнения эксперимента и времени Tp,
вычисленного по соотношениям (6.14), (6.15), представлено в
таблице 6.4 и на рис.
6.6 и
6.7. Теоретическое время $$T_p^1$$ вычисляется согласно (6.14), а теоретическое
время $$T_p^2$$ – в соответствии с (6.15).
(рис 6.6) График зависимости теоретического и экспериментального времени выполнения параллельного алгоритма на четырех процессорах от объема исходных данных (ленточное разбиение матрицы по столбцам)| Размер матрицы | 2 процессора | 4 процессора | 8 процессоров | ||||||
|---|---|---|---|---|---|---|---|---|---|
| $$T_p^*$$ | $$T_p^1$$ | $$T_p^2$$ | $$T_p^*$$ | $$T_p^1$$ | $$T_p^2$$ | $$T_p^*$$ | $$T_p^1$$ | $$T_p^2$$ | |
| 1000 | 0,0022 | 0,0021 | 0,0021 | 0,0013 | 0,0013 | 0,0014 | 0,0008 | 0,0011 | 0,0015 |
| 2000 | 0,0085 | 0,0082 | 0,0080 | 0,0046 | 0,0044 | 0,0044 | 0,0029 | 0,0027 | 0,0031 |
| 3000 | 0,019 | 0,0177 | 0,0177 | 0,0095 | 0,0094 | 0,0094 | 0,0055 | 0,0054 | 0,0056 |
| 4000 | 0,0331 | 0,0313 | 0,0313 | 0,0168 | 0,0163 | 0,0162 | 0,0090 | 0,0090 | 0,0091 |
| 5000 | 0,0518 | 0,0487 | 0,0487 | 0,0265 | 0,0251 | 0,0251 | 0,0136 | 0,0135 | 0,0136 |
(рис 6.7) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма умножения матрицы на вектор (ленточное разбиение матрицы по столбцам) для разных размеров матриц
Рассмотрим теперь параллельный алгоритм умножения матрицы на вектор, который основан на ином способе разделения данных – на разбиении матрицы на прямоугольные фрагменты ( блоки ).
Блочная схема разбиения матриц подробно рассмотрена в
подразделе 6.1. При таком способе разделения данных исходная
матрица A представляется в виде набора прямоугольных
блоков:$$A =
\begin{pmatrix}
A_{00} A_{02} \ldots A_{0q-1} \\
\ldots \\
A_{s-11} A_{s-12} \ldots A_{s-1q-1}
\end{pmatrix} ,$$
где Aij, 0<=i<s, 0<=j<q, есть блок матрицы:$$A =
\begin{pmatrix}
a_{i_0 j_0} a_{i_0 j_1} \ldots a_{i_0 j_{l-1}} \\
\ldots \\
a_{i_{k-1} j_0} a_{i_{k-1} j_1} \ldots a_{i_{k-1} j_{l-1}}
\end{pmatrix} ,
\begin{aligned}
i_{\nu} = ik+\nu, 0 \le \nu < k,\ k=m/s, \\
j_u = jl+u, 0 \le u \le l,\ l=n/q.
\end{aligned}$$
(здесь, как и ранее, предполагается, что p=sxq, количество строк матрицы является
кратным s, а количество столбцов – кратным q, то есть m=kxs и n=lxq ).
При использовании блочного представления матрицы A базовые подзадачи целесообразно определить на
основе вычислений, выполняемых над матричными блоками. Для
нумерации подзадач могут применяться индексы располагаемых в
подзадачах блоков матрицы A, т.е. подзадача (i,j) содержит блок Aij.
Помимо блока матрицы A каждая подзадача должна
содержать также и блок вектора b. При этом для
блоков одной и той же подзадачи должны соблюдаться определенные
правила соответствия – операция умножения блока матрицы A ij может быть выполнена только, если
блок вектора b'(i,j) имеет вид$$b'(i,j)=(b'_0(i,j),\ldots,b'_l-1(i,j)),\quad \text{где} \quad
b'_u(i,j)=b_{j_u} , \; j_u=jl+u, \; 0\le u < l, \; l=n/q.$$
Рассмотрим общую схему параллельных вычислений для операции
умножения матрицы на вектор при блочном разделении исходных
данных. После перемножения блоков матрицы A и
вектора b каждая подзадача (i,j) будет
содержать вектор c'(i,j),
определяемый в соответствии с выражениями$$c'_\nu(i,j)=\sum_{u=0}^{l-1} a_{i_{\nu} j_u} b_{j_u}, \;
i_{\nu}=i+\nu, \; 0 \le \nu < k, \; k=m/s, \; j_u=jl+u, \;
0 \le u \le l, l=n/q$$
Поэлементное суммирование векторов A позволяет получить результирующий вектор c$$c_{\eta} = \sum_{j=0}^{q-1} c'_{\nu}(i,j), \; 0 \le \eta < m, \;
i=\eta/s, \; \nu = \eta - i \cdots .$$
Для размещения вектора c применим ту же схему,
что и для исходного вектора b: организуем вычисления
таким образом, чтобы при завершении расчетов вектор c
располагался поблочно в каждой из вертикальных полос блоков
матрицы A (тем самым, каждый блок вектора c должен
быть продублирован по каждой горизонтальной полосе). Выполнение
всех необходимых действий для этого – суммирование частичных
результатов и дублирование блоков результирующего вектора — может
быть обеспечено при помощи функции MPI_Allreduce
библиотеки
Общая схема выполняемых вычислений для умножения матрицы на вектор при блочном разделении данных показана на рис. 6.8.
(рис 6.8) Общая схема параллельного алгоритма умножения матрицы на вектор при блочном разделении данных: a) исходное распределение результатов, б) распределение векторов частичных результатов, в) распределение блоков результирующего вектора cРассмотрев представленную схему параллельных вычислений, можно
сделать вывод, что информационная зависимость базовых подзадач
проявляется только на этапе суммирования результатов перемножения
блоков матрицы A и блоков вектора b.
Выполнение таких расчетов может быть выполнено по обычной
Размер блоков матрицы А может быть подобран таким образом,
чтобы общее количество базовых подзадач совпадало с числом
процессоров p. Так, например, если определить размер блочной
решетки как p=sxq, то
k=m/s, l=n/q,
где k и l есть количество строк и
столбцов в блоках матрицы А. Такой способ
определения размера блоков приводит к тому, что объем вычислений
в каждой подзадаче является равным, и, тем самым, достигается
полная балансировка вычислительной нагрузки между
процессорами.
Возможность выбора остается при определении размеров блочной
структуры матрицы А. Большое количество блоков по горизонтали
приводит к возрастанию числа итераций в операции редукции
результатов блочного умножения, а увеличение размера блочной
решетки по вертикали повышает объем передаваемых данных между
процессорами. Простое, часто применяемое решение состоит в
использовании одинакового количества блоков по вертикали и
горизонтали, т.е.$$s=q=\sqrt{p}$$
Следует отметить, что блочная схема разделения данных является
обобщением всех рассмотренных в данной лекции подходов.
Действительно, при q=1 блоки сводятся к
горизонтальным полосам матрицы А, при s=1 исходные данные разбиваются на вертикальные
полосы.
При решении вопроса распределения подзадач между процессорами должна учитываться возможность эффективного выполнения операции редукции. Возможный вариант подходящего способа распределения состоит в выделении для подзадач одной и той же горизонтальной полосы блоков множества процессоров, структура сети передачи данных между которыми имеет вид гиперкуба или полного графа.
Выполним анализ А является квадратной, т.е. m=n. Будем предполагать также, что процессоры,
составляющие многопроцессорную вычислительную систему, образуют
прямоугольную решетку p=sxq ( s –
количество строк в процессорной решетке, q –
количество столбцов).
Общий анализ
Для уточнения полученных соотношений оценим более точно
количество вычислительных операций алгоритма и учтем затраты на
выполнение
Общее время умножения блоков матрицы А и вектора b может быть определено как$$T_p(calc) = \lceil n/s \rceil \cdot (2 \cdot \lceil n/q \rceil - 1) \cdot \tau .$$
Операция редукции данных может быть выполнена с использованием
log2q итераций передачи сообщений размера $$w\lceil n/s \rceil$$. Как результат, оценка
коммуникационных затрат параллельного алгоритма при использовании
модели Хокни может быть определена при помощи следующего
выражения$$T_p(comm)=(\alpha +w \lceil n/s \rceil / \beta) \lceil log_2 q \rceil$$
Таким образом, общее время выполнения параллельного алгоритма умножения матрицы на вектор при блочном разделении данных составляет$$T_p = \lceil n/s \rceil \cdot (2 \cdot \lceil n/q \rceil - 1) \cdot \tau + (\alpha + w \lceil n/s \rceil / \beta) \lceil \log_2 q \rceil .$$
Сравнение экспериментального времени $$T^*_p$$
выполнения эксперимента и теоретического времени T
p, вычисленного в соответствии с выражением (6.19),
представлено в таблице 6.5 и на
рис. 6.10.
| Размер матриц | Последовательный алгоритм | Параллельный алгоритм | |||
|---|---|---|---|---|---|
| 4 процессора | 9 процессоров | ||||
| Время | Ускорение | Время | Ускорение | ||
| 1000 | 0,0041 | 0,0028 | 1,4260 | 0,0011 | 3,7998 |
| 2000 | 0,016 | 0,0099 | 1,6127 | 0,0095 | 3,2614 |
| 3000 | 0,031 | 0,0214 | 1,4441 | 0,0095 | 3,2614 |
| 4000 | 0,062 | 0,0381 | 1,6254 | 0,0175 | 3,5420 |
| 5000 | 0,11 | 0,0583 | 1,8860 | 0,0263 | 4,1755 |
(рис 6.9) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма умножения матрицы на вектор (блочное разбиение матрицы) для разных размеров матриц| Размер матриц | Последовательный алгоритм | Параллельный алгоритм | ||
|---|---|---|---|---|
| $$T_p$$ | $$T_p^*$$ | $$T_p$$ | $$T_p^*$$ | |
| 1000 | 0,0025 | 0,0028 | 0,0012 | 0,0011 |
| 2000 | 0,0095 | 0,0099 | 0,0043 | 0,0042 |
| 3000 | 0,0212 | 0,0214 | 0,0095 | 0,0095 |
| 4000 | 0,0376 | 0,0381 | 0,0168 | 0,0175 |
| 5000 | 0,0586 | 0,0583 | 0,0262 | 0,0263 |
(рис 6.10) График зависимости экспериментального и теоретического времени проведения эксперимента на четырех процессорах от объема исходных данных (блочное разбиение матрицы)
В данной лекции на примере задачи умножения матрицы на вектор рассматриваются возможные схемы разделения матриц между процессорами многопроцессорной вычислительной системы, которые могут быть использованы для организации параллельных вычислений при выполнении матричных операций. В числе излагаемых схем способы разбиения матриц на полосы (по вертикали или горизонтали) или на прямоугольные наборы элементов ( блоки ).
Далее в лекции с использованием рассмотренных способов
разделения матриц подробно излагаются три возможных варианта
параллельного выполнения операции умножения матрицы на вектор.
Первый алгоритм основан на разделении матрицы между процессорами
по строкам, второй – на разделении матрицы по столбцам, а третий
– на блочном разделении данных. Каждый алгоритм представлен в
соответствии с общей схемой, описанной в
лекции 4, — вначале
определяются базовые подзадачи, затем выделяются информационные
зависимости подзадач, далее обсуждается масштабирование и
распределение подзадач между процессорами. В завершение для
каждого алгоритма проводится анализ
Полученные показатели
На рис. 6.11 на общем
графике представлены показатели
(рис 6.11) Показатели ускорения рассмотренных параллельных алгоритмов умножения по результатам вычислительных экспериментов с матрицами размера 2000x2000 и векторами из 2000 элементов
Задача умножения матрицы на вектор часто используется как
демонстрационный пример параллельного программирования и, как
результат, широко рассматривается в литературе. В качестве
дополнительного учебного материала могут быть рекомендованы
работы [, , ]. Широкое обсуждение вопросов параллельного
выполнения
При рассмотрении вопросов
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.