Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лабы по ПарАлг 2024 / Baraeva_Elizaveta_lb7

.pdf
Скачиваний:
1
Добавлен:
09.09.2026
Размер:
397 Кб
Скачать
☆
МИНОБРНАУКИ РОССИИ
САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ
ЭЛЕКТРОТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ
«ЛЭТИ» ИМ. В. И. УЛЬЯНОВА (ЛЕНИНА)
Кафедра МО ЭВМ
ОТЧЕТ
по дисциплине «Параллельные алгоритмы»
Тема: Умножение матриц
Студентка гр. 2383
Бараева Е. Н.
Преподаватель
Татаринов Ю. С.
Санкт-Петербург
2024
2
Цель работы.
Реализация и сравнение последовательных и параллельных алгоритмов
перемножения двух квадратных матриц. Измерение времени работы
программ в зависимости от размера матриц и количества процессов. Анализ результатов работы с построением графика ускорения/замедления, а также с построением сети Петри.
Задание.
Вариант 4 Блочный алгоритм Фокса.
Выполнить задачу умножения двух квадратных матриц A и B размера
m×m, результат записать в матрицу C. Реализовать последовательный и параллельный алгоритм, одним из перечисленных ниже способов и провести анализ полученных результатов. Выбор параллельного алгоритма определяется индивидуальным номером задания. Все числа в заданиях являются целыми. Матрицы должны вводиться и выводиться по строкам.
Выполнение работы.
Был реализован последовательный способ перемножения матриц.
Асимптотическая оценка сложности алгоритма O(n^3), где n – размерность
матрицы. Также был реализован блочный алгоритм Фокса для параллельного
перемножения матриц. В программу подается размерность матриц, а затем и сами матрицы. Процессы организованы в двумерную декартову сетку, закольцованную по строкам и столбцам. Матрицы разбиваются на блоки одинакового размера, которые распределяются между процессами. Для
упрощения передачи блоков матрицы используется новый типа данных.
MPI_Type_vector описывает блоки матриц: данные размером block_size * block_size с шагом matrix_size (размер строки). Тип дополнительно изменяется с помощью MPI_Type_create_resized, чтобы данные имели
3
корректный размер. Для передачи блоков матриц всем процессам
используется MPI_Scatterv. send_counts и displs задают размеры и смещения блоков для каждого процесса.
Основная идея алгоритма Фокса состоит в том, чтобы минимизировать
объём пересылки данных между процессами, передавая блоки матриц только
внутри строк и столбцов. Для каждого процесса создаются два
коммуникатора: горизонтальный (объединяет все процессы в одной строке
топологии) и вертикальный (объединяет процессы в одном столбце
топологии). Использование декартовой топологии и коммуникаторов
позволяет минимизировать конфликтующие обмены между процессами.
Горизонтальная и вертикальная передача данных происходят независимо, что
увеличивает производительность.
После выполнения всех итераций алгоритма Фокса, локальные части
результирующей матрицы C собираются в процесс с рангом 0. Для этого используется функция MPI_Gatherv, которая собирает данные из всех
процессов в один массив в нулевом процессе.
Время выполнения последовательной программы с разной размерностью матриц приведено в таблице 1. Таблица 1 – Время выполнения последовательной программы
Размерность матриц
Время работы, с
Размерность матриц
Время работы, с
10
0.000006
250
0.060037
25
0.000083
500
0.441158
50
0.000684
750
1.500994
100
0.005431
1000
3.962494
Время выполнения параллельной программы с разной размерностью матриц и разным количеством процессов приведено в таблице 2. Таблица 2 – Время выполнения параллельной программы
Размерность
матриц
Количество процессов
4 9 16
25
64
10
0.000238
0.000216
0.000270
0.003323
0.266634
25
0.000354
0.000338
0.000394
0.000309
0.000342
50
0.000485
0.000425
0.000408
0.000429
0.004426
100
0.002808
0.001332
0.000839
0.000811
0.004583
4
250
0.017942
0.008636
0.005555
0.003534
0.008432
500
0.272445
0.124588
0.069963
0.045410
0.081532
750
0.946513
0.401541
0.223872
0.146274
0.258241
1000
2.154427
0.964227
0.561337
0.357300
0.578596
Сеть Петри см. рис. 1.
Рисунок 1 – Сеть Петри
5
Вывод параллельной программы на 4 процессах см. рис. 2.
Рисунок 2 – Вывод параллельной программы
Разработанный программный код см. в приложении А.
Исследование программы.
Проведём исследование на устройстве с 12 ядрами. При замерах за
итоговое время программы взято максимальное время среди всех
участвующих процессов. Не во всех случаях удавалось использовать матрицы
необходимых размеров, в таких случаях бралось ближайшее число, нацело
делящееся на необходимое количество процессов.
Построим график ускорения/замедления программы в зависимости от количества процессов см. на рис. 3.
6
Рисунок 3 – График ускорения/замедления программы
Построим график ускорения/замедления программы в зависимости от размер матриц см. на рис. 4.
Рисунок 4 – График ускорения/замедления программы
Вывод.
Были реализованы и исследованы два алгоритма перемножения
квадратных матриц: последовательный и блочный алгоритм Фокса.
Исследование показало, что время работы параллельной программы
уменьшается при увеличении количества процессов. Эта тенденция
сохраняется, пока количество процессов не начинает превышать количество
доступных ядер. Также можно заметить, что параллельный алгоритм
действует хуже, чем последовательный на некоторых очень маленьких
матрицах. Это вполне логично, в таких случаях на обмен данными между
процессами тратится больше времени, чем на сами вычисления. Однако с
увеличением размера матрицы всё больше и больше становится очевидным
преимущество параллельной реализации.
7
ПРИЛОЖЕНИЕ А
ИСХОДНЫЙ КОД ПРОГРАММЫ
Название файла: lb7_consistently.c
#include <stdio.h> #include <stdlib.h> #include <time.h> #include "mpi.h"
void print_matrix(int *matrix, int m) { for (int i = 0; i < m; i++) { for (int j = 0; j < m; j++) { printf("%d ", matrix[i * m + j]); } printf("\n"); } }
void multiply_matrix(int *a, int *b, int *c, int m) { for (int i = 0; i < m; i++) { for (int j = 0; j < m; j++) { c[i * m + j] = 0; for (int k = 0; k < m; k++) { c[i * m + j] += a[i * m + k] * b[k * m + j]; } } } }
int main(int argc, char **argv) { int proc_rank, num_procs; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &proc_rank); MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
int m; printf("Введите размер матрицы m: "); scanf("%d", &m);
int *a = malloc(m * m * sizeof(int)); int *b = malloc(m * m * sizeof(int)); int *c = malloc(m * m * sizeof(int));
printf("Введите матрицу A по строкам:\n"); for (int i = 0; i < m * m; i++) { scanf("%d", &a[i]); }
printf("Введите матрицу B по строкам:\n"); for (int i = 0; i < m * m; i++) { scanf("%d", &b[i]); }
double start_time = MPI_Wtime();
8
multiply_matrix(a, b, c, m); double end_time = MPI_Wtime();
printf("Матрица С:\n"); print_matrix(c, m);
printf("The program has taken %lf seconds\n", end_time -
start_time);
free(a); free(b); free(c);
MPI_Finalize(); return 0; }
Название файла: lb7_parallel.c
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <math.h> #include "mpi.h"
void print_matrix(int *matrix, int m) { for (int i = 0; i < m; i++) { for (int j = 0; j < m; j++) { printf("%d ", matrix[i * m + j]); } printf("\n"); } }
void create_cartesian_topology(MPI_Comm *cart_comm, int sqrt_size)
{
int dims[2] = {sqrt_size, sqrt_size}; int periods[2] = {1, 1}; MPI_Cart_create(MPI_COMM_WORLD, 2, dims, periods, 1,
cart_comm);
}
void scatter_matrices(int *a, int *b, int block_size, int matrix_size, int rank, int sqrt_size, int *local_a, int *local_b, MPI_Datatype block_type) {
int *send_counts = NULL;
int *displs = NULL;
if (rank == 0) {
send_counts = malloc(sqrt_size * sqrt_size * sizeof(int));
displs = malloc(sqrt_size * sqrt_size * sizeof(int));
for (int i = 0; i < sqrt_size; i++) {
for (int j = 0; j < sqrt_size; j++) {
int proc_rank = i * sqrt_size + j;
send_counts[proc_rank] = 1;
9
displs[proc_rank] = i * block_size * matrix_size + j * block_size;
}
}
}
MPI_Scatterv(a, send_counts, displs, block_type, local_a, block_size * block_size, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Scatterv(b, send_counts, displs, block_type, local_b, block_size * block_size, MPI_INT, 0, MPI_COMM_WORLD);
if (rank == 0) {
free(send_counts);
free(displs);
}
}
void gather_result(int *c, int block_size, int matrix_size, int rank, int sqrt_size, int *local_c, MPI_Datatype block_type) {
int *send_counts = malloc(sqrt_size * sqrt_size * sizeof(int));
int *displs = malloc(sqrt_size * sqrt_size * sizeof(int));
if (rank == 0) {
for (int i = 0; i < sqrt_size; i++) {
for (int j = 0; j < sqrt_size; j++) {
int proc_rank = i * sqrt_size + j;
send_counts[proc_rank] = 1;
displs[proc_rank] = i * block_size * matrix_size + j * block_size;
}
}
}
MPI_Gatherv(local_c, block_size * block_size, MPI_INT, c, send_counts, displs, block_type, 0, MPI_COMM_WORLD);
free(send_counts);
free(displs);
}
void fox_algorithm(MPI_Comm cart_comm, int block_size, int sqrt_size, int *local_a, int *local_b, int *local_c) {
int coords[2];
int rank;
MPI_Comm_rank(cart_comm, &rank);
MPI_Cart_coords(cart_comm, rank, 2, coords);
MPI_Comm row_comm, col_comm;
MPI_Comm_split(cart_comm, coords[0], coords[1], &row_comm);
MPI_Comm_split(cart_comm, coords[1], coords[0], &col_comm);
int *buffer_a = malloc(block_size * block_size * sizeof(int));
for (int k = 0; k < sqrt_size; k++) {
int root = (coords[0] + k) % sqrt_size;
10
if (coords[1] == root) {
for (int i = 0; i < block_size * block_size; i++) {
buffer_a[i] = local_a[i];
}
}
MPI_Bcast(buffer_a, block_size * block_size, MPI_INT, root, row_comm);
for (int i = 0; i < block_size; i++) {
for (int j = 0; j < block_size; j++) {
for (int l = 0; l < block_size; l++) {
local_c[i * block_size + j] += buffer_a[i * block_size + l] * local_b[l * block_size + j];
}
}
}
MPI_Status status;
MPI_Sendrecv_replace(local_b, block_size * block_size, MPI_INT, (coords[0] + 1) % sqrt_size, 0, (coords[0] - 1 + sqrt_size) % sqrt_size, 0, col_comm, &status);
}
free(buffer_a);
MPI_Comm_free(&row_comm);
MPI_Comm_free(&col_comm);
}
int main(int argc, char **argv) { int proc_rank, num_procs;
double start_time = 0.0, end_time; MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &proc_rank); MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
int p = (int) sqrt(num_procs);
int m, block_size;
if (num_procs != p * p) { if (proc_rank == 0) {
printf("Число процессоров должно быть квадратом целого числа\n"); }
MPI_Finalize(); return 1;
}
int *a = NULL; int *b = NULL;
int *c = NULL;
if (proc_rank == 0) { printf("Введите размер матрицы m: ");
scanf("%d", &m);
if (m % p != 0) { printf("Размер матрицы должен быть кратен квадрату из числа процессоров\n");
MPI_Finalize(); return 1;
}
block_size = m / p;
a = malloc(m * m * sizeof(int)); b = malloc(m * m * sizeof(int));
c = calloc(m * m, sizeof(int));
Соседние файлы в папке Лабы по ПарАлг 2024