Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
385 changes: 385 additions & 0 deletions tasks/alekseev_a_mult_matrix_crs/all/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,385 @@
# Умножение разреженных матриц. Элементы типа double. Формат хранения CRS — ALL

- Student: Алексеев Артемий Алексеевич
- Technology: ALL
- Variant: 4

## 1. Контекст

Задача — произведение двух разреженных матриц `A` и `B`
в формате хранения CRS (Compressed Row Storage).

Для реализации используется
гибридная технология распараллеливания,
сочетающая:
- MPI — для распределения вычислений
между процессами;
- OpenMP — для многопоточной обработки
внутри каждого процесса.

В основе вычислений лежит
алгоритм Густавсона (Gustavson SpGEMM),
адаптированный
для гибридной параллельной архитектуры.

MPI используется
для распределения строк матрицы `A`
между процессами,
а OpenMP —
для параллельной обработки строк
внутри локального диапазона процесса.

## 2. Постановка задачи

**Входные данные**
(`InType = std::tuple<CRSMatrix, CRSMatrix>`):

- `A` — первая разреженная матрица
в формате CRS;
- `B` — вторая разреженная матрица
в формате CRS.

Структура хранения матрицы:

```cpp
struct CRSMatrix {
std::vector<double> values;
std::vector<std::size_t> col_indices;
std::vector<std::size_t> row_ptr;
std::size_t rows = 0;
std::size_t cols = 0;
};
```

Где:
- `values` — ненулевые элементы;
- `col_indices` — индексы столбцов;
- `row_ptr` — указатели начала строк;
- `rows`, `cols` — размеры матрицы.

**Выходные данные**
(`OutType = CRSMatrix`):

результирующая матрица `C = A·B`
в формате CRS.

**Ограничения:**

- размеры матриц
должны быть корректны;
- `row_ptr`
не должен быть пустым;
- число столбцов матрицы `A`
должно совпадать
с числом строк матрицы `B`;
- индексы столбцов
не должны выходить
за границы матрицы.

**Крайние случаи:**

- матрица `1×1`;
- нулевая матрица;
- прямоугольные матрицы;
- диагональные разреженные матрицы;
- строки без ненулевых элементов.

## 3. Базовый алгоритм

Используется алгоритм Густавсона
для умножения разреженных матриц.

Для каждой строки матрицы `A`
выполняется проход
по ненулевым элементам строки.
Для каждого элемента `A(i, k)`
выбирается соответствующая строка матрицы `B`,
после чего результаты умножения
накапливаются
во временном аккумуляторе.

```text
для каждой строки i матрицы A:
для каждого ненулевого элемента A(i, k):
для каждого элемента B(k, j):
accum[j] += A(i, k) * B(k, j)

сохранить ненулевые accum[j] в CRS
```

Распараллеливание реализуется
на двух уровнях:

1. MPI:
строки матрицы `A`
распределяются
между процессами.

2. OpenMP:
внутри каждого MPI-процесса
строки обрабатываются параллельно
с помощью потоков.

Распределение строк между процессами:

```cpp
int rows_per_proc =
static_cast<int>(a.rows) / size;
```

OpenMP-распараллеливание:

```cpp
#pragma omp for schedule(dynamic)
```

Асимптотика по времени:

```text
O(nnz(A) * bandwidth(B))
```

В худшем случае:

```text
O(N³)
```

Асимптотика по памяти:

```text
O(M * T * P)
```

где:
- `M` —
количество столбцов результирующей матрицы;
- `T` —
количество потоков OpenMP;
- `P` —
количество MPI-процессов.

Дополнительная память используется
для локальных аккумуляторов,
буферов MPI
и временных контейнеров потоков.

## 4. Детали реализации

**Файлы:**

- `all/include/ops_all.hpp`
- `all/src/ops_all.cpp`

Перед началом вычислений
матрицы распространяются
между процессами
с помощью MPI:

```cpp
MPI_Bcast(...)
```

Каждый процесс получает
копии матриц `A` и `B`,
после чего вычисляет
свой диапазон строк.

Ключевой фрагмент `RunImpl`:

```cpp
#pragma omp parallel default(none) \
shared(p_a, p_b, p_lv, p_lc, local_n, local_start)
{
std::vector<double> accum(p_b->cols, 0.0);
std::vector<int> touched_flag(p_b->cols, -1);
std::vector<std::size_t> touched_cols;

#pragma omp for schedule(dynamic)
for (int i = 0; i < local_n; ++i) {

auto g_row =
static_cast<std::size_t>(local_start) +
static_cast<std::size_t>(i);

ProcessRow(g_row,
*p_a,
*p_b,
(*p_lv)[i],
(*p_lc)[i],
accum,
touched_flag,
touched_cols);
}
}
```

Каждый поток использует:
- локальный аккумулятор `accum`;
- локальный массив `touched_flag`;
- локальный список `touched_cols`.

Результаты вычислений
сохраняются
во временные контейнеры:

```cpp
std::vector<std::vector<double>> local_v(local_n);
std::vector<std::vector<std::size_t>> local_c(local_n);
```

После завершения вычислений
локальные результаты
собираются
с использованием:

```cpp
MPI_Gatherv(...)
```

Финальная CRS-структура
формируется
на процессе с `rank = 0`,
после чего
распространяется
между всеми процессами.

**Этапы пайплайна:**

- `ValidationImpl` —
проверяет корректность размеров матриц
на главном процессе;
- `PreProcessingImpl` —
выполняет подготовку вычислений;
- `RunImpl` —
выполняет MPI + OpenMP умножение;
- `PostProcessingImpl` —
синхронизирует процессы
через `MPI_Barrier`.

## 5. Проверка корректности

Корректность гибридной версии
проверяется
функциональными тестами
из `tests/functional/main.cpp`.

Проверяются следующие случаи:

- единичные матрицы;
- нулевые матрицы;
- матрицы `1×1`;
- прямоугольные матрицы;
- диагональные разреженные матрицы;
- стандартные матрицы `2×2`.

Для проверки результатов используется функция:

```cpp
CompareCRS()
```

Сравнение выполняется:
- по размерам матриц;
- по структуре CRS;
- по индексам столбцов;
- по значениям элементов
с точностью `1e-10`.

Результат функциональных тестов:

```text
[ PASSED ] 6 tests.
```

## 6. Экспериментальная среда

| Параметр | Значение |
| ---------------- | ------------------------------------------ |
| CPU | AMD Ryzen 7 8845HS w/ Radeon 780M Graphics |
| RAM | 32,0 ГБ |
| MPI Processes | 4 |
| OpenMP Threads | 4 |
| Total Workers | 16 |
| OS | Windows 11 Pro |
| Compiler | g++ / clang++ (Release) |
| CMake build type | Release |

**Команды сборки и запуска:**

```bash
cmake -B build

# Функциональные тесты
mpirun -np 4 ./build/bin/ppc_func_tests --gtest_filter="*alekseev_a*"

# Тесты производительности
mpirun -np 4 ./build/bin/ppc_perf_tests --gtest_filter="*alekseev_a*"
```

## 7. Результаты

Результаты perf-тестирования
(`pipeline` и `task_run`,
режим `performance`):

| Режим | Время (с) | Рабочих единиц |
| -------- | ---------- | -------------- |
| pipeline | 0.842731 | 16 |
| task_run | 0.691508 | 16 |

Тестирование выполнялось
на ленточных разреженных матрицах
размера:

```cpp
kSize = 10000
kBandwidth = 30
```

Наиболее затратной частью алгоритма
является проход
по строкам матрицы `B`
и накопление промежуточных значений
во временных аккумуляторах.

Гибридная MPI + OpenMP реализация
показывает наилучшую производительность
среди всех реализованных технологий
за счет двухуровневого распараллеливания.

MPI обеспечивает
эффективное распределение вычислений
между процессами,
а OpenMP —
дополнительное ускорение
внутри каждого процесса.

## 8. Выводы

В ходе работы была реализована
гибридная параллельная версия
алгоритма Густавсона
для умножения разреженных матриц
в формате CRS
с использованием MPI и OpenMP.

Распределение строк матрицы
между MPI-процессами
и дополнительное OpenMP-распараллеливание
внутри процессов
позволили эффективно использовать
многоядерную архитектуру процессора.

Использование локальных временных структур
исключило возникновение состояния гонки
при накоплении промежуточных результатов.

Гибридная реализация
демонстрирует максимальное ускорение
по сравнению
с последовательной версией
и превосходит реализации
на STL, OpenMP и TBB
за счет комбинированного подхода
к распараллеливанию.
Loading
Loading