medvedkov_andrey_lab_5 is ready
This commit is contained in:
210
medvekov_andrey_lab_5/lab.py
Normal file
210
medvekov_andrey_lab_5/lab.py
Normal file
@@ -0,0 +1,210 @@
|
||||
import random
|
||||
import time
|
||||
import math
|
||||
from concurrent.futures import ProcessPoolExecutor, as_completed
|
||||
import argparse
|
||||
|
||||
|
||||
def generate_random_matrix(n: int, seed: int | None = None) -> list[list[float]]:
|
||||
"""Генерация квадратной матрицы n x n со случайными целыми значениями [0, 10)."""
|
||||
rng = random.Random(seed)
|
||||
return [[rng.randint(0, 9) for _ in range(n)] for _ in range(n)]
|
||||
|
||||
|
||||
def multiply_sequential(a: list[list[float]], b: list[list[float]]) -> list[list[float]]:
|
||||
"""Классический последовательный алгоритм умножения матриц O(n^3)."""
|
||||
n = len(a)
|
||||
if n == 0 or len(b) != n or len(b[0]) != n:
|
||||
raise ValueError("Матрицы должны быть квадратными и одинакового размера")
|
||||
|
||||
# Результирующая матрица, заполненная нулями
|
||||
c = [[0.0 for _ in range(n)] for _ in range(n)]
|
||||
|
||||
for i in range(n):
|
||||
for j in range(n):
|
||||
s = 0.0
|
||||
# скалярное произведение i-й строки A и j-го столбца B
|
||||
for k in range(n):
|
||||
s += a[i][k] * b[k][j]
|
||||
c[i][j] = s
|
||||
|
||||
return c
|
||||
|
||||
|
||||
def _multiply_chunk(a: list[list[float]],
|
||||
b: list[list[float]],
|
||||
start_row: int,
|
||||
end_row: int) -> tuple[int, list[list[float]]]:
|
||||
"""
|
||||
Вспомогательная функция для процесса:
|
||||
считает строки [start_row, end_row) результата и возвращает их.
|
||||
Возвращаем также start_row, чтобы потом собрать всё в правильном порядке.
|
||||
"""
|
||||
n = len(a)
|
||||
rows = []
|
||||
|
||||
for i in range(start_row, end_row):
|
||||
row_res = []
|
||||
for j in range(n):
|
||||
s = 0.0
|
||||
for k in range(n):
|
||||
s += a[i][k] * b[k][j]
|
||||
row_res.append(s)
|
||||
rows.append(row_res)
|
||||
|
||||
return start_row, rows
|
||||
|
||||
|
||||
def multiply(a: list[list[float]],
|
||||
b: list[list[float]],
|
||||
workers: int = 1) -> list[list[float]]:
|
||||
"""
|
||||
Универсальное умножение матриц:
|
||||
- workers = 1 -> фактически последовательный режим
|
||||
- workers >= 2 -> параллельный режим через ProcessPoolExecutor
|
||||
|
||||
Логика "задания со *": один алгоритм, параметризованный количеством "потоков" (воркеров).
|
||||
"""
|
||||
n = len(a)
|
||||
if n == 0 or len(b) != n or len(b[0]) != n:
|
||||
raise ValueError("Матрицы должны быть квадратными и одинакового размера")
|
||||
|
||||
if workers <= 1:
|
||||
# последовательный режим
|
||||
return multiply_sequential(a, b)
|
||||
|
||||
# параллельный режим: делим строки результата между воркерами
|
||||
result = [[0.0 for _ in range(n)] for _ in range(n)]
|
||||
|
||||
rows_per_worker = math.ceil(n / workers)
|
||||
tasks = []
|
||||
|
||||
with ProcessPoolExecutor(max_workers=workers) as executor:
|
||||
for w in range(workers):
|
||||
start_row = w * rows_per_worker
|
||||
end_row = min(start_row + rows_per_worker, n)
|
||||
if start_row >= end_row:
|
||||
break # воркеров больше, чем строк
|
||||
fut = executor.submit(_multiply_chunk, a, b, start_row, end_row)
|
||||
tasks.append(fut)
|
||||
|
||||
for fut in as_completed(tasks):
|
||||
start_row, rows = fut.result()
|
||||
for offset, row in enumerate(rows):
|
||||
result[start_row + offset] = row
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def matrices_equal(m1: list[list[float]],
|
||||
m2: list[list[float]],
|
||||
eps: float = 1e-6) -> bool:
|
||||
"""Проверка равенства двух матриц поэлементно с допуском eps."""
|
||||
if len(m1) != len(m2) or len(m1[0]) != len(m2[0]):
|
||||
return False
|
||||
n = len(m1)
|
||||
m = len(m1[0])
|
||||
for i in range(n):
|
||||
for j in range(m):
|
||||
if abs(m1[i][j] - m2[i][j]) > eps:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def benchmark_one_size(n: int, worker_counts: list[int]) -> None:
|
||||
"""Запуск серии бенчмарков для одной размерности матрицы."""
|
||||
print(f"\nРазмер матрицы: {n}x{n}")
|
||||
print("-" * 40)
|
||||
|
||||
# фиксируем seed, чтобы A и B были одинаковыми для разных запусков
|
||||
a = generate_random_matrix(n, seed=42)
|
||||
b = generate_random_matrix(n, seed=123)
|
||||
|
||||
# последовательное умножение
|
||||
t0 = time.perf_counter()
|
||||
c_seq = multiply(a, b, workers=1)
|
||||
t1 = time.perf_counter()
|
||||
seq_ms = (t1 - t0) * 1000.0
|
||||
print(f"Последовательный алгоритм (1 процесс): {seq_ms:.3f} ms")
|
||||
|
||||
for w in worker_counts:
|
||||
if w <= 1:
|
||||
continue
|
||||
|
||||
# небольшой прогрев (не меряем)
|
||||
_ = multiply(a, b, workers=w)
|
||||
|
||||
t0 = time.perf_counter()
|
||||
c_par = multiply(a, b, workers=w)
|
||||
t1 = time.perf_counter()
|
||||
par_ms = (t1 - t0) * 1000.0
|
||||
|
||||
ok = matrices_equal(c_seq, c_par)
|
||||
speedup = seq_ms / par_ms if par_ms > 0 else float('inf')
|
||||
status = "OK" if ok else "FAIL"
|
||||
|
||||
print(f"Параллельный алгоритм ({w} процессов): {par_ms:.3f} ms "
|
||||
f"(ускорение: {speedup:.2f}x, корректность: {status})")
|
||||
|
||||
|
||||
def run_default_benchmarks():
|
||||
sizes = [100, 300, 500]
|
||||
workers = [1, 2, 4]
|
||||
|
||||
print("Бенчмарк умножения квадратных матриц (Python)")
|
||||
print("============================================")
|
||||
|
||||
for n in sizes:
|
||||
benchmark_one_size(n, workers)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Умножение квадратных матриц (последовательно и параллельно)."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-n", "--size",
|
||||
type=int,
|
||||
help="размер квадратной матрицы n (по умолчанию запускаются все: 100, 300, 500)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-w", "--workers",
|
||||
type=int,
|
||||
help="количество процессов (workers) для параллельного режима; "
|
||||
"если не задано, выполняются бенчмарки для 1, 2, 4"
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
|
||||
if args.size is not None and args.workers is not None:
|
||||
# одиночный запуск
|
||||
n = args.size
|
||||
w = args.workers
|
||||
print(f"Одиночный запуск: размер {n}x{n}, workers={w}")
|
||||
|
||||
a = generate_random_matrix(n, seed=42)
|
||||
b = generate_random_matrix(n, seed=123)
|
||||
|
||||
t0 = time.perf_counter()
|
||||
c_seq = multiply(a, b, workers=1)
|
||||
t1 = time.perf_counter()
|
||||
seq_ms = (t1 - t0) * 1000.0
|
||||
|
||||
t0 = time.perf_counter()
|
||||
c_par = multiply(a, b, workers=w)
|
||||
t1 = time.perf_counter()
|
||||
par_ms = (t1 - t0) * 1000.0
|
||||
|
||||
ok = matrices_equal(c_seq, c_par)
|
||||
speedup = seq_ms / par_ms if par_ms > 0 else float('inf')
|
||||
|
||||
print(f"Последовательный (1 процесс): {seq_ms:.3f} ms")
|
||||
print(f"Параллельный ({w} процессов): {par_ms:.3f} ms")
|
||||
print(f"Ускорение: {speedup:.2f}x")
|
||||
print(f"Корректность: {'OK' if ok else 'FAIL'}")
|
||||
else:
|
||||
# дефолтные бенчмарки
|
||||
run_default_benchmarks()
|
||||
76
medvekov_andrey_lab_5/readme.md
Normal file
76
medvekov_andrey_lab_5/readme.md
Normal file
@@ -0,0 +1,76 @@
|
||||
## Как запустить
|
||||
|
||||
Требуется **Python 3** и файл `matrix_mul.py` в текущей папке.
|
||||
|
||||
Полный прогон бенчмарков (матрицы 100×100, 300×300, 500×500; 1, 2, 4 процесса):
|
||||
|
||||
```bash
|
||||
python matrix_mul.py
|
||||
```
|
||||
Запуск одного эксперимента с заданным размером и числом процессов:
|
||||
|
||||
```bash
|
||||
Копировать код
|
||||
python matrix_mul.py -n <размер> -w <число_процессов>
|
||||
```
|
||||
## пример:
|
||||
```python matrix_mul.py -n 300 -w 4```
|
||||
|
||||
Используемые технологии
|
||||
Язык: Python 3
|
||||
|
||||
Стандартная библиотека:
|
||||
|
||||
- random — генерация матриц;
|
||||
|
||||
- time — измерение времени;
|
||||
|
||||
- math — расчёт размеров блоков строк;
|
||||
|
||||
- argparse — аргументы командной строки;
|
||||
|
||||
- concurrent.futures.ProcessPoolExecutor — параллельные вычисления в нескольких процессах.
|
||||
|
||||
## Что делает программа
|
||||
Генерирует две квадратные матрицы размера n×n со случайными значениями.
|
||||
|
||||
Умножает их:
|
||||
|
||||
- последовательно (1 процесс);
|
||||
|
||||
- параллельно (несколько процессов), деля строки результата между процессами.
|
||||
|
||||
Использует одну функцию multiply(a, b, workers):
|
||||
|
||||
- workers = 1 — последовательный режим;
|
||||
|
||||
- workers > 1 — параллельный режим (выполнение задания со *).
|
||||
|
||||
Замеряет время работы обоих вариантов, считает ускорение и выводит результаты в консоль.
|
||||
|
||||
## Результаты бенчмарка
|
||||
|
||||
|
||||
Размер матрицы: 100x100
|
||||
----------------------------------------
|
||||
- Последовательный алгоритм (1 процесс): 59.812 ms
|
||||
|
||||
- Параллельный алгоритм (2 процессов): 135.846 ms (ускорение: 0.44x, корректность: OK)
|
||||
|
||||
- Параллельный алгоритм (4 процессов): 148.590 ms (ускорение: 0.40x, корректность: OK)
|
||||
|
||||
|
||||
Размер матрицы: 300x300
|
||||
----------------------------------------
|
||||
- Последовательный алгоритм (1 процесс): 1741.963 ms
|
||||
- Параллельный алгоритм (2 процессов): 1048.928 ms (ускорение: 1.66x, корректность: OK)
|
||||
- Параллельный алгоритм (4 процессов): 579.664 ms (ускорение: 3.01x, корректность: OK)
|
||||
|
||||
Размер матрицы: 500x500
|
||||
----------------------------------------
|
||||
- Последовательный алгоритм (1 процесс): 8510.345 ms
|
||||
- Параллельный алгоритм (2 процессов): 4915.916 ms (ускорение: 1.73x, корректность: OK)
|
||||
- Параллельный алгоритм (4 процессов): 2448.955 ms (ускорение: 3.48x, корректность: OK)
|
||||
|
||||
# ссылка на видео:
|
||||
https://vkvideo.ru/video-234070899_456239021?list=ln-v5QLhJLR9cEzcnI3Og
|
||||
Reference in New Issue
Block a user