From 26df1a6c2b2291dd1d88b480366c06702516af1c Mon Sep 17 00:00:00 2001 From: Kamil Haliullov Date: Sun, 29 Dec 2024 14:37:32 +0400 Subject: [PATCH] haliullov_kamil_lab_5 is ready --- haliullov_kamil_lab_5/README.md | 30 +++++++++++++ haliullov_kamil_lab_5/project/main.py | 61 ++++++++++++++++++++++++++ haliullov_kamil_lab_5/result.png | Bin 0 -> 17695 bytes 3 files changed, 91 insertions(+) create mode 100644 haliullov_kamil_lab_5/README.md create mode 100644 haliullov_kamil_lab_5/project/main.py create mode 100644 haliullov_kamil_lab_5/result.png diff --git a/haliullov_kamil_lab_5/README.md b/haliullov_kamil_lab_5/README.md new file mode 100644 index 0000000..f597e3c --- /dev/null +++ b/haliullov_kamil_lab_5/README.md @@ -0,0 +1,30 @@ +# Лабораторная работа №5 - Параллельное умножение матриц + +## Задание + + +* Кратко: реализовать умножение двух больших квадратных матриц. + +* Подробно: в лабораторной работе требуется сделать два алгоритма: обычный и параллельный (задание со * - реализовать это в рамках одного алгоритма). В параллельном алгоритме предусмотреть ручное задание количества потоков (число потоков = 1 как раз и реализует задание со *), каждый из которых будет выполнять умножение элементов матрицы в рамках своей зоны ответственности. + +## Работа программы: + + 1. Генерируются случайные матрицы A и B заданных размеров. + + 2. multiply_matrices_sequential(A, B) умножает две матрицы A и B последовательно, используя вложенные циклы для вычисления элементов результирующей матрицы C. + + 3. multiply_matrices_parallel(A, B, num_workers) выполняет параллельное умножение матриц с использованием пула процессов. + + 4. benchmark(matrix_size, num_workers) Измеряет время выполнения операций умножения. И выводит результат в консоль. + +### Результат: + +![](result.png "") + +### Вывод: + +Параллельный подход может быть более быстрым, чем последовательный на матрицах большого размера, так как он позволяет производить более эффективное распределение работы между процессами. Однако на маленьких матрицах небольшая задержка на создание и инициализацию нескольких процессов может оказаться более затратным, чем просто выполнение умножения последовательно. + + +# Видео +https://disk.yandex.ru/i/jnli8mT8pkz4mw diff --git a/haliullov_kamil_lab_5/project/main.py b/haliullov_kamil_lab_5/project/main.py new file mode 100644 index 0000000..3d159fb --- /dev/null +++ b/haliullov_kamil_lab_5/project/main.py @@ -0,0 +1,61 @@ +import numpy as np +import time +from multiprocessing import Pool + +def multiply_matrices_sequential(A, B): + """Последовательное умножение матриц.""" + n = A.shape[0] + C = np.zeros((n, n)) + for i in range(n): + for j in range(n): + C[i][j] = np.dot(A[i], B[:, j]) # Используем векторизированное умножение для повышения производительности + return C + +def worker(args): + """Функция для параллельного умножения матриц, которая обрабатывает строки.""" + A, B, row_indices = args + C_part = np.zeros((len(row_indices), B.shape[1])) + + for idx, i in enumerate(row_indices): + C_part[idx] = np.dot(A[i], B) + + return C_part + +def multiply_matrices_parallel(A, B, num_workers): + """Параллельное умножение матриц.""" + n = A.shape[0] + C = np.zeros((n, n)) + row_indices = np.array_split(range(n), num_workers) + + with Pool(processes=num_workers) as pool: + results = pool.map(worker, [(A, B, idx) for idx in row_indices]) + + # Объединяем результаты + for i, result in enumerate(results): + C[i * len(result): (i + 1) * len(result)] = result + + return C + +def benchmark(matrix_size, num_workers): + # Генерация случайных матриц + A = np.random.rand(matrix_size, matrix_size) + B = np.random.rand(matrix_size, matrix_size) + + start_time = time.time() + if num_workers == 1: + C = multiply_matrices_sequential(A, B) + else: + C = multiply_matrices_parallel(A, B, num_workers) + end_time = time.time() + + method = "последовательное" if num_workers == 1 else f"параллельное с {num_workers} потоками" + print(f"{method.capitalize()} умножение матриц {matrix_size}x{matrix_size}: {end_time - start_time:.6f} сек") + +if __name__ == "__main__": + # Запуск бенчмарков + sizes = [100, 300, 500] + for size in sizes: + print(f"\nБенчмарк для матриц размером {size}x{size}:") + benchmark(size, 1) # Последовательный + benchmark(size, 4) # Параллельный (4 потока) + benchmark(size, 8) # Параллельный (8 потоков) diff --git a/haliullov_kamil_lab_5/result.png b/haliullov_kamil_lab_5/result.png new file mode 100644 index 0000000000000000000000000000000000000000..ec543b8de7eca3d7e4b1718bf234523385a1fc64 GIT binary patch literal 17695 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