import numpy as np
from multiprocessing import Pool
import time

# Генерация квадратной матрицы случайными значениями
def generate_matrix(size):
    return np.random.randint(1, 10, (size, size))


# Обычное умножение матриц
def matrix_multiply_sequential(A, B):
    size = A.shape[0]
    C = np.zeros((size, size), dtype=int)
    for i in range(size):
        for j in range(size):
            C[i, j] = sum(A[i, k] * B[k, j] for k in range(size))
    return C

def multiply_row(args):
    A, B, row = args
    return np.dot(A[row, :], B)


# Параллельное умножение матриц
def parallel_matrix_multiply(A, B, num_processes):
    n = A.shape[0]
    C = np.zeros((n, n))

    # Создаем пул процессов
    with Pool(processes=num_processes) as pool:
        results = pool.map(multiply_row, [(A, B, i) for i in range(n)])

    # Записываем результат
    for i, row in enumerate(results):
        C[i, :] = row

    return C


# Бенчмарк функции
def benchmark(matrix_size, num_processes_list):
    A = np.random.randint(0, 10, (matrix_size, matrix_size))
    B = np.random.randint(0, 10, (matrix_size, matrix_size))

    # Последовательное умножение
    start_time = time.time()
    C_seq = np.dot(A, B)
    sequential_time = time.time() - start_time
    print(f"Sequential time for {matrix_size}x{matrix_size}: {sequential_time:.4f} seconds")

    # Параллельное умножение
    for num_processes in num_processes_list:
        start_time = time.time()
        C_par = parallel_matrix_multiply(A, B, num_processes)
        parallel_time = time.time() - start_time
        speedup = sequential_time / parallel_time
        print(f"Parallel time with {num_processes} processes: {parallel_time:.4f} seconds, Speedup: {speedup:.2f}")


if __name__ == '__main__':
    # Запуск бенчмарков
    matrix_sizes = [100, 500, 1000, 2500, 400]
    num_processes_list = [1, 2, 4, 6, 8, 12, 16]

    for size in matrix_sizes:
        print(f"\n--- Benchmark for {size}x{size} Matrix ---")
        benchmark(size, num_processes_list)