import time
import multiprocessing
import numpy as np

def multiply_matrices_sequential(matrix1, matrix2):
    rows1 = len(matrix1)
    cols1 = len(matrix1[0])
    rows2 = len(matrix2)
    cols2 = len(matrix2[0])

    if cols1 != rows2:
        raise ValueError("Число столбцов первой матрицы должно быть равно числу строк второй матрицы.")

    result = [[0 for _ in range(cols2)] for _ in range(rows1)]
    for i in range(rows1):
        for j in range(cols2):
            for k in range(cols1):
                result[i][j] += matrix1[i][k] * matrix2[k][j]
    return result

def multiply_matrices_parallel(matrix1, matrix2, num_processes):
    rows1 = len(matrix1)
    cols1 = len(matrix1[0])
    rows2 = len(matrix2)
    cols2 = len(matrix2[0])

    if cols1 != rows2:
        raise ValueError("Число столбцов первой матрицы должно быть равно числу строк второй матрицы.")

    chunk_size = rows1 // num_processes
    processes = []
    results = []

    with multiprocessing.Pool(processes=num_processes) as pool:
        for i in range(num_processes):
            start_row = i * chunk_size
            end_row = (i + 1) * chunk_size if i < num_processes - 1 else rows1
            p = pool.apply_async(multiply_matrix_chunk, (matrix1, matrix2, start_row, end_row))
            processes.append(p)

        for p in processes:
            results.append(p.get())

    result = [[0 for _ in range(cols2)] for _ in range(rows1)]
    row_index = 0
    for sub_result in results:
        for row in sub_result:
            result[row_index] = row
            row_index += 1

    return result


def multiply_matrix_chunk(matrix1, matrix2, start_row, end_row):
    rows2 = len(matrix2)
    cols2 = len(matrix2[0])
    cols1 = len(matrix1[0])
    result = [[0 for _ in range(cols2)] for _ in range(end_row - start_row)]
    for i in range(end_row - start_row):
        for j in range(cols2):
            for k in range(cols1):
                result[i][j] += matrix1[i + start_row][k] * matrix2[k][j]
    return result


def benchmark(matrix_size, num_processes):
  matrix1 = np.random.rand(matrix_size, matrix_size).tolist()
  matrix2 = np.random.rand(matrix_size, matrix_size).tolist()

  try:
    start_time = time.time()
    sequential_result = multiply_matrices_sequential(matrix1, matrix2)
    end_time = time.time()
    sequential_time = end_time - start_time

    start_time = time.time()
    parallel_result = multiply_matrices_parallel(matrix1, matrix2, num_processes)
    end_time = time.time()
    parallel_time = end_time - start_time
    return sequential_time, parallel_time
  except ValueError as e:
    print(f"Ошибка бенчмарка с размером матрицы {matrix_size} и {num_processes} процессов: {e}")
    return float('inf'), float('inf')


if __name__ == "__main__":
  sizes = [100, 300, 500]
  num_processes = int(input("Введите количество потоков: "))
  print("Размер | Последовательно | Параллельно")

  for size in sizes:
    sequential_time, parallel_time = benchmark(size, num_processes)
    print(f"{size:6} | {sequential_time:.4f} с \t | {parallel_time:.4f} с")