Compare commits

...

1 Commits

Author SHA1 Message Date
f31120978d arutunyan_dmitry_lab_6 is ready 2024-01-17 22:32:28 +04:00
2 changed files with 182 additions and 0 deletions

View File

@ -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

View File

@ -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)