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

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

.doc
Скачиваний:
1
Добавлен:
09.09.2026
Размер:
565 Кб
Скачать
☆

МИНОБРНАУКИ РОССИИ

Санкт-Петербургский государственный

электротехнический университет

«ЛЭТИ» им. В. И. Ульянова (Ленина)

Кафедра МО ЭВМ

отчет

по лабораторной работе №7

по дисциплине «Параллельные алгоритмы»

Тема: Умножение матриц

Студентка гр. 2383

Бараева Е. Н.

Преподаватель

Татаринов Ю. С.

Санкт-Петербург

2024

Цель работы.

Реализация и сравнение последовательных и параллельных алгоритмов перемножения двух квадратных матриц. Измерение времени работы программ в зависимости от размера матриц и количества процессов. Анализ результатов работы с построением графика ускорения/замедления, а также с построением сети Петри.

Задание.

Вариант 4

Блочный алгоритм Фокса.

Выполнить задачу умножения двух квадратных матриц A и B размера m×m, результат записать в матрицу C. Реализовать последовательный и параллельный алгоритм, одним из перечисленных ниже способов и провести анализ полученных результатов. Выбор параллельного алгоритма определяется индивидуальным номером задания. Все числа в заданиях являются целыми. Матрицы должны вводиться и выводиться по строкам.

Выполнение работы.

Был реализован последовательный способ перемножения матриц. Асимптотическая оценка сложности алгоритма O(n^3), где n – размерность матрицы. Также был реализован блочный алгоритм Фокса для параллельного перемножения матриц. В программу подается размерность матриц, а затем и сами матрицы. Процессы организованы в двумерную декартову сетку, закольцованную по строкам и столбцам. Матрицы разбиваются на блоки одинакового размера, которые распределяются между процессами. Для упрощения передачи блоков матрицы используется новый типа данных. MPI_Type_vector описывает блоки матриц: данные размером block_size * block_size с шагом matrix_size (размер строки). Тип дополнительно изменяется с помощью MPI_Type_create_resized, чтобы данные имели корректный размер. Для передачи блоков матриц всем процессам используется 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

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 – Сеть Петри

Вывод параллельной программы на 4 процессах см. рис. 2.

Рисунок 2 – Вывод параллельной программы

Разработанный программный код см. в приложении А.

Исследование программы.

Проведём исследование на устройстве с 12 ядрами. При замерах за итоговое время программы взято максимальное время среди всех участвующих процессов. Не во всех случаях удавалось использовать матрицы необходимых размеров, в таких случаях бралось ближайшее число, нацело делящееся на необходимое количество процессов.

Построим график ускорения/замедления программы в зависимости от количества процессов см. на рис. 3.

Рисунок 3 – График ускорения/замедления программы

Построим график ускорения/замедления программы в зависимости от размер матриц см. на рис. 4.

Рисунок 4 – График ускорения/замедления программы

Вывод.

Были реализованы и исследованы два алгоритма перемножения квадратных матриц: последовательный и блочный алгоритм Фокса. Исследование показало, что время работы параллельной программы уменьшается при увеличении количества процессов. Эта тенденция сохраняется, пока количество процессов не начинает превышать количество доступных ядер. Также можно заметить, что параллельный алгоритм действует хуже, чем последовательный на некоторых очень маленьких матрицах. Это вполне логично, в таких случаях на обмен данными между процессами тратится больше времени, чем на сами вычисления. Однако с увеличением размера матрицы всё больше и больше становится очевидным преимущество параллельной реализации.

ПРИЛОЖЕНИЕ А ИСХОДНЫЙ КОД ПРОГРАММЫ

Название файла: 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();

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;

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;

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));

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]);

}

MPI_Bcast(&m, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&block_size, 1, MPI_INT, 0, MPI_COMM_WORLD);

}

int *local_a = malloc(block_size * block_size * sizeof(int)); int *local_b = malloc(block_size * block_size * sizeof(int));

int *local_c = calloc(block_size * block_size, sizeof(int));

MPI_Datatype block_type, resized_block_type; MPI_Type_vector(block_size, block_size, m, MPI_INT, &block_type);

MPI_Type_create_resized(block_type, 0, sizeof(int), &resized_block_type); MPI_Type_commit(&resized_block_type);

MPI_Comm cart_comm;

create_cartesian_topology(&cart_comm, p);

scatter_matrices(a, b, block_size, m, proc_rank, p, local_a, local_b, resized_block_type);

if (proc_rank == 0) start_time = MPI_Wtime();

fox_algorithm(cart_comm, block_size, p, local_a, local_b, local_c);

if (proc_rank == 0) {

end_time = MPI_Wtime(); printf("The program has taken %lf seconds\n", end_time - start_time);

}

gather_result(c, block_size, m, proc_rank, p, local_c, resized_block_type);

MPI_Barrier(MPI_COMM_WORLD);

printf("Матрица С:\n"); print_matrix(c, m);

if (proc_rank == 0) {

free(a);

free(b); free(c);

}

free(local_a); free(local_b);

free(local_c);

MPI_Comm_free(&cart_comm); MPI_Type_free(&block_type);

MPI_Type_free(&resized_block_type); MPI_Finalize();

return 0;

}

13

Соседние файлы в папке Лабы по ПарАлг 2024