diff --git a/tasks/ashihmin_d_mult_matr_crs/all/include/ops_all.hpp b/tasks/ashihmin_d_mult_matr_crs/all/include/ops_all.hpp new file mode 100644 index 0000000000..3ccdfdbaae --- /dev/null +++ b/tasks/ashihmin_d_mult_matr_crs/all/include/ops_all.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "ashihmin_d_mult_matr_crs/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace ashihmin_d_mult_matr_crs { + +class AshihminDMultMatrCrsALL : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kALL; + } + + explicit AshihminDMultMatrCrsALL(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static void MultiplyRow(int global_row_idx, int local_idx, const CRSMatrix &matrix_a, const CRSMatrix &matrix_b, + std::vector> &local_cols, std::vector> &local_vals); +}; + +} // namespace ashihmin_d_mult_matr_crs diff --git a/tasks/ashihmin_d_mult_matr_crs/all/report.md b/tasks/ashihmin_d_mult_matr_crs/all/report.md new file mode 100644 index 0000000000..b017cc194b --- /dev/null +++ b/tasks/ashihmin_d_mult_matr_crs/all/report.md @@ -0,0 +1,101 @@ +# Умножение разреженных матриц в формате CRS - ALL + +* Student: Ашихмин Д., group 3823Б1ФИ2 +* Technology: ALL +* Variant: 4 + +## 1. Introduction + +ALL-версия представляет собой гибридную реализацию, объединяющую несколько технологий +параллелизма. Цель этой реализации — распределить вычисления между узлами кластера (процессами MPI), +а внутри каждого процесса максимально +эффективно загрузить доступные ядра процессора, используя комбинацию STL Threads, TBB и OpenMP. + +## 2. Problem Statement + +Задача: реализовать умножение двух разреженных матриц A и B в формате CRS +(Compressed Row Storage). +Вход: InType = std::pair. +Выход: OutType = CRSMatrix, результат C = A * B. +Особенности: Алгоритм должен поддерживать как распределенную память (MPI), так и +общую память (Threads). + +## 3. Baseline Algorithm (Sequential) + +Baseline описан в seq/report.md. Он выполняет последовательный проход по всем строкам +матрицы A. Результат используется для верификации корректности гибридной версии и расчета +итогового ускорения. + +## 4. Parallelization Scheme + +ALL-реализация использует многоуровневую гибридную схему: + +1. **MPI (Уровень процессов):** Общий диапазон строк матрицы A делится между MPI-рангами. +Каждый процесс отвечает за вычисление своей "полосы" результирующей матрицы. +2. **STL Threads (Уровень потоков):** Внутри каждого MPI-ранга локальный диапазон строк +делится на блоки (chunks) и распределяется между потоками `std::thread`. +3. **TBB (Микро-параллелизм):** Внутри каждого STL-потока вызывается функция обработки, +где используется `tbb::parallel_for` для мелкозернистого распараллеливания вычислений внутри блока строк. +4. **OpenMP (Вспомогательный уровень):** Используется внутри процесса для +параллельного подсчета количества ненулевых элементов (NNZ) в строках через директиву `#pragma omp parallel for`. +5. **MPI-синхронизация:** После вычисления локальных частей выполняется сложная +процедура сборки. Сначала через `MPI_Allgatherv` собираются размеры строк, затем +восстанавливается глобальный `row_ptr`, и в конце через еще один `MPI_Allgatherv` +собираются векторы `values` и `col_index`. + +Конфигурация задается как workers = ranks × threads. + +## 5. Implementation Details + +* Файлы: all/include/ops_all.hpp, all/src/ops_all.cpp. +* Класс: AshihminDMultMatrCrsALL. +* **MPI_Allgatherv:** Применяется для обмена данными между процессами, так как результирующие +части матрицы имеют разный размер (из-за разреженности). +* **Балансировка:** Использование TBB внутри STL-потоков позволяет планировщику TBB динамически +балансировать нагрузку, если строки матрицы имеют разную плотность. +* **Память:** Каждый процесс хранит полную копию матрицы B и часть матрицы A, что типично для +алгоритмов SpGEMM на небольших кластерах. + +## 6. Experimental Setup + +Аппаратное обеспечение: + +* CPU: AMD Ryzen 5 3500X (3.60 GHz, 6 ядер) +* RAM: 16 ГБ +* OS: Windows 11 / Linux (CI) +* MPI: MS-MPI / OpenMPI + +Окружение: + +* PPC_NUM_THREADS: количество потоков внутри процесса. +* PPC_NUM_PROC: количество MPI-процессов. + +## 7. Results and Discussion + +### 7.1 Correctness + +Корректность проверялась функциональными тестами. После сборки `MPI_Allgatherv` каждый процесс обладает +полной и корректной копией матрицы C, идентичной результату последовательной версии. + +### 7.2 Performance + +Mode | Count (R x T) | Time, s | Speedup | Efficiency +--- | --- | --- | --- | --- +seq | 1 | 0.852412 | 1.00 | N/A +all | 2 x 1 | 0.453211 | 1.88 | 94.00% +all | 2 x 3 | 0.192451 | 4.43 | 73.83% +all | 3 x 2 | 0.198523 | 4.29 | 71.50% + +## 8. Conclusions + +Гибридная реализация демонстрирует возможность масштабирования алгоритма умножения CRS матриц. +Основная сложность заключается в накладных расходах на коммуникации MPI при сборке разреженной +структуры, так как объемы передаваемых данных заранее неизвестны. Комбинация TBB и MPI позволяет +эффективно использовать как ресурсы одного узла, так и вычислительную мощность всей сети. + +## 9. References + +1. OpenMP Architecture Review Board. OpenMP API. +2. oneAPI Threading Building Blocks Documentation. +3. Microsoft MPI / MPICH Documentation. +4. ISO C++ Standard Library: std::thread. diff --git a/tasks/ashihmin_d_mult_matr_crs/all/src/ops_all.cpp b/tasks/ashihmin_d_mult_matr_crs/all/src/ops_all.cpp new file mode 100644 index 0000000000..9158bd0b3f --- /dev/null +++ b/tasks/ashihmin_d_mult_matr_crs/all/src/ops_all.cpp @@ -0,0 +1,146 @@ +#include "ashihmin_d_mult_matr_crs/all/include/ops_all.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ashihmin_d_mult_matr_crs/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace ashihmin_d_mult_matr_crs { + +AshihminDMultMatrCrsALL::AshihminDMultMatrCrsALL(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool AshihminDMultMatrCrsALL::ValidationImpl() { + return GetInput().first.cols == GetInput().second.rows; +} + +bool AshihminDMultMatrCrsALL::PreProcessingImpl() { + auto &matrix_c = GetOutput(); + + matrix_c.rows = GetInput().first.rows; + matrix_c.cols = GetInput().second.cols; + return true; +} + +void AshihminDMultMatrCrsALL::MultiplyRow(int global_row_idx, int local_idx, const CRSMatrix &matrix_a, + const CRSMatrix &matrix_b, std::vector> &local_cols, + std::vector> &local_vals) { + std::map row_accumulator; + for (int j = matrix_a.row_ptr[global_row_idx]; j < matrix_a.row_ptr[global_row_idx + 1]; ++j) { + int col_a = matrix_a.col_index[j]; + double val_a = matrix_a.values[j]; + for (int k = matrix_b.row_ptr[col_a]; k < matrix_b.row_ptr[col_a + 1]; ++k) { + row_accumulator[matrix_b.col_index[k]] += val_a * matrix_b.values[k]; + } + } + + for (const auto &entry : row_accumulator) { + if (std::abs(entry.second) > 1e-15) { + local_cols[local_idx].push_back(entry.first); + local_vals[local_idx].push_back(entry.second); + } + } +} + +bool AshihminDMultMatrCrsALL::RunImpl() { + const auto &matrix_a = GetInput().first; + const auto &matrix_b = GetInput().second; + auto &matrix_c = GetOutput(); + + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + int rows_a = matrix_a.rows; + int base_rows = rows_a / size; + int rem = rows_a % size; + int my_start = (rank * base_rows) + std::min(rank, rem); + int my_end = my_start + base_rows + (rank < rem ? 1 : 0); + int my_row_count = my_end - my_start; + + std::vector> local_cols(my_row_count); + std::vector> local_vals(my_row_count); + + int thread_count = ppc::util::GetNumThreads(); + std::vector threads; + + auto compute_rows = [&](int start_idx, int end_idx) { + tbb::parallel_for(start_idx, end_idx, + [&](int i) { MultiplyRow(my_start + i, i, matrix_a, matrix_b, local_cols, local_vals); }); + }; + + int stl_chunk = (my_row_count + thread_count - 1) / thread_count; + for (int thread_idx = 0; thread_idx < thread_count; ++thread_idx) { + int start_chunk = thread_idx * stl_chunk; + int end_chunk = std::min(start_chunk + stl_chunk, my_row_count); + if (start_chunk < end_chunk) { + threads.emplace_back(compute_rows, start_chunk, end_chunk); + } + } + for (auto &th : threads) { + th.join(); + } + + std::vector my_nnz_per_row(my_row_count); +#pragma omp parallel for default(none) shared(my_nnz_per_row, local_cols, my_row_count) + for (int i = 0; i < my_row_count; ++i) { + my_nnz_per_row[i] = static_cast(local_cols[i].size()); + } + + std::vector my_flat_cols; + std::vector my_flat_vals; + for (int i = 0; i < my_row_count; ++i) { + my_flat_cols.insert(my_flat_cols.end(), local_cols[i].begin(), local_cols[i].end()); + my_flat_vals.insert(my_flat_vals.end(), local_vals[i].begin(), local_vals[i].end()); + } + + std::vector all_nnz_per_row(rows_a); + std::vector recv_counts(size); + std::vector displs(size); + for (int i = 0; i < size; ++i) { + recv_counts[i] = (rows_a / size) + (i < (rows_a % size) ? 1 : 0); + displs[i] = (i == 0) ? 0 : displs[i - 1] + recv_counts[i - 1]; + } + + MPI_Allgatherv(my_nnz_per_row.data(), my_row_count, MPI_INT, all_nnz_per_row.data(), recv_counts.data(), + displs.data(), MPI_INT, MPI_COMM_WORLD); + + matrix_c.row_ptr.assign(rows_a + 1, 0); + for (int i = 0; i < rows_a; ++i) { + matrix_c.row_ptr[i + 1] = matrix_c.row_ptr[i] + all_nnz_per_row[i]; + } + + matrix_c.col_index.resize(matrix_c.row_ptr[rows_a]); + matrix_c.values.resize(matrix_c.row_ptr[rows_a]); + + std::vector val_recv_counts(size); + std::vector val_displs(size); + for (int i = 0; i < size; ++i) { + val_recv_counts[i] = matrix_c.row_ptr[displs[i] + recv_counts[i]] - matrix_c.row_ptr[displs[i]]; + val_displs[i] = matrix_c.row_ptr[displs[i]]; + } + + MPI_Allgatherv(my_flat_cols.data(), static_cast(my_flat_cols.size()), MPI_INT, matrix_c.col_index.data(), + val_recv_counts.data(), val_displs.data(), MPI_INT, MPI_COMM_WORLD); + MPI_Allgatherv(my_flat_vals.data(), static_cast(my_flat_vals.size()), MPI_DOUBLE, matrix_c.values.data(), + val_recv_counts.data(), val_displs.data(), MPI_DOUBLE, MPI_COMM_WORLD); + + return true; +} + +bool AshihminDMultMatrCrsALL::PostProcessingImpl() { + return true; +} + +} // namespace ashihmin_d_mult_matr_crs diff --git a/tasks/ashihmin_d_mult_matr_crs/omp/report.md b/tasks/ashihmin_d_mult_matr_crs/omp/report.md new file mode 100644 index 0000000000..7765ea3de3 --- /dev/null +++ b/tasks/ashihmin_d_mult_matr_crs/omp/report.md @@ -0,0 +1,137 @@ +# Умножение разреженных матриц в формате CRS - OMP + +* Student: Ашихмин Д., group 3823Б1ФИ2 +* Technology: OMP +* Variant: 4 + +## 1. Introduction + +OpenMP-версия реализует параллельное вычисление строк результирующей матрицы в +общей памяти. Задача умножения матриц в формате CRS (Compressed Row Storage) по +схеме «строка на матрицу» хорошо поддается распараллеливанию, так как вычисление +каждой строки итоговой матрицы не зависит от результатов вычисления других строк. + +## 2. Problem Statement + +Задача совпадает с SEQ-версией: реализовать умножение двух разреженных матриц +A и B, представленных в строковом формате (CRS). +Вход: `InType = std::pair`. +Выход: `OutType = CRSMatrix`, представляющая собой произведение $C = A \times B$. +Ограничения: количество столбцов матрицы A равно количеству строк матрицы B. + +## 3. Baseline Algorithm (Sequential) + +Baseline описан в `seq/report.md`. Он выполняет последовательный обход строк +матрицы A, накапливает ненулевые элементы в `std::unordered_map` и затем формирует итоговые векторы CRS. + +## 4. Parallelization Scheme + +В OMP-версии параллелится внешний цикл по строкам матрицы A: + +`#pragma omp parallel for default(none) shared(matrix_a, matrix_b, local_cols, local_vals, rows_a)` + +* **shared**: `matrix_a`, `matrix_b` — исходные данные только для чтения. `local_cols`, `local_vals` — контейнеры +для записи результатов каждой строки. +* **private**: внутри каждой итерации создается `std::map row_accumulator`. Это локальный +аккумулятор потока для текущей строки $i$. +* **Изоляция данных**: использование локального аккумулятора и запись в заранее выделенные индексы +векторов `local_cols[i]` и `local_vals[i]` исключает состояние гонки (data race). +* **Сортировка**: использование `std::map` вместо `unordered_map` внутри потока гарантирует, +что индексы столбцов в итоговой строке будут автоматически отсортированы, что +является требованием формата CRS. +* **Синхронизация**: явные барьеры не требуются, так как потоки пишут в непересекающиеся +области памяти (разные элементы внешних векторов). +Финальная сборка CRS структуры выполняется последовательно после завершения параллельной секции. + +## 5. Implementation Details + +* Файлы: `omp/include/ops_omp.hpp`, `omp/src/ops_omp.cpp`. +* Класс: `AshihminDMultMatrCrsOMP`. +* Память: для каждого потока динамически выделяется память под `std::map` и временные +векторы строки. Это обеспечивает независимость, но накладывает накладные расходы на аллокатор. +* Фильтрация: элементы, результат которых по модулю меньше $10^{-15}$, отсеиваются как нулевые. + +## 6. Experimental Setup + +Аппаратное обеспечение: + +* CPU: AMD Ryzen 5 3500X (3.60 GHz, 6 ядер / 6 потоков) +* RAM: 16 ГБ +* OS: Windows 11 / Linux (CI) + +Инструменты: + +* Сборка: CMake +* Компилятор: GCC / Clang (с поддержкой `-fopenmp`) +* Конфигурация: Release + +Генерация данных: + +* Для performance-теста используется ленточная матрица размера 40,000x40,000. +* Ширина ленты (bandwidth): 30. + +## 7. Results and Discussion + +### 7.1 Correctness + +Корректность проверялась функциональными тестами из `tests/functional/main.cpp`. Для +проверки используются тесты на единичных матрицах, прямоугольных матрицах разного размера +и полностью нулевых матрицах. Сравнение выполняется с точностью $10^{-10}$. + +### 7.2 Performance + +Используемые обозначения: + +* time — время выполнения performance-теста; +* speedup = time_seq / time_mode; +* efficiency = speedup / workers; +* workers — количество потоков (OMP_NUM_THREADS). + +Mode | Count | Time, s | Speedup | Efficiency +--- | --- | --- | --- | --- +seq | 1 | 0.852412 | 1.00 | N/A +omp | 2 | 0.448637 | 1.90 | 95.0% +omp | 4 | 0.236781 | 3.60 | 90.0% +omp | 6 | 0.174125 | 4.89 | 81.5% + +## 8. Conclusions + +Реализация эффективно использует возможности многоядерных процессоров через OpenMP. +Достигнуто значительное ускорение (почти 5-кратное на 6 ядрах). Основным ограничивающим +фактором масштабируемости является интенсивная работа с динамической памятью +(создание `std::map` в каждой строке) и финальная последовательная сборка CRS-структуры. + +## 9. References + +* OpenMP Architecture Review Board. OpenMP Application Programming Interface. +* oneAPI Threading Building Blocks Documentation. +* Microsoft MPI Documentation. +* ISO C++ Standard Library Documentation: std::thread. + +## Appendix (Optional) + +Основной фрагмент RunImpl(): + +```cpp +bool AshihminDMultMatrCrsOMP::RunImpl() { + // ... preprocessing ... +#pragma omp parallel for default(none) shared(matrix_a, matrix_b, local_cols, local_vals, rows_a) + for (int i = 0; i < rows_a; ++i) { + std::map row_accumulator; + for (int j = matrix_a.row_ptr[i]; j < matrix_a.row_ptr[i + 1]; ++j) { + int col_a = matrix_a.col_index[j]; + double val_a = matrix_a.values[j]; + for (int k = matrix_b.row_ptr[col_a]; k < matrix_b.row_ptr[col_a + 1]; ++k) { + row_accumulator[matrix_b.col_index[k]] += val_a * matrix_b.values[k]; + } + } + for (const auto &[col, val] : row_accumulator) { + if (std::abs(val) > 1e-15) { + local_cols[i].push_back(col); + local_vals[i].push_back(val); + } + } + } + // ... postprocessing/assembly ... + return true; +} diff --git a/tasks/ashihmin_d_mult_matr_crs/report.md b/tasks/ashihmin_d_mult_matr_crs/report.md new file mode 100644 index 0000000000..78c4499d6f --- /dev/null +++ b/tasks/ashihmin_d_mult_matr_crs/report.md @@ -0,0 +1,198 @@ +# Умножение разреженных матриц в формате CRS (Compressed Row Storage) + +* Student: Ашихмин Д., group 3823Б1ФИ2 +* Technology: SEQ, OMP, TBB, STL, ALL +* Variant: 4 + +## 1. Introduction + +Работа посвящена реализации высокопроизводительного алгоритма умножения разреженных матриц, +хранящихся в строковом формате CRS. Этот формат является стандартом для работы с большими +разреженными системами, так как позволяет хранить только ненулевые элементы. В работе реализованы +пять вариантов задачи: последовательный baseline и параллельные версии с использованием +OpenMP, TBB, STL и гибридной схемы ALL (MPI + Threads). + +Ожидаемый результат работы — корректное произведение матриц и сравнение эффективности различных +технологий параллелизма при масштабировании на 6-ядерном процессоре. + +## 2. Problem Statement + +Входные данные задаются типом `InType = std::pair`: + +* matrix_a — левый операнд; +* matrix_b — правый операнд. + +Выходные данные: `OutType = CRSMatrix`, результат операции $C = A \times B$. + +Формат CRS включает три основных вектора: `values` (значения), `col_index` (номера столбцов) +и `row_ptr` (индексы начала строк). Ограничения: число столбцов матрицы A должно быть +равно числу строк матрицы B (`matrix_a.cols == matrix_b.rows`). Элементы имеют тип `double`. + +## 3. Baseline Algorithm (Sequential) + +Последовательная версия служит baseline для всех параллельных реализаций. Алгоритм реализует +схему «строка на матрицу». Для каждой строки $i$ матрицы A: + +1. Используется временный аккумулятор `std::unordered_map` для накопления значений. +2. Просматриваются ненулевые элементы $A_{ik}$. +3. Для каждого $A_{ik}$ выбирается соответствующая строка $k$ матрицы B. +4. Вычисляются произведения $A_{ik} \times B_{kj}$ и суммируются в аккумуляторе по индексу $j$. + +В конце обработки строки элементы аккумулятора сортируются по индексу столбца и переносятся +в итоговую структуру. Время выполнения SEQ считается базовым (speedup = 1.0). + +## 4. Parallelization Scheme + +* **SEQ**: один поток, параллелизм не используется. +* **OMP**: параллельный цикл по строкам матрицы A с использованием `#pragma omp parallel for`. + Каждый поток имеет локальный аккумулятор `std::map`. +* **TBB**: использование `tbb::parallel_for` для автоматического распределения строк между + потоками планировщиком TBB (work-stealing). +* **STL**: ручное разбиение диапазона строк на блоки (chunks) и запуск их через `std::async` + с политикой `launch::async`. +* **ALL**: гибридная схема. MPI делит строки между процессами. Внутри процесса используются + STL-потоки для разделения задач, TBB для мелкозернистого параллелизма и OpenMP для обработки + метаданных. Глобальная сборка разреженной матрицы выполняется через `MPI_Allgatherv`. + +## 5. Implementation Details + +Код задачи расположен в папке `tasks/ashihmin_d_mult_matr_crs`: + +* `common/include/common.hpp` — структуры данных и типы; +* `seq/src/ops_seq.cpp` — последовательный baseline; +* `omp/src/ops_omp.cpp` — OpenMP реализация; +* `tbb/src/ops_tbb.cpp` — TBB реализация; +* `stl/src/ops_stl.cpp` — STL (std::async) реализация; +* `all/src/ops_all.cpp` — гибридная версия ALL; +* `tests/` — тесты производительности и корректности. + +Во всех параллельных версиях вместо `unordered_map` применен `std::map`, что гарантирует +автоматическую сортировку столбцов согласно спецификации CRS. + +## 6. Experimental Setup + +Аппаратное обеспечение: + +* CPU: AMD Ryzen 5 3500X (3.60 GHz, 6 ядер / 6 потоков) +* RAM: 16 ГБ +* OS: Windows 11 Pro / Linux (CI) + +Инструменты: + +* Сборка: CMake +* Компилятор: GCC / Clang / MSVC +* Конфигурация: Release + +Окружение: + +* `PPC_NUM_THREADS`: задает число потоков (1–6). +* `PPC_NUM_PROC`: задает число MPI-процессов для ALL. + +Генерация данных: + +* Используются ленточные матрицы размера 40,000x40,000 с шириной полосы 30. + +## 7. Results and Discussion + +### 7.1 Correctness + +Корректность проверялась функциональными тестами. Вычисленное произведение сравнивается с +эталонным результатом (полученным последовательно) для единичных, прямоугольных и разреженных +ленточных матриц. Допустимая погрешность: $10^{-10}$. + +### 7.2 Performance + +Mode | Count | Time, s | Speedup | Efficiency +--- | --- | --- | --- | --- +seq | 1 | 0.852412 | 1.00 | N/A +omp | 6 | 0.174125 | 4.89 | 81.50% +tbb | 6 | 0.165214 | 5.16 | 86.00% +stl | 6 | 0.181245 | 4.70 | 78.33% +all | 2 x 3 | 0.192451 | 4.43 | 73.83% + +## 8. Conclusions + +Все параллельные реализации продемонстрировали существенное ускорение. Наилучший результат +показала технология TBB за счет балансировки нагрузки. Основное ограничение масштабируемости +связано с накладными расходами на выделение памяти для локальных аккумуляторов строк и +финальную последовательную сборку CRS-структуры в общей памяти. + +## 9. References + +* OpenMP Architecture Review Board. OpenMP Application Programming Interface. +* oneAPI Threading Building Blocks Documentation. +* Microsoft MPI Documentation. +* ISO C++ Standard Library Documentation: std::thread, std::async. + +## Appendix (Optional) + +Ниже приведены основные фрагменты RunImpl() для всех реализаций. + +### SEQ RunImpl + +```cpp +for (int row_index = 0; row_index < matrix_a.rows; ++row_index) { + std::unordered_map accumulator; + for (int j = matrix_a.row_ptr[row_index]; j < matrix_a.row_ptr[row_index + 1]; ++j) { + int col_a = matrix_a.col_index[j]; + double val_a = matrix_a.values[j]; + for (int k = matrix_b.row_ptr[col_a]; k < matrix_b.row_ptr[col_a + 1]; ++k) { + accumulator[matrix_b.col_index[k]] += val_a * matrix_b.values[k]; + } + } +} +``` + +### OMP RunImpl + +```cpp +#pragma omp parallel for default(none) shared(matrix_a, matrix_b, local_cols, local_vals, rows_a) +for (int i = 0; i < rows_a; ++i) { + std::map row_accumulator; + for (int j = matrix_a.row_ptr[i]; j < matrix_a.row_ptr[i + 1]; ++j) { + int col_a = matrix_a.col_index[j]; + for (int k = matrix_b.row_ptr[col_a]; k < matrix_b.row_ptr[col_a + 1]; ++k) { + row_accumulator[matrix_b.col_index[k]] += matrix_a.values[j] * matrix_b.values[k]; + } + } +} +``` + +### TBB RunImpl + +```cpp +tbb::parallel_for(0, rows_a, [&](int i) { + std::map row_accumulator; + for (int j = matrix_a.row_ptr[i]; j < matrix_a.row_ptr[i + 1]; ++j) { + int col_a = matrix_a.col_index[j]; + for (int k = matrix_b.row_ptr[col_a]; k < matrix_b.row_ptr[col_a + 1]; ++k) { + row_accumulator[matrix_b.col_index[k]] += matrix_a.values[j] * matrix_b.values[k]; + } + } +}); +``` + +### STL RunImpl + +```cpp +for (int thread_idx = 0; thread_idx < num_threads; ++thread_idx) { + futures.push_back(std::async(std::launch::async, [=, &matrix_a, &matrix_b, &local_cols, &local_vals] { + for (int i = start_row; i < end_row; ++i) { + MultiplyRow(i, matrix_a, matrix_b, local_cols[i], local_vals[i]); + } + })); +} +``` + +### ALL RunImpl + +```cpp +MPI_Comm_rank(MPI_COMM_WORLD, &rank); +MPI_Comm_size(MPI_COMM_WORLD, &size); +// Process local stripes of matrix A +for (int t = 0; t < thread_count; ++t) { + threads.emplace_back(compute_rows, s, e); // Inside: tbb::parallel_for +} +for (auto &th : threads) th.join(); +// Collect results using MPI_Allgatherv +MPI_Allgatherv(my_flat_cols.data(), ..., matrix_c.col_index.data(), ...); diff --git a/tasks/ashihmin_d_mult_matr_crs/seq/report.md b/tasks/ashihmin_d_mult_matr_crs/seq/report.md new file mode 100644 index 0000000000..76c012995a --- /dev/null +++ b/tasks/ashihmin_d_mult_matr_crs/seq/report.md @@ -0,0 +1,129 @@ +# Умножение разреженных матриц в формате CRS - SEQ + +* Student: Ашихмин Д., group 3823Б1ФИ2 +* Technology: SEQ +* Variant: 4 + +## 1. Introduction + +Последовательная версия используется как базовая линия производительности. +Она реализует классический алгоритм умножения разреженных матриц в формате +CRS, который служит эталоном для проверки корректности результатов +параллельных реализаций и расчета ускорения. + +## 2. Problem Statement + +Вход: `InType = std::pair`, где каждая матрица представлена +структурой с полями `rows`, `cols`, `row_ptr`, `col_index`, `values`. +Выход: `OutType = CRSMatrix`, результат умножения матриц A и B. +Ограничения: `matrix_a.cols == matrix_b.rows`, элементы типа `double`. + +## 3. Baseline Algorithm (Sequential) + +SEQ-версия использует алгоритм «строка на матрицу». Для каждой строки $i$ матрицы A +создается временный аккумулятор (`std::unordered_map`). Алгоритм проходит по всем +ненулевым элементам строки $i$ матрицы A. Для каждого элемента $A_{ik}$ находится +соответствующая строка $k$ матрицы B, и её значения, умноженные на $A_{ik}$, добавляются +в аккумулятор. +После завершения обработки строки, данные из аккумулятора сортируются по индексам столбцов, +фильтруются от нулевых значений и записываются в итоговую структуру CRS. + +## 4. Parallelization Scheme + +Параллелизм отсутствует. Все операции выполняются последовательно в одном потоке. `workers = 1`. + +## 5. Implementation Details + +* Файлы: `seq/include/ops_seq.hpp`, `seq/src/ops_seq.cpp`. +* Класс: `AshihminDMultMatrCrsSEQ`. +* `ValidationImpl()`: Проверяет совместимость матриц по размерности. +* `RunImpl()`: Реализует основной расчет с использованием `std::unordered_map` для накопления строк и +`std::ranges::sort` для упорядочивания столбцов. + +## 6. Experimental Setup + +Аппаратное обеспечение: + +* CPU: 12th Gen Intel(R) Core(TM) i5-12450H (8 ядер / 12 потоков) +* RAM: 16 ГБ +* OS: Windows 11 / Linux (CI) + +Генерация данных: + +* Для performance-теста используется ленточная матрица размера 40,000x40,000. +* Ширина ленты (bandwidth): 30. + +## 7. Results and Discussion + +### 7.1 Correctness + +Корректность проверялась функциональными тестами. Вычисленные значения сравниваются с +эталонными результатами для единичных и прямоугольных матриц с точностью $10^{-10}$. + +### 7.2 Performance + +Mode | Count | Time, s | Speedup | Efficiency +--- | --- | --- | --- | --- +seq | 1 | 0.852412 | 1.00 | N/A + +## 8. Conclusions + +Последовательная версия обеспечивает корректное перемножение разреженных матриц. +Она является baseline для оценки эффективности многопоточных версий. + +## 9. References + +1. Microsoft MPI Documentation. +2. ISO C++ Standard Library Documentation: std::thread. +3. Sparse Matrix-Matrix Multiplication Algorithms. + +## Appendix (Optional) + +Основной фрагмент RunImpl(): + +```cpp +bool AshihminDMultMatrCrsSEQ::RunImpl() { + const auto &matrix_a = GetInput().first; + const auto &matrix_b = GetInput().second; + auto &matrix_c = GetOutput(); + + matrix_c.values.clear(); + matrix_c.col_index.clear(); + + for (int row_index = 0; row_index < matrix_a.rows; ++row_index) { + std::unordered_map accumulator; + + auto row_begin = static_cast(matrix_a.row_ptr[row_index]); + auto row_end = static_cast(matrix_a.row_ptr[row_index + 1]); + + for (std::size_t index_a = row_begin; index_a < row_end; ++index_a) { + int col_a = matrix_a.col_index[index_a]; + double value_a = matrix_a.values[index_a]; + + auto col_begin = static_cast(matrix_b.row_ptr[col_a]); + auto col_end = static_cast(matrix_b.row_ptr[col_a + 1]); + + for (std::size_t index_b = col_begin; index_b < col_end; ++index_b) { + int col_b = matrix_b.col_index[index_b]; + double value_b = matrix_b.values[index_b]; + + accumulator[col_b] += value_a * value_b; + } + } + + std::vector> sorted_values(accumulator.begin(), accumulator.end()); + + std::ranges::sort(sorted_values, {}, &std::pair::first); + + for (const auto &entry : sorted_values) { + if (entry.second != 0.0) { + matrix_c.values.push_back(entry.second); + matrix_c.col_index.push_back(entry.first); + } + } + + matrix_c.row_ptr[row_index + 1] = static_cast(matrix_c.values.size()); + } + + return true; +} diff --git a/tasks/ashihmin_d_mult_matr_crs/stl/report.md b/tasks/ashihmin_d_mult_matr_crs/stl/report.md new file mode 100644 index 0000000000..1584ba6404 --- /dev/null +++ b/tasks/ashihmin_d_mult_matr_crs/stl/report.md @@ -0,0 +1,113 @@ +# Умножение разреженных матриц в формате CRS - STL + +* Student: Ашихмин Д., group 3823Б1ФИ2 +* Technology: STL +* Variant: 4 + +## 1. Introduction + +STL-версия использует стандартные механизмы многопоточности C++, такие как +`std::async` и `std::future`. Эта реализация демонстрирует, как задачу умножения +разреженных матриц можно эффективно распараллелить, используя исключительно +средства стандартной библиотеки (ISO C++), что обеспечивает максимальную переносимость +кода без зависимости от OpenMP или TBB. + +## 2. Problem Statement + +Задача совпадает с SEQ-версией: реализовать умножение двух разреженных матриц A и B, +представленных в строковом формате (CRS). +Вход: InType = std::pair. +Выход: OutType = CRSMatrix, результат C = A * B. +Ограничения: matrix_a.cols == matrix_b.rows, элементы типа double. + +## 3. Baseline Algorithm (Sequential) + +Baseline описан в seq/report.md. Последовательный алгоритм выполняет обход строк матрицы A, +вычисляет ненулевые значения результирующей строки через вспомогательный аккумулятор и сохраняет +их в структуру CRS. + +## 4. Parallelization Scheme + +Для распределения нагрузки используется декомпозиция по строкам матрицы A. Весь диапазон +строк делится на блоки (chunks), количество которых соответствует числу аппаратных потоков: + +* **Определение потоков:** thread_count берется из `std::thread::hardware_concurrency()`. +* **Разбиение на блоки:** Каждому потоку назначается диапазон строк `[start_row, end_row)`. +* **Запуск задач:** Для каждого блока вызывается `std::async` с политикой `std::launch::async`, +что гарантирует выполнение в отдельном потоке. +* **Вычисления:** Внутри каждой задачи выполняется цикл по назначенным строкам. +Для каждой строки вызывается метод `MultiplyRow`, использующий локальный `std::map` для накопления значений. +* **Синхронизация:** Главный поток сохраняет объекты `std::future` в вектор и дожидается +завершения всех задач через вызов `.get()`. +* **Безопасность:** Гонок данных нет, так как каждый поток пишет результаты только в свои +индексы векторов `local_cols` и `local_vals`. + +## 5. Implementation Details + +* Файлы: stl/include/ops_stl.hpp, stl/src/ops_stl.cpp. +* Класс: AshihminDMultMatrCrsSTL. +* **MultiplyRow:** Логика вычисления одной строки вынесена в статический метод для снижения +когнитивной сложности и удовлетворения требований Clang-Tidy. +* **Память:** Дополнительная память используется для хранения промежуточных локальных векторов +каждой строки перед финальной сборкой. + +## 6. Experimental Setup + +Аппаратное обеспечение: + +* CPU: AMD Ryzen 5 3500X (3.60 GHz, 6 ядер / 6 потоков) +* RAM: 16 ГБ +* OS: Windows 11 / Linux (CI) + +Инструменты: + +* Сборка: CMake +* Компилятор: GCC / Clang / MSVC (стандарт C++20) + +## 7. Results and Discussion + +### 7.1 Correctness + +Корректность проверялась функциональными тестами из tests/functional/main.cpp. +Вычисленные значения сравниваются с результатами последовательной версии для различных +типов матриц (единичные, прямоугольные, случайные) с точностью 1e-10. + +### 7.2 Performance + +Mode | Count | Time, s | Speedup | Efficiency +--- | --- | --- | --- | --- +seq | 1 | 0.852412 | 1.00 | N/A +stl | 2 | 0.463267 | 1.84 | 92.00% +stl | 4 | 0.247794 | 3.44 | 86.00% +stl | 6 | 0.181245 | 4.70 | 78.33% + +## 8. Conclusions + +Реализация на базе STL (`std::async`) показала производительность, сопоставимую с +OpenMP. Использование chunks (блоков строк) вместо создания потока на каждую строку +позволило минимизировать накладные расходы на управление задачами. + +## 9. References + +1. ISO C++ Standard Library Documentation: std::thread, std::async. +2. Anthony Williams. C++ Concurrency in Action. +3. OpenMP Architecture Review Board. OpenMP API. +4. Microsoft MPI Documentation. + +## Appendix (Optional) + +Фрагмент RunImpl() с использованием std::async: + +```cpp +for (int thread_idx = 0; thread_idx < num_threads; ++thread_idx) { + int start_row = thread_idx * chunk_size; + int end_row = std::min(start_row + chunk_size, rows_a); + if (start_row >= end_row) break; + + futures.push_back(std::async(std::launch::async, [=, &matrix_a, &matrix_b, &local_cols, &local_vals] { + for (int i = start_row; i < end_row; ++i) { + MultiplyRow(i, matrix_a, matrix_b, local_cols[i], local_vals[i]); + } + })); +} +for (auto &fut : futures) fut.get(); diff --git a/tasks/ashihmin_d_mult_matr_crs/tbb/report.md b/tasks/ashihmin_d_mult_matr_crs/tbb/report.md new file mode 100644 index 0000000000..9b4c3690e5 --- /dev/null +++ b/tasks/ashihmin_d_mult_matr_crs/tbb/report.md @@ -0,0 +1,108 @@ +# Умножение разреженных матриц в формате CRS - TBB + +* Student: Ашихмин Д., group 3823Б1ФИ2 +* Technology: TBB +* Variant: 4 + +## 1. Introduction + +TBB-версия использует задачно-ориентированный подход Intel oneAPI Threading +Building Blocks. Вместо явного создания потоков, общий диапазон строк матрицы +передаётся планировщику TBB, который разбивает его на подзадачи и распределяет +работу между потоками, используя алгоритм кражи задач (work-stealing). + +## 2. Problem Statement + +Требуется реализовать умножение двух разреженных матриц A и B, представленных в +строковом формате CRS (Compressed Row Storage). +Вход: InType = std::pair. +Выход: OutType = CRSMatrix, произведение C = A * B. +Ограничения: matrix_a.cols == matrix_b.rows, элементы типа double. + +## 3. Baseline Algorithm (Sequential) + +Baseline описан в seq/report.md. Один поток последовательно обходит строки матрицы A, +накапливает ненулевые значения во временном словаре и формирует результирующие векторы values и col_index для каждой строки. + +## 4. Parallelization Scheme + +Используется функция tbb::parallel_for для распределения вычислений строк результирующей матрицы. + +* **Диапазон:** tbb::parallel_for принимает диапазон индексов от 0 до количества строк матрицы A. +* **Планировщик:** Используется стандартный механизм TBB. Это эффективно для разреженных +матриц, так как плотность строк может сильно различаться, а динамическая балансировка +нагрузки (work-stealing) позволяет избежать простоя ядер. +* **Локальность данных:** Каждый рабочий поток внутри лямбда-выражения создает свой +std::map row_accumulator. Это обеспечивает потокобезопасность без использования блокировок. +* **Автоматическая сортировка:** Использование std::map гарантирует, что индексы столбцов +внутри каждой строки будут отсортированы, что необходимо для корректности формата CRS. +* **Контроль конкуренции:** Количество используемых потоков ограничивается через +настройки тестового фреймворка PPC (переменная PPC_NUM_THREADS). + +## 5. Implementation Details + +* Файлы: tbb/include/ops_tbb.hpp, tbb/src/ops_tbb.cpp. +* Класс: AshihminDMultMatrCrsTBB. +* Результаты каждой итерации записываются в заранее подготовленные локальные векторы local_cols[i] и local_vals[i]. +* Гонок данных нет, так как каждый таск работает с уникальным индексом строки i и пишет в свою область памяти. +* Сборка финальной структуры (объединение локальных векторов в один плоский массив) +выполняется последовательно после завершения параллельного цикла. + +## 6. Experimental Setup + +Аппаратное обеспечение: + +* CPU: AMD Ryzen 5 3500X (3.60 GHz, 6 ядер) +* RAM: 16 ГБ +* OS: Windows 11 / Linux (CI) + +Инструменты: + +* Сборка: CMake +* Компилятор: GCC / Clang / MSVC +* Библиотека: Intel oneAPI TBB + +## 7. Results and Discussion + +7.1 Correctness +Корректность проверялась функциональными тестами. Вычисленные значения сравниваются с +результатами последовательной версии для различных конфигураций: единичные, диагональные и прямоугольные матрицы. + +7.2 Performance + +Mode | Count | Time, s | Speedup | Efficiency +--- | --- | --- | --- | --- +seq | 1 | 0.852412 | 1.00 | N/A +tbb | 2 | 0.441664 | 1.93 | 96.5% +tbb | 4 | 0.226705 | 3.76 | 94.0% +tbb | 6 | 0.165214 | 5.16 | 86.0% + +## 8. Conclusions + +Реализация на TBB показала высокую эффективность и отличную масштабируемость. +За счет механизма динамического распределения задач (work-stealing), версия TBB эффективно +справляется с неравномерной плотностью строк в разреженных матрицах. + +## 9. References + +1. oneAPI Threading Building Blocks Documentation. +2. OpenMP Architecture Review Board. OpenMP API. +3. Microsoft MPI Documentation. +4. ISO C++ Standard Library Documentation: std::thread. + +## Appendix (Optional) + +Основной фрагмент RunImpl(): + +```cpp +tbb::parallel_for(0, rows_a, [&](int i) { + std::map row_accumulator; + for (int j = matrix_a.row_ptr[i]; j < matrix_a.row_ptr[i + 1]; ++j) { + int col_a = matrix_a.col_index[j]; + double val_a = matrix_a.values[j]; + for (int k = matrix_b.row_ptr[col_a]; k < matrix_b.row_ptr[col_a + 1]; ++k) { + row_accumulator[matrix_b.col_index[k]] += val_a * matrix_b.values[k]; + } + } + // Filter and save... +}); diff --git a/tasks/ashihmin_d_mult_matr_crs/tests/functional/main.cpp b/tasks/ashihmin_d_mult_matr_crs/tests/functional/main.cpp index 60b3ba232f..9c9a52776e 100644 --- a/tasks/ashihmin_d_mult_matr_crs/tests/functional/main.cpp +++ b/tasks/ashihmin_d_mult_matr_crs/tests/functional/main.cpp @@ -7,6 +7,7 @@ #include #include +#include "ashihmin_d_mult_matr_crs/all/include/ops_all.hpp" #include "ashihmin_d_mult_matr_crs/common/include/common.hpp" #include "ashihmin_d_mult_matr_crs/omp/include/ops_omp.hpp" #include "ashihmin_d_mult_matr_crs/seq/include/ops_seq.hpp" @@ -115,7 +116,8 @@ const auto kTestTasksList = std::tuple_cat( ppc::util::AddFuncTask(kTestParams, PPC_SETTINGS_ashihmin_d_mult_matr_crs), ppc::util::AddFuncTask(kTestParams, PPC_SETTINGS_ashihmin_d_mult_matr_crs), ppc::util::AddFuncTask(kTestParams, PPC_SETTINGS_ashihmin_d_mult_matr_crs), - ppc::util::AddFuncTask(kTestParams, PPC_SETTINGS_ashihmin_d_mult_matr_crs)); + ppc::util::AddFuncTask(kTestParams, PPC_SETTINGS_ashihmin_d_mult_matr_crs), + ppc::util::AddFuncTask(kTestParams, PPC_SETTINGS_ashihmin_d_mult_matr_crs)); const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); const auto kPerfTestName = AshihminDMultMatrCrsFuncTests::PrintFuncTestName; diff --git a/tasks/ashihmin_d_mult_matr_crs/tests/performance/main.cpp b/tasks/ashihmin_d_mult_matr_crs/tests/performance/main.cpp index 3f7f318a60..96a1c07589 100644 --- a/tasks/ashihmin_d_mult_matr_crs/tests/performance/main.cpp +++ b/tasks/ashihmin_d_mult_matr_crs/tests/performance/main.cpp @@ -5,6 +5,7 @@ #include #include +#include "ashihmin_d_mult_matr_crs/all/include/ops_all.hpp" #include "ashihmin_d_mult_matr_crs/common/include/common.hpp" #include "ashihmin_d_mult_matr_crs/omp/include/ops_omp.hpp" #include "ashihmin_d_mult_matr_crs/seq/include/ops_seq.hpp" @@ -81,7 +82,8 @@ namespace { const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks(PPC_SETTINGS_ashihmin_d_mult_matr_crs); + AshihminDMultMatrCrsSTL, AshihminDMultMatrCrsALL>( + PPC_SETTINGS_ashihmin_d_mult_matr_crs); const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks);