МИНОБРНАУКИ РОССИИ
Санкт-Петербургский государственный
электротехнический университет
«ЛЭТИ» им. В.И. Ульянова (Ленина)
Кафедра МО ЭВМ
отчет
по лабораторной работе № 6
по дисциплине «Параллельные алгоритмы»
Тема: Умножение матриц.
Студентка гр. 3384 |
|
|
Преподаватель |
|
Татаринов Ю.С. |
Санкт-Петербург
2025
Цель работы.
Разработать модель параллельного умножения квадратных матриц, а также последовательный алгоритм. Провести тестирование корректности работы, выполнить серию вычислительных экспериментов, построить графики, провести анализ полученных результатов и построить сеть Петри.
Задание. (Вариант 3)
Выполнить задачу умножения двух квадратных матриц A и B размера m×m, результат записать в матрицу C. Реализовать последовательный алгоритм (на одном процессе) и параллельный блочным алгоритмом Кэннона и провести анализ полученных результатов. Все числа в заданиях являются целыми. Матрицы должны вводиться и выводиться по строкам.
Выполнение работы.
Описание алгоритма выполнения работы.
Разбиение задачи основано на блочном алгоритме Кэннона. Исходные матрицы дополняются нулями до размера, который равномерно делится между всеми процессами, после чего разбиваются на одинаковые квадратные блоки. Эти блоки образуют логическую сетку процессов размера q на q, где q - квадратный корень из числа процессов. Каждый процесс получает один блок матрицы A и один блок матрицы B, а затем формирует соответствующую часть результирующей матрицы C. Таким образом, всё умножение реализовано как набор одинаковых независимых локальных умножений блоков, и каждый процесс отвечает только за свой участок итоговой матрицы.
Взаимодействие процессов построено по схеме обменов, характерной для алгоритма Кэннона. Все процессы организуются в двумерную декартовую сетку с циклическими связями. Это позволяет выполнять стандартные циклические сдвиги блоков A и B между соседними процессами. Сначала выполняется начальное выравнивание: блоки A сдвигаются влево вдоль строки на число позиций, равное номеру строки процесса, а блоки B сдвигаются вверх вдоль столбца на число позиций, равное номеру столбца процесса. Затем выполняется цикл из q шагов. На каждом шаге процесс умножает свои текущие блоки A и B, добавляет результат в локальный блок C, после чего снова сдвигает A влево, а B вверх. Циклическая топология гарантирует, что все блоки последовательно проходят через все процессы без глобальных пересылок, используя только локальные обмены sendrecv.
Распределение вычислений между процессами равномерное. Каждый процесс получает блоки одинакового размера и выполняет одинаковое количество умножений. На каждом шаге процесс выполняет одно блочное умножение и участвует в двух локальных обменах данных - один для блока A и один для блока B. Использование дополнения матриц нулями позволяет сохранить одинаковый объем работы даже в том случае, если исходный размер матрицы не делится на q.
Сеть Петри.
Рисунок 3 – Сеть Петри
Сеть Петри для четырёх процессов представлена на рисунке 3. В сети существуют Init_i, соответствующие начальному состоянию каждого процесса i. Эти места связаны с общим переходом create_grid, который моделирует создание двумерной сетки процессов. Срабатывание create_grid переводит процессы из состояния инициализации в GridR_i. Появление процесса в GridR_i означает, что он включён в топологию и готов участвовать в коллективных обменах.
Следующий шаг моделируется переходом scatter_data, который переводит процессы из GridR_i в Data_i. Этот шаг соответствует фазе распределения входных данных - исходных блоков матриц A и B. На этом этапе выполняется нарезка матриц на фрагменты, а затем каждый процесс получает свой блок, либо через операции MPI_Recv, либо копированием из локальной памяти.
Для выравнивания данных перед первым вычислительным шагом используется initial_align, который перемещает процессы из Data_i в Align_i. Этот этап моделирует ключевую операцию алгоритма Кэннона - первоначальный сдвиг блоков: каждый процесс передаёт свой блок A соседу по строке на число позиций, равное номеру своей строки, а блок B передает соседу по столбцу на число позиций, равное номеру столбца.
Далее следуют локальные вычисления первого шага. Для каждого процесса i существует переход compute_1_i, связанный с Align_i и переводящий процесс в Calc_1_i. Это соответствует выполнению локального умножения блоков A и B процессом i и добавлению первого фрагмента в свой участок результирующей матрицы C.
После выполнения первого умножения выполняется обмен блоками, моделируемый shift_1, который переводит процессы из Calc_1_i в Shift_1_i. Этот этап соответствует обмену: блок A передаётся соседу слева, а блок B - соседу сверху. В программе это соответствует двум вызовам MPI_Sendrecv_replace.
Затем начинается второй вычислительный шаг, моделируемый переходами compute_2_i, которые переводят процессы из Shift_1_i в Calc_2_i. Здесь процессы выполняют второе локальное умножение с новыми блоками A и B, накопив очередной вклад в результирующую матрицу C.
Завершающий обмен блоками отражён переходом shift_2, который переводит процессы из Calc_2_i в Shift_2_i. В алгоритме Кэннона это финальный шаг циркуляции блоков, после которого данные находятся в согласованном состоянии, и процессы переходят к завершению вычислительного цикла.
Для каждого процесса существует finish_i, который переводит его из Shift_2_i в Done_i. Нахождение процесса в Done_i означает, что он завершил все локальные вычисления и сформировал свой фрагмент результирующей матрицы.
Анализ эффективности алгоритма.
Анализ теоретического времени выполнения алгоритма основывается на детальном рассмотрении его структуры и параметров. Исходными данными являются размер матрицы m, количество процессов p, которое должно быть полным квадратом, так что p = q², и размер блока, вычисляемый как bs = m/q, причем для упрощения будем считать, что m делится на q без остатка. Каждый процесс в сетке q×q выполняет локальные вычисления и обмены данными в соответствии с алгоритмом.
Вычислительная часть времени определяется операциями умножения блоков. На каждой из q итераций основного цикла процесс перемножает два локальных блока A и B размером bs×bs, используя стандартный тройной вложенный цикл. Такое умножение требует 2·bs³ арифметических операций (bs³ умножений и bs³ сложений). Поскольку количество итераций равно q, то общее количество операций на процесс составляет 2·bs³·q. Подставляя bs = m/q, получаем 2·(m/q)³·q=2m³/q² операций. Если обозначить t_c - среднее время выполнения одной элементарной операции, включая доступ к памяти, то полное вычислительное время T_comp = (2m³/q²)·t_c.
Коммуникационная часть времени складывается из пересылок блоков между процессами. Алгоритм включает фазу начального выравнивания, где блок матрицы A циклически сдвигается влево на количество позиций, равное номеру строки процесса, а блок B сдвигается вверх на количество позиций, равное номеру столбца. Это приводит к двум операциям пересылки блока размером bs². Далее в основном цикле выполняются q итераций, и на каждой итерации после локального умножения происходят два циклических сдвига: блока A влево и блока B вверх на одну позицию, что добавляет 2q пересылок. Таким образом, общее количество пересылок на процесс: 2+2q= 2(q+1). Каждая пересылка передает bs² элементов. В модели передачи сообщений время одной пересылки выражается как t_s + bs²·t_w, где t_s - время старта передачи, а t_w - время передачи одного слова данных. Следовательно, общее коммуникационное время равно T_comm = 2(q+1)·[t_s + bs²·t_w] = 2(q+1)·[t_s + (m/q)²·t_w].
Суммируя вычислительную и коммуникационную составляющие, получаем теоретическое время выполнения: T_par = T_comp + T_comm = (2m³/q²)·t_c + 2(q+1)·[t_s + (m/q)²·t_w]. В этой формуле первый член отражает уменьшение вычислительной нагрузки с ростом числа процессов, а второй член показывает увеличение накладных расходов на коммуникации.
Результаты вычислительных экспериментов.
На таблице 1 представлено тестирование программы на матрицах разных размерностей и разном числе процессов.
Таблица 1 – тестирование программы
Размерность матриц (m) |
Последовательный алгоритм |
4 процесса |
16 процессов |
64 процесса |
||||
|
время |
время |
ускорение |
время |
ускорение |
время |
ускорение |
|
10 |
0.000003 |
0.000013 |
0.300 |
0.000184 |
0.019 |
0.002314 |
0.001 |
|
50 |
0.000255 |
0.000691 |
0.689 |
0.000294 |
0.986 |
0.003227 |
0.089 |
|
100 |
0.002263 |
0.001012 |
1.591 |
0.000849 |
2.392 |
0.003033 |
0.058 |
|
200 |
0.015711 |
0.006576 |
2.166 |
0.004939 |
3.679 |
0.009492 |
1.396 |
|
500 |
0.251331 |
0.063987 |
4.383 |
0.042759 |
6.823 |
0.046209 |
5.802 |
|
1000 |
1.908656 |
0.490530 |
3.918 |
0.278912 |
6.814 |
0.282147 |
6.821 |
|
5000 |
237.6682 |
59.55804 |
3.971 |
32.44034 |
7.718 |
31.00423 |
7.622 |
|
На рисунке 2 изображен график ускорения в зависимости от количества процессов.
Рисунок 2 – График ускорения
Разработанный программный код см. в Приложении А.
Тестирование программы см. в Приложении В.
