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