DAS_2023_1/arutunyan_dmitry_lab_6
2024-01-17 22:32:28 +04:00
..
main.py arutunyan_dmitry_lab_6 is ready 2024-01-17 22:32:28 +04:00
README.md arutunyan_dmitry_lab_6 is ready 2024-01-17 22:32:28 +04:00

Лабораторная работа 6. Вариант 4.

Задание

Реализовать нахождение детерминанта квадратной матрицы.

  • Создать алгоритм параллельного нахождения детерминанта квадратной матрицы,
  • Предусмотреть задание потоков вручную, для работы параллельного алгоритма нахождения определителя как обычного.

Как запустить

Для запуска программы необходимо с помощью командной строки в корневой директории файлов прокета прописать:

python main.py

Результат работы программы будет выведен в консоль.

Используемые технологии

  • Библиотека numpy, используемая для обработки массивов данных и вычислений.
  • Библиотека concurrent.futures- высокоуровневый интерфейс для выполнения параллельных и асинхронных задач.
    • ThreadPoolExecutor - класс пула потоков для выполнения задач в нескольких потоках. Он использует пул потоков, чтобы автоматически управлять созданием и выполнением потоков, что обеспечивает простой способ распараллеливания задач. Метод submit() позволяет отправлять задачи в пул потоков, и возвращает объект Future, который представляет результат выполнения задачи.
  • Библиотека time, используемая для измерения времени работы программы.
  • Библиотека psutil, используемая для отслеживания нагрузки на процессор и количества загруженной оперативной памяти.

Описание работы

Распараллеливание задачи нахождения определителя

Воспользуемся правилом нахождения определителя с помощью миноров матрицы:

  • Определитель матрицы равен сумме произведений элементов строки (столбца) на соответствующие алгебраические дополнения.
|a1, a2, a3|           |b2, b3|           |b1, b3|           |b1, b2|
|b1, b2, b3| = (+)a1 x |c2, c3| + (-)a2 x |c1, c3| + (+)a3 x |c1, c2|
|c1, c2, c3|

Таким образом, мы можем выбрать 1ю строку матрицы и параллельно вычислить определитель каждого минора матрицы, умножив его на соответсвующий элемент.

Разработка программы

Для начала создадим функцию вычисления детерминанта минора матрицы с его умножением на соответсвующий элемент строки:

def calculate_determinant(args):
    matrix, i = args
    multiplier = matrix[0][i]
    if i % 2 != 0:
        multiplier *= -1
    matrix = np.delete(matrix, 0, axis=0)
    submatrix = np.delete(matrix, i, axis=1)
    return np.linalg.det(submatrix) * multiplier

Если элемент находится на нечётной позиции в строке, то, по правилу, знак множителя меняется на противоположный. Теперь перейдём к алгоритму параллельного вычисления детерминанта матрицы. Создадим пул потоков с помощью класса concurrent.futures.ThreadPoolExecutor, в который будем передавать желаемое количество потоков:

with concurrent.futures.ThreadPoolExecutor(max_workers=n) as executor:
    results = []
    start_time = time.time()
    for i in range(n):
        results.append(executor.submit(calculate_determinant, args=(matrix, i)))
    result = np.sum([res.result() for res in results])
    end_time = time.time()

В пул потоков в качестве аргументов мы передаём последовательно матрицу и номер столбца элемента-множителя, а в качестве их обработчика указываем ранее созданный метод calculate_determinant. Массив определителей миноров, умноженных на соответсвующие множители суммируется методом sum и записывается в результат.

Note

Поскольку определение детерминанта матрицы распараллеливается по 1му порядку миноров, задавать значение кол-ва потоков больше числа элементов строки-множителя (1й строки матрицы) не имеет значения. По сути, самым оптимальным решением будет являться состояние, когда каждый поток ищет определитель определённого минора, составленного по определённому элементу строки матрицы.

Создадим тестовый метод и проверим работу калькулятора вычисления определителя на грамотность:

mx = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
return parallel_determinant(mx, parallel)

Результат вычислений: 0 Алгоритм работает верно.

Замеры параметров

Бенчмарки в данной лабораторной работе задаются аналогично предыдущей. Прогоним все бенчмарки и сравним измеряемые показатели при максимальной многопоточности (кол-во потоков = кол-ву эл-тов 1й строки матрицы) и монопоточности.

Результаты (параллельный - слева, обычный - справа):

50 * 50
_________________________________________________________________

Время выполнения: 0.0129.. сек. | Время выполнения: 0.0010.. сек.
Загрузка ЦП: 5.4%               | Загрузка ЦП: 4.8%
Использование ОЗУ: 71.6%        | Использование ОЗУ: 71.7%

75 * 75
_________________________________________________________________

Время выполнения: 0.0220.. сек. | Время выполнения: 0.0..    сек.
Загрузка ЦП: 5.7%               | Загрузка ЦП: 1.2%
Использование ОЗУ: 71.1%        | Использование ОЗУ: 71.0%

125 * 125
_________________________________________________________________

Время выполнения: 0.5048.. сек. | Время выполнения: 0.0070.. сек.
Загрузка ЦП: 5.6%               | Загрузка ЦП: 2.5%
Использование ОЗУ: 71.2%        | Использование ОЗУ: 71.7%

Вывод

По результатам замеров видно, что при любых размерностях матрицы, монопоточное вычисление определителя происходит эффективнее как по времени, так и по ресурс-затратности. По загруженности ЦП можно убедиться, что многопоточное определение детерминанта матрицы работает корректно, тк загрузка процессора при использовании нескольких потоков всегда выше, чем при использовании монопоточности.

Таким образом, применение парралельных вычислений при решении данной задачи показали себя не учшим образом. Связанно это может быть с оптимальностью алгоритма вычисления определителя функции det библиотеки numpy, с недостаточной оптимальностью алгоритма распараллеливания вычисления (находится минор только последующего порядка, а не рассчитывается до 1го в связи с рекурсивностью выбранного метода определения детерминанта) или с недостаточно большой размерностью матрицы (оперативной памяти данной машины хватает на вычисления определителя матрицы, максимальной размерностью 125х125)

Видео

https://youtu.be/ayDhflcf7PM