import random as rnd
import time
from multiprocessing import Pool

# Инициализируем матрицу результата нулями (так легче!)
base_matrix = [[0 for i in range(500)] for j in range(500)]

# Функция для генерации квадратной матрицы заданного размера со случайными значениями от 0 до 100
def generateMatrixWithSize(size):
    return [[rnd.randint(0, 100) for i in range(size)] for j in range(size)]

# Функция для вывода матрицы на экран
def printMatrix(matrix):
    for row in matrix:
        print(*row, sep="\t")

# Функция для перемножения матриц без использования потоков (стандартный алгоритм)
def multiplyMatrixOGWay(matrix1, matrix2):
    l1 = len(matrix1)
    l2 = len(matrix2)
    global base_matrix  # Используем глобальную переменную для хранения результата
    result = base_matrix
    for i in range(l1):
        for j in range(l2):
            for k in range(l2):
                result[i][j] += matrix1[i][k] * matrix2[k][j]

    return result


# Функция для перемножения части матриц в отдельном процессе (worker для Pool)
def matrixMultiplyByOneProcess(args):
    matrix1_slice, matrix2, start_i, end_i = args # Распаковываем аргументы
    global base_matrix #  Используем глобальную переменную для хранения результата

    # Локальная ссылка для удобства
    result = base_matrix

    for i in range(end_i-start_i):
        for j in range(len(matrix2[0])):
            for k in range(len(matrix2)):
               result[i + start_i][j] += matrix1_slice[i][k] * matrix2[k][j]


# Функция для параллельного перемножения матриц с использованием процессов
def matrixMultiplyWithProcesses(matrix1, matrix2, process_count):
    l1 = len(matrix1)

    # Расчет количества строк на каждый процесс
    chunk_size = l1 // process_count
    remainder = l1 % process_count

    args = []
    start_i = 0
    for i in range(process_count):
        end_i = start_i + chunk_size + (1 if i < remainder else 0)
        args.append((matrix1[start_i:end_i], matrix2, start_i, end_i))
        start_i = end_i
        
    # Создание пула процессов и выполнение расчета
    with Pool(processes=process_count) as pool:
        pool.map(matrixMultiplyByOneProcess, args)


if __name__ == "__main__":

    matrix_sizes = [100, 300, 500]
    num_processes = [1, 5, 10]

    for size in matrix_sizes:
        matrix1 = generateMatrixWithSize(size)
        matrix2 = generateMatrixWithSize(size)

        # Замер времени для стандартного умножения
        base_matrix = [[0 for i in range(size)] for j in range(size)] # Очищаем перед каждым запуском.
        start_time = time.time()
        multiplyMatrixOGWay(matrix1, matrix2)
        end_time = time.time()
        print(f"Обычный режим.  Размер матрицы: {size}. {end_time - start_time} сек.")

        # Замер времени для параллельного умножения с разным количеством процессов
        for processes in num_processes:
            base_matrix = [[0 for i in range(size)] for j in range(size)] # Очищаем перед каждым запуском.
            start_time = time.time()
            matrixMultiplyWithProcesses(matrix1, matrix2, processes)
            end_time = time.time()
            print(f"Параллельный режим. Размер матрицы: {size}. Количество процессов: {processes}. {end_time - start_time} сек.")

        print("\n\n")