Compare commits
1 Commits
main
...
arutunyan_
Author | SHA1 | Date | |
---|---|---|---|
f31120978d |
106
arutunyan_dmitry_lab_6/README.md
Normal file
106
arutunyan_dmitry_lab_6/README.md
Normal 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
|
76
arutunyan_dmitry_lab_6/main.py
Normal file
76
arutunyan_dmitry_lab_6/main.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user