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 потоков)