save
This commit is contained in:
84
kozyrev_sergey_lab_6/README.md
Normal file
84
kozyrev_sergey_lab_6/README.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Лабораторная работа 6 — Параллельное вычисление детерминанта матрицы
|
||||
|
||||
## 1. Цель работы
|
||||
|
||||
Изучение параллельного программирования на примере реализации алгоритмов вычисления детерминанта (определителя) квадратных матриц с использованием многопроцессности Python.
|
||||
|
||||
## 2. Как запустить лабораторную работу
|
||||
|
||||
### Предварительные требования:
|
||||
* Python 3.9+
|
||||
* Библиотека NumPy
|
||||
|
||||
### Установка зависимостей
|
||||
|
||||
```bash
|
||||
pip install numpy
|
||||
```
|
||||
|
||||
### Запуск программы
|
||||
|
||||
```bash
|
||||
python matrix.py
|
||||
```
|
||||
|
||||
Программа автоматически выполнит:
|
||||
1. Тесты корректности на известных матрицах
|
||||
2. Бенчмарки на матрицах размером 100×100, 300×300 и 500×500 элементов
|
||||
3. Сравнение последовательного и параллельного алгоритмов
|
||||
|
||||
## 3. Структура проекта
|
||||
|
||||
```
|
||||
matrix.py # Основной файл с реализацией алгоритмов
|
||||
README.md # Документация проекта
|
||||
```
|
||||
|
||||
## 4. Результаты тестирования
|
||||
|
||||
### Матрицы 100×100
|
||||
|
||||
#### Результат:
|
||||
|
||||
| Алгоритм | Время (сек) | Ускорение |
|
||||
|----------|-------------|-----------|
|
||||
| Последовательный (LU) | 0.01-0.03 | 1.00x |
|
||||
| Параллельный (2 процесса) | 2.5-3.5 | 0.01x |
|
||||
| Параллельный (4 процесса) | 1.8-2.8 | 0.01x |
|
||||
| Параллельный (8 процессов) | 1.5-2.5 | 0.01x |
|
||||
|
||||
#### Вывод:
|
||||
На матрицах 100×100 параллелизм **крайне неэффективен**. Overhead от создания процессов (2-3 сек) во много раз превышает время самих вычислений (0.01-0.03 сек). Последовательный LU-алгоритм работает мгновенно и является оптимальным выбором.
|
||||
|
||||
### Матрицы 300×300
|
||||
|
||||
#### Результат:
|
||||
|
||||
| Алгоритм | Время (сек) | Ускорение |
|
||||
|----------|-------------|-----------|
|
||||
| Последовательный (LU) | 0.15-0.25 | 1.00x |
|
||||
| Параллельный (2 процесса) | 45-55 | 0.004x ❌ |
|
||||
| Параллельный (4 процесса) | 25-35 | 0.007x ❌ |
|
||||
| Параллельный (8 процессов) | 15-20 | 0.01x ❌ |
|
||||
|
||||
#### Вывод:
|
||||
Даже на матрицах 300×300 параллелизм через разложение по строке остается **неэффективным**. LU-разложение работает за доли секунды, в то время как параллельное разложение требует десятков секунд на вычисление 300 миноров размером 299×299.
|
||||
|
||||
### Матрицы 500×500
|
||||
|
||||
#### Результат:
|
||||
|
||||
| Алгоритм | Время (сек) | Ускорение | Детерминант |
|
||||
|----------|-------------|-----------|-------------|
|
||||
| Последовательный (LU) | 0.5-0.8 | 1.00x | ~10¹⁰⁰⁰ |
|
||||
| Параллельный (2 процесса) | 180-220 | 0.003x ❌ | ~10¹⁰⁰⁰ |
|
||||
| Параллельный (4 процесса) | 100-130 | 0.006x ❌ | ~10¹⁰⁰⁰ |
|
||||
| Параллельный (8 процессов) | 60-80 | 0.009x ❌ | ~10¹⁰⁰⁰ |
|
||||
|
||||
#### Вывод:
|
||||
На больших матрицах 500×500 ситуация **не улучшается**. LU-разложение остается самым эффективным (менее 1 секунды), в то время как параллельное разложение по строке требует минут работы.
|
||||
|
||||
|
||||
## 13. Видео
|
||||
|
||||
ВидеоСсылка на видео работы алгоритма: [Rutube](https://rutube.ru/video/private/d4dc5613005afbbe601862d54b248d36/?p=De8RTMImQZQtab3avfH2Zg)
|
||||
235
kozyrev_sergey_lab_6/matrix.py
Normal file
235
kozyrev_sergey_lab_6/matrix.py
Normal file
@@ -0,0 +1,235 @@
|
||||
import numpy as np
|
||||
import multiprocessing as mp
|
||||
import time
|
||||
import warnings
|
||||
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
def sequential_determinant(matrix):
|
||||
n = matrix.shape[0]
|
||||
|
||||
A = matrix.astype(np.float64).copy()
|
||||
|
||||
sign = 1
|
||||
|
||||
for i in range(n):
|
||||
max_row = i
|
||||
max_val = abs(A[i, i])
|
||||
|
||||
for k in range(i + 1, n):
|
||||
if abs(A[k, i]) > max_val:
|
||||
max_row = k
|
||||
max_val = abs(A[k, i])
|
||||
|
||||
if max_row != i:
|
||||
A[[i, max_row]] = A[[max_row, i]]
|
||||
sign *= -1
|
||||
|
||||
if abs(A[i, i]) < 1e-15:
|
||||
return 0.0, -np.inf
|
||||
|
||||
for k in range(i + 1, n):
|
||||
factor = A[k, i] / A[i, i]
|
||||
A[k, i:] -= factor * A[i, i:]
|
||||
|
||||
log_abs_det = 0.0
|
||||
for i in range(n):
|
||||
if A[i, i] < 0:
|
||||
sign *= -1
|
||||
log_abs_det += np.log(abs(A[i, i]))
|
||||
|
||||
return sign, log_abs_det
|
||||
|
||||
def to_float(sign, log_abs_det):
|
||||
if log_abs_det == -np.inf:
|
||||
return 0.0
|
||||
|
||||
if log_abs_det > 700:
|
||||
return sign * np.inf
|
||||
|
||||
return sign * np.exp(log_abs_det)
|
||||
|
||||
|
||||
def worker_lu_decomposition(args):
|
||||
matrix, start, end = args
|
||||
n = matrix.shape[0]
|
||||
A = matrix.copy()
|
||||
perm_count = 0
|
||||
|
||||
for i in range(start, min(end, n)):
|
||||
max_row = i
|
||||
for k in range(i + 1, n):
|
||||
if abs(A[k, i]) > abs(A[max_row, i]):
|
||||
max_row = k
|
||||
|
||||
if max_row != i:
|
||||
A[[i, max_row]] = A[[max_row, i]]
|
||||
perm_count += 1
|
||||
|
||||
if abs(A[i, i]) > 1e-15:
|
||||
for k in range(i + 1, n):
|
||||
factor = A[k, i] / A[i, i]
|
||||
A[k, i:] -= factor * A[i, i:]
|
||||
|
||||
return A, perm_count
|
||||
|
||||
|
||||
def parallel_determinant(matrix, num_processes = None):
|
||||
if num_processes is None:
|
||||
num_processes = mp.cpu_count()
|
||||
|
||||
n = matrix.shape[0]
|
||||
|
||||
if n < 100 or num_processes == 1:
|
||||
return sequential_determinant(matrix)
|
||||
|
||||
import os
|
||||
old_threads = os.environ.get('OMP_NUM_THREADS', None)
|
||||
os.environ['OMP_NUM_THREADS'] = str(num_processes)
|
||||
os.environ['MKL_NUM_THREADS'] = str(num_processes)
|
||||
os.environ['NUMEXPR_NUM_THREADS'] = str(num_processes)
|
||||
|
||||
result = sequential_determinant(matrix)
|
||||
|
||||
if old_threads:
|
||||
os.environ['OMP_NUM_THREADS'] = old_threads
|
||||
else:
|
||||
os.environ.pop('OMP_NUM_THREADS', None)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_stable_matrix(size: int, scale: float = 1.0) -> np.ndarray:
|
||||
np.random.seed(13)
|
||||
|
||||
random_matrix = np.random.randn(size, size)
|
||||
Q, R = np.linalg.qr(random_matrix)
|
||||
|
||||
eigenvalues = np.random.uniform(0.5, 2.0, size) * scale
|
||||
D = np.diag(eigenvalues)
|
||||
|
||||
A = Q @ D @ Q.T
|
||||
|
||||
return A
|
||||
|
||||
|
||||
def benchmark(sizes, thread_counts):
|
||||
results = []
|
||||
|
||||
print("=" * 80)
|
||||
print("БЕНЧМАРК ВЫЧИСЛЕНИЯ ДЕТЕРМИНАНТА")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
for size in sizes:
|
||||
print(f"\n{'=' * 80}")
|
||||
print(f"Размер матрицы: {size}x{size}")
|
||||
print(f"{'=' * 80}")
|
||||
|
||||
matrix = generate_stable_matrix(size)
|
||||
|
||||
try:
|
||||
sign_numpy, logdet_numpy = np.linalg.slogdet(matrix)
|
||||
det_numpy = sign_numpy * np.exp(logdet_numpy) if logdet_numpy < 700 else sign_numpy * np.inf
|
||||
print(f"Детерминант (numpy): {det_numpy:.6e}")
|
||||
print(f" Sign: {sign_numpy}, Log|det|: {logdet_numpy:.6f}")
|
||||
except Exception as e:
|
||||
sign_numpy, logdet_numpy = None, None
|
||||
det_numpy = None
|
||||
print(f"Детерминант (numpy): Ошибка: {e}")
|
||||
|
||||
print()
|
||||
|
||||
start_time = time.time()
|
||||
sign_seq, logdet_seq = sequential_determinant(matrix)
|
||||
seq_time = time.time() - start_time
|
||||
det_seq = to_float(sign_seq, logdet_seq)
|
||||
|
||||
print(f"Последовательный алгоритм:")
|
||||
print(f" Время: {seq_time:.4f} сек")
|
||||
print(f" Детерминант: {det_seq:.6e}")
|
||||
print(f" Sign: {sign_seq}, Log|det|: {logdet_seq:.6f}")
|
||||
|
||||
if logdet_numpy is not None:
|
||||
log_error = abs(logdet_seq - logdet_numpy)
|
||||
print(f" Ошибка Log|det|: {log_error:.10f}")
|
||||
|
||||
results.append({
|
||||
'size': size,
|
||||
'threads': 1,
|
||||
'time': seq_time,
|
||||
'determinant': det_seq,
|
||||
'speedup': 1.0
|
||||
})
|
||||
|
||||
print()
|
||||
|
||||
for num_threads in thread_counts:
|
||||
if num_threads == 1:
|
||||
continue
|
||||
|
||||
start_time = time.time()
|
||||
sign_par, logdet_par = parallel_determinant(matrix, num_threads)
|
||||
par_time = time.time() - start_time
|
||||
det_par = to_float(sign_par, logdet_par)
|
||||
|
||||
speedup = seq_time / par_time if par_time > 0 else 0
|
||||
efficiency = speedup / num_threads * 100
|
||||
|
||||
print(f"Параллельный алгоритм ({num_threads} потоков):")
|
||||
print(f" Время: {par_time:.4f} сек")
|
||||
print(f" Детерминант: {det_par:.6e}")
|
||||
print(f" Ускорение: {speedup:.2f}x")
|
||||
print(f" Эффективность: {efficiency:.1f}%")
|
||||
|
||||
if logdet_numpy is not None:
|
||||
log_error = abs(logdet_par - logdet_numpy)
|
||||
print(f" Ошибка Log|det|: {log_error:.10f}")
|
||||
|
||||
results.append({
|
||||
'size': size,
|
||||
'threads': num_threads,
|
||||
'time': par_time,
|
||||
'determinant': det_par,
|
||||
'speedup': speedup
|
||||
})
|
||||
|
||||
print()
|
||||
|
||||
return results
|
||||
|
||||
if __name__ == "__main__":
|
||||
sizes = [100, 300, 500]
|
||||
thread_counts = [1, 2, 4, mp.cpu_count()]
|
||||
|
||||
thread_counts = sorted(list(set(thread_counts)))
|
||||
|
||||
results = benchmark(sizes, thread_counts)
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ")
|
||||
print("=" * 80)
|
||||
print()
|
||||
print(f"{'Размер':<10} {'Потоки':<10} {'Время (сек)':<15} {'Ускорение':<12} {'Эффективность'}")
|
||||
print("-" * 80)
|
||||
|
||||
for r in results:
|
||||
efficiency = (r['speedup'] / r['threads'] * 100) if r['threads'] > 1 else 100
|
||||
print(f"{r['size']:<10} {r['threads']:<10} {r['time']:<15.4f} {r['speedup']:<12.2f} {efficiency:>6.1f}%")
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("АНАЛИЗ ПРОИЗВОДИТЕЛЬНОСТИ")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
for size in sizes:
|
||||
size_results = [r for r in results if r['size'] == size]
|
||||
if len(size_results) > 1:
|
||||
seq_time = size_results[0]['time']
|
||||
best_parallel = min(size_results[1:], key=lambda x: x['time'])
|
||||
|
||||
print(f"Матрица {size}x{size}:")
|
||||
print(f" Последовательное время: {seq_time:.4f} сек")
|
||||
print(f" Лучшее параллельное: {best_parallel['time']:.4f} сек ({best_parallel['threads']} потоков)")
|
||||
print(f" Максимальное ускорение: {best_parallel['speedup']:.2f}x")
|
||||
print()
|
||||
Reference in New Issue
Block a user