From f31120978d906fc143cd486d9fa4cedb94924d24 Mon Sep 17 00:00:00 2001 From: Arutunyan-Dmitry Date: Wed, 17 Jan 2024 22:32:28 +0400 Subject: [PATCH] arutunyan_dmitry_lab_6 is ready --- arutunyan_dmitry_lab_6/README.md | 106 +++++++++++++++++++++++++++++++ arutunyan_dmitry_lab_6/main.py | 76 ++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 arutunyan_dmitry_lab_6/README.md create mode 100644 arutunyan_dmitry_lab_6/main.py diff --git a/arutunyan_dmitry_lab_6/README.md b/arutunyan_dmitry_lab_6/README.md new file mode 100644 index 0000000..5cf1f61 --- /dev/null +++ b/arutunyan_dmitry_lab_6/README.md @@ -0,0 +1,106 @@ + +## Лабораторная работа 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ю строку матрицы и параллельно вычислить определитель каждого минора матрицы, умножив его на соответсвующий элемент. + +#### Разработка программы +Для начала создадим функцию вычисления детерминанта минора матрицы с его умножением на соответсвующий элемент строки: +```python +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`, в который будем передавать желаемое количество потоков: +```python +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й строки матрицы) не имеет значения. По сути, самым оптимальным решением будет являться состояние, когда каждый поток ищет определитель определённого минора, составленного по определённому элементу строки матрицы. + +Создадим тестовый метод и проверим работу калькулятора вычисления определителя на грамотность: +```python +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 \ No newline at end of file diff --git a/arutunyan_dmitry_lab_6/main.py b/arutunyan_dmitry_lab_6/main.py new file mode 100644 index 0000000..9cb235b --- /dev/null +++ b/arutunyan_dmitry_lab_6/main.py @@ -0,0 +1,76 @@ +import time +import numpy as np +import concurrent.futures +import psutil + + +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 + + +def parallel_determinant(matrix, parallel): + memory = psutil.virtual_memory() + n = matrix.shape[0] + if parallel: + 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() + else: + start_time = time.time() + result = np.linalg.det(matrix) + end_time = time.time() + execution_time = end_time - start_time + return result, execution_time, psutil.cpu_percent(interval=1), memory.percent + + +def test(parallel): + mx = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + result = parallel_determinant(mx, parallel) + print(f"Определитель матрицы: {result[0]}") + print(f"Время выполнения: {result[1]} сек.") + print(f"Загрузка ЦП: {result[2]} %") + print(f"Использование ОЗУ: {result[3]} %") + + +def bench50x50(parallel): + mx = np.random.randint(0, 100, size=(50, 50)) + result = parallel_determinant(mx, parallel) + print(f"Определитель матрицы: {result[0]}") + print(f"Время выполнения: {result[1]} сек.") + print(f"Загрузка ЦП: {result[2]} %") + print(f"Использование ОЗУ: {result[3]} %") + + +def bench75x75(parallel): + mx = np.random.randint(0, 100, size=(75, 75)) + result = parallel_determinant(mx, parallel) + print(f"Определитель матрицы: {result[0]}") + print(f"Время выполнения: {result[1]} сек.") + print(f"Загрузка ЦП: {result[2]} %") + print(f"Использование ОЗУ: {result[3]} %") + + +def bench125x125(parallel): + mx = np.random.randint(0, 100, size=(125, 125)) + result = parallel_determinant(mx, parallel) + print(f"Определитель матрицы: {result[0]}") + print(f"Время выполнения: {result[1]} сек.") + print(f"Загрузка ЦП: {result[2]} %") + print(f"Использование ОЗУ: {result[3]} %") + + +if __name__ == '__main__': + # test(parallel=False) + # bench50x50(parallel=False) + # bench75x75(parallel=False) + bench125x125(parallel=False)