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); diff --git a/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/all/report.md b/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/all/report.md new file mode 100644 index 0000000000..4de546c5ee --- /dev/null +++ b/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/all/report.md @@ -0,0 +1,214 @@ +# Умножение разреженных матриц. Элементы комплексного типа. Формат хранения матрицы – столбцовый (CCS) - ALL + +- Student: Горячева Ксения Александровна +- Technology: ALL +- Variant: 7 + +## 1. Контекст + +Гибридная схема MPI + OpenMP +объединяет: + +- распределённую память (MPI); +- общую память (OpenMP). + +Это позволяет использовать +как межпроцессный, +так и внутрипроцессный параллелизм. + +## 2. Постановка задачи + +Полная постановка задачи приведена в отчёте для SEQ-версии [seq/report.md](../seq/report.md). +Кратко: + +Входные данные: +Две разреженные комплексные матрицы: + +- A — комплексная матрица размера m × n +- B — комплексная матрица размера n × k +представленные в формате CCS: + +Выходные данные: +Матрица: +C = A * B +в формате CCS. + +OpenMP-реализация должна давать **идентичный** SEQ результат при любом количестве потоков. + +## 3. Базовый алгоритм + +Последовательная версия (SEQ): + +1. Проверить корректность входных данных. +2. Инициализировать структуру результата. +3. Для каждого столбца матрицы B: + +- пройти по ненулевым элементам; +- выполнить умножение элементов; +- накопить значения в accumulator. + +4. Отсортировать индексы строк. +5. Сохранить ненулевые значения в матрицу результата. + +## 4. Схема распараллеливания + +В гибридной версии используется двухуровневое распараллеливание: + +- MPI — распределение столбцов между процессами; +- OpenMP — параллельная обработка столбцов внутри процесса. + +Алгоритм включает: + +1. Разделение столбцов между MPI-процессами; +2. Параллельную обработку столбцов внутри процесса; +3. Сбор результатов через MPI_Gatherv; +4. Формирование глобальной CCS-матрицы. + +MPI +Столбцы матрицы B распределяются между процессами: +int cols_per_proc = + (b.cols + size - 1) / size; +Каждый процесс обрабатывает собственный диапазон столбцов. + +OpenMP +Внутри процесса используется: + +```cpp +#pragma omp parallel for +``` + +для обработки локальных столбцов. + +Сборка результата +После вычислений: +локальные данные собираются через MPI_Gatherv; +формируется глобальный массив col_ptr; +результат рассылается всем процессам через MPI_Bcast. + +Схема: + MPI + ┌──────────┼──────────┐ + Proc0 Proc1 Proc2 + │ │ │ + OpenMP OpenMP OpenMP + threads threads threads + │ │ │ + local CCS local CCS local CCS + └──────────┼──────────┘ + MPI_Gatherv + +## 5. Детали реализации + +- `all/include/ops_all.hpp` — объявление класса `GoriachevaKMultSparseComplexMatrixCcsSALL`. +- `all/src/ops_all.cpp` — реализация методов. + +RunImpl +Основные этапы: + +- определение ранга процесса; +- распределение столбцов; +- вычисление локальных результатов; +- сбор данных на процессе 0; +- рассылка итоговой матрицы. +Для внутреннего распараллеливания используется OpenMP. + +Коммуникации MPI +Используются: +MPI_Gather +MPI_Gatherv +MPI_Bcast +для сборки результирующей CCS-матрицы. + +## 6. Проверка корректности + +Гибридная версия проходит все функциональные тесты, +используемые для последовательной версии. + +### Способ проверки + +Корректность реализации проверялась с помощью функциональных тестов Google Test. + +Проверялись: +размеры результирующей матрицы; +массив col_ptr; +массив row_ind; +значения комплексных элементов с точностью 1e-9. + +Сравнение результатов реализовано функцией: +CompareMatrices(...) + +### Функциональные тесты + +Используется 14 тестов. + +| № теста | Описание | Проверяемый случай | +| ------- | -------------------------------- | ----------------------------------- | +| 0 | Матрицы 1×1 | Базовое умножение комплексных чисел | +| 1 | Диагональные 2×2 | Корректность умножения | +| 2 | Нулевой результат | Отсутствие ненулевых элементов | +| 3 | Мнимые числа | Работа с чисто мнимыми значениями | +| 4 | Комплексные коэффициенты | Корректность комплексной арифметики | +| 5 | Прямоугольные матрицы | Умножение матриц разных размеров | +| 6 | Разреженные 4×4 | Работа CCS-структуры | +| 7 | Один ненулевой столбец | Частично заполненная матрица | +| 8 | Крайние индексы | Проверка граничных строк | +| 9 | Несколько потоковых блоков | Корректность распределения | +| 10 | Полностью заполненная диагональ | Проверка накопления | +| 11 | Центральный элемент | Корректность индексации | +| 12 | Разреженные комплексные значения | Проверка знаков и мнимой части | +| 13 | Несортированные элементы столбца | Проверка сортировки строк | + +Все функциональные тесты успешно пройдены +Результаты совпадают с SEQ +с точностью 1e-9. + +## 7. Экспериментальная среда + +CPU: Intel(R) Core(TM) i5-9300H CPU @ 2.40GHz +OS: Windows 10 +Compiler: MinGW-w64 GCC, C++17 +Build type: Release +CMake: 4.0.1 +RAM: 32 GB + +### Сборка + +cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release +cmake --build build --config Release --parallel + +### Запуск тестов + +cd build/bin +mpiexec -n 4 ./ppc_func_tests --gtest_filter="*GoriachevaKViolationOrderElemVecFuncTests*" +mpiexec -n 4 ./ppc_perf_tests --gtest_filter="*GoriachevaKMultSparseComplexMatrixCcsPerfTest*" + +## 8. Результаты + +| Режим | Workers (p) | Time (сек) | Speedup | Efficiency | +| :---- | :---------: | :--------: | :-----: | :--------: | +| SEQ | 1 | 0.000166 | 1.00 | 1.00 | +| ALL | 2 | 0.000399 | 0.42 | 0.21 | +| ALL | 4 | 0.005986 | 0.03 | 0.01 | +| ALL | 8 | 0.026888 | 0.01 | 0.00 | +| ALL | 16 | 0.103833 | 0.00 | 0.00 | + +- Speedup = `T_seq / T_par` +- Efficiency = `Speedup / threads` +- База для `T_seq` — замер SEQ-версии + +## 9. Выводы + +В ходе работы была реализована гибридная версия алгоритма с использованием MPI и OpenMP. +Гибридная модель MPI + OpenMP позволяет эффективно выполнять умножение разреженных матриц на +многопроцессорных системах. +Преимущества: + +- масштабируемость; +- возможность работы на кластерах; +- эффективное использование вычислительных ресурсов. +Недостатки: +- сложность реализации; +- необходимость синхронизации процессов; +- накладные расходы на передачу данных. +Несмотря на увеличение накладных расходов, связанных с MPI-коммуникациями и синхронизацией процессов, +гибридная реализация обеспечивает возможность масштабирования вычислений на распределённые системы. diff --git a/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/omp/report.md b/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/omp/report.md new file mode 100644 index 0000000000..aa1d0ac008 --- /dev/null +++ b/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/omp/report.md @@ -0,0 +1,209 @@ +# Умножение разреженных матриц. Элементы комплексного типа. Формат хранения матрицы – столбцовый (CCS) - OMP + +- Student: Горячева Ксения Александровна +- Technology: OMP +- Variant: 7 + +## 1. Контекст + +OpenMP позволяет эффективно распараллеливать +циклы с независимыми итерациями +в системах с общей памятью. + +В задаче умножения CCS-матриц +обработка столбцов результирующей матрицы независима, +что делает алгоритм хорошо подходящим +для распараллеливания через OpenMP. + +## 2. Постановка задачи + +Полная постановка задачи приведена в отчёте для SEQ-версии [seq/report.md](../seq/report.md). +Кратко: + +Входные данные: +Две разреженные комплексные матрицы: + +- A — комплексная матрица размера m × n +- B — комплексная матрица размера n × k + представленные в формате CCS: + +Выходные данные: +Матрица: +C = A \* B +в формате CCS. + +OpenMP-реализация должна давать **идентичный** SEQ результат при любом количестве потоков. + +## 3. Базовый алгоритм + +Последовательная версия (SEQ): + +1. Проверить корректность входных данных. +2. Инициализировать структуру результата. +3. Для каждого столбца матрицы B: + +- пройти по ненулевым элементам; +- выполнить умножение элементов; +- накопить значения в accumulator. + +4. Отсортировать индексы строк. +5. Сохранить ненулевые значения в матрицу результата. + +## 4. Схема распараллеливания + +Параллелизация выполняется по столбцам результирующей матрицы. + +Используется директива: + +```cpp +#pragma omp parallel for default(none) shared(a, b, local_values, local_rows) +``` + +Каждый поток независимо обрабатывает собственный столбец: +ProcessColumn(j, a, b, local_values[j], local_rows[j]); + +Для исключения гонок данных: + +каждый поток записывает результат только в собственные контейнеры: + +- local_values[j] +- local_rows[j] +- объединение результатов выполняется после завершения параллельной области. + +Shared-переменные: + +- a; +- b; +- local_values; +- local_rows. + +Private-переменные: + +- j; +- локальные accumulator; +- marker; +- used_rows. + +Синхронизация: + +- явные critical sections отсутствуют; +- используется неявный барьер после parallel for. + +Схема распараллеливания: + Matrix B columns + ┌─────┬─────┬─────┬─────┐ + │ j0 │ j1 │ j2 │ ... │ + └─────┴─────┴─────┴─────┘ + ↓ ↓ ↓ + Thread0 Thread1 Thread2 + ↓ ↓ ↓ + local_values/local_rows + +## 5. Детали реализации + +- `omp/include/ops_omp.hpp` — объявление класса `GoriachevaKMultSparseComplexMatrixCcsOMP`. +- `omp/src/ops_omp.cpp` — реализация методов. + +### Особенности + +Для каждого столбца используются локальные буферы: +`std::vector> local_values(c.cols);` +`std::vector> local_rows(c.cols);` +Это устраняет гонки данных. + +### Схема памяти + +- a, b — shared; +- local_values[j] — private для столбца; +- временные аккумуляторы — private. + +## 6. Проверка корректности + +OpenMP-реализация проходит все функциональные тесты, +используемые для последовательной версии. + +### Способ проверки + +Корректность реализации проверялась с помощью функциональных тестов Google Test. + +Проверялись: +размеры результирующей матрицы; +массив col_ptr; +массив row_ind; +значения комплексных элементов с точностью 1e-9. + +Сравнение результатов реализовано функцией: +CompareMatrices(...) + +### Функциональные тесты + +Используется 14 тестов. + +| № теста | Описание | Проверяемый случай | +| ------- | -------------------------------- | ----------------------------------- | +| 0 | Матрицы 1×1 | Базовое умножение комплексных чисел | +| 1 | Диагональные 2×2 | Корректность умножения | +| 2 | Нулевой результат | Отсутствие ненулевых элементов | +| 3 | Мнимые числа | Работа с чисто мнимыми значениями | +| 4 | Комплексные коэффициенты | Корректность комплексной арифметики | +| 5 | Прямоугольные матрицы | Умножение матриц разных размеров | +| 6 | Разреженные 4×4 | Работа CCS-структуры | +| 7 | Один ненулевой столбец | Частично заполненная матрица | +| 8 | Крайние индексы | Проверка граничных строк | +| 9 | Несколько потоковых блоков | Корректность распределения | +| 10 | Полностью заполненная диагональ | Проверка накопления | +| 11 | Центральный элемент | Корректность индексации | +| 12 | Разреженные комплексные значения | Проверка знаков и мнимой части | +| 13 | Несортированные элементы столбца | Проверка сортировки строк | + +Все функциональные тесты успешно пройдены +Результаты совпадают с SEQ +с точностью 1e-9. + +## 7. Экспериментальная среда + +CPU: Intel(R) Core(TM) i5-9300H CPU @ 2.40GHz +OS: Windows 10 +Compiler: MinGW-w64 GCC, C++17 +Backend: OpenMP +PPC_NUM_THREADS: 4 +OMP_NUM_THREADS: 4 +Build type: Release +CMake: 4.0.1 +RAM: 32 GB + +### Сборка + +cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release +cmake --build build --config Release --parallel + +### Запуск тестов + +cd build/bin +mpiexec -n 4 ./ppc*func_tests --gtest_filter="\_GoriachevaKViolationOrderElemVecFuncTests*" +mpiexec -n 4 ./ppc*perf_tests --gtest_filter="\_GoriachevaKMultSparseComplexMatrixCcsPerfTest*" + +## 8. Результаты + +| Режим | Workers (p) | Time (сек) | Speedup | Efficiency | +| :---- | :---------: | :--------: | :-----: | :--------: | +| SEQ | 1 | 0.000166 | 1.00 | 1.00 | +| OMP | 2 | 0.000502 | 0.33 | 0.17 | +| OMP | 4 | 0.001694 | 0.10 | 0.02 | +| OMP | 8 | 0.001913 | 0.09 | 0.01 | + +- Speedup = `T_seq / T_par` +- Efficiency = `Speedup / threads` +- База для `T_seq` — замер SEQ-версии + +## 9. Выводы + +В ходе работы была разработана параллельная версия алгоритма с использованием OpenMP. +OpenMP позволяет быстро распараллелить вычисления за счёт директив компилятора и минимального +объёма изменений в последовательном коде. В данной реализации параллельная обработка выполняется +по столбцам результирующей матрицы, что обеспечивает независимость вычислений между потоками. +Преимуществами OpenMP являются простота интеграции, хорошая масштабируемость на многоядерных +процессорах и низкие накладные расходы при работе с вычислительно интенсивными задачами. +Недостатком подхода является ограничение одной вычислительной машиной с общей памятью. Кроме +того, при небольших размерах матриц эффект от распараллеливания может быть незначительным из-за +накладных расходов на создание и синхронизацию потоков. diff --git a/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/report.md b/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/report.md new file mode 100644 index 0000000000..6f73a5fe50 --- /dev/null +++ b/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/report.md @@ -0,0 +1,247 @@ +# Умножение разреженных матриц. Элементы комплексного типа. Формат хранения матрицы – столбцовый (CCS) + +- Student: Горячева Ксения Александровна, group: 3823Б1ФИ2 +- Variant: 7 +- Local reports: [seq/report.md](seq/report.md) · [omp/report.md](omp/report.md) · +[tbb/report.md](tbb/report.md) · [stl/report.md](stl/report.md) · [all/report.md](all/report.md) + +## 1. Введение + +Разреженные матрицы широко применяются в задачах вычислительной математики, +численного моделирования, обработки графов, +решении систем линейных уравнений, +машинном обучении и инженерных расчётах. + +Во многих практических задачах большая часть элементов матрицы равна нулю. +Использование плотного хранения в таких случаях приводит +к значительным затратам памяти и снижению производительности вычислений. + +Для эффективного хранения разреженных матриц используются специализированные форматы, +одним из которых является CCS (Compressed Column Storage). + +Формат CCS позволяет хранить только ненулевые элементы матрицы, +что существенно уменьшает объём памяти +и ускоряет выполнение операций линейной алгебры. + +Цель данной работы — +реализация алгоритма умножения разреженных комплексных матриц, +представленных в формате CCS, +а также исследование эффективности различных технологий параллельного программирования. + +В работе реализованы следующие версии алгоритма: + +- последовательная (SEQ); +- OpenMP; +- oneTBB; +- STL threads; +- гибридная MPI + OpenMP. + +## 2. Единая постановка задачи + +### Входные данные + +Две разреженные комплексные матрицы: + +- A — комплексная матрица размера m × n +- B — комплексная матрица размера n × k +представленные в формате CCS: + +```cpp +struct SparseMatrixCCS { + int rows; + int cols; + + std::vector values; + std::vector row_ind; + std::vector col_ptr; +}; +``` + +где: +values — ненулевые элементы; +row_ind — индексы строк элементов; +col_ptr — указатели начала столбцов. + +### Выходные данные + +Матрица: +C = A * B +в формате CCS. + +### Ограничения + +- число столбцов матрицы A должно совпадать с числом строк матрицы B; +- массивы col_ptr не должны быть пустыми. + +### Крайние случаи + +| Случай | Ожидаемое поведение | +| ---------------------------------------- | -------------------------------------- | +| Несовместимые размеры | ValidationImpl() возвращает false | +| Пустые col_ptr | ValidationImpl() возвращает false | +| Полностью нулевая результирующая матрица | values и row_ind пустые | +| Матрица 1×1 | Корректное умножение комплексных чисел | + +## 3. Единая методика эксперимента + +CPU: Intel(R) Core(TM) i5-9300H CPU @ 2.40GHz +OS: Windows 10 +Compiler: MinGW-w64 GCC, C++17 +Build type: Release +CMake: 4.0.1 +RAM: 32 GB + +Использовались переменные окружения: +export PPC_NUM_THREADS=4 +export PPC_NUM_PROC=2 + +Использовались случайно сгенерированные разреженные матрицы. +Параметры: +Размер матрицы: 200 × 200 +Ненулевых элементов на столбец: 5 +Тип данных: complex + +Метрики + +- Speedup = `T_seq / T_par` +- Efficiency = `Speedup / total_workers` (total_workers = число потоков × число процессов для MPI) +- База для `T_seq` — замер SEQ-версии + +## 4. Сводка корректности + +Корректность проверялась при помощи общего набора функциональных тестов. +Тесты покрывают: + +- минимальные размеры матриц; +- квадратные матрицы; +- прямоугольные матрицы; +- пустые столбцы; +- полностью нулевой результат; +- работу с комплексными числами; +- совпадение CCS-структур. +Сравнение результатов выполняется функцией: +CompareMatrices() +Проверяются: +- размеры матриц; +- col_ptr; +- row_ind; +- значения с точностью eps = 1e-9. +Всего реализовано 14 сценариев. +Каждый сценарий запускается для: +- SEQ; +- OMP; +- TBB; +- STL; +- ALL. +Общее количество тестов: +70 tests +Все тесты были успешно пройдены. + +## 5. Агрегированные результаты + +| Технология | Конфигурация | Workers | Time (с) | Speedup | Efficiency | +| ---------- | ------------ | ------- | -------- | ------- | ---------- | +| SEQ | 1 процесс | 1 | 0.000166 | 1.00 | 1.00 | +| OpenMP | 2 потока | 2 | 0.000502 | 0.33 | 0.17 | +| | 4 потока | 4 | 0.001694 | 0.10 | 0.02 | +| | 8 потоков | 8 | 0.001913 | 0.09 | 0.01 | +| | 16 потоков | 16 | 0.008535 | 0.02 | 0.00 | +| STL | 2 потока | 2 | 0.001527 | 0.11 | 0.05 | +| | 4 потока | 4 | 0.002967 | 0.06 | 0.01 | +| | 8 потоков | 8 | 0.004881 | 0.03 | 0.00 | +| | 16 потоков | 16 | 0.008424 | 0.02 | 0.00 | +| TBB | 2 потока | 2 | 0.001216 | 0.14 | 0.07 | +| | 4 потока | 4 | 0.001273 | 0.13 | 0.03 | +| | 8 потоков | 8 | 0.001195 | 0.14 | 0.02 | +| | 16 потоков | 16 | 0.001162 | 0.14 | 0.01 | +| ALL | 1x2 | 2 | 0.000399 | 0.42 | 0.21 | +| (MPI+OMP) | 1x4 | 4 | 0.005986 | 0.03 | 0.01 | +| | 1x8 | 8 | 0.026888 | 0.01 | 0.00 | +| | 1x16 | 16 | 0.103833 | 0.00 | 0.00 | + +## 6. Интерпретация различий + +SEQ +Последовательная версия используется как baseline. +Преимущества: + +- простота; +- предсказуемость; +- отсутствие накладных расходов на синхронизацию. +Недостатки: +- отсутствие масштабируемости. + +OMP +OpenMP версия показала хорошую производительность при относительно простой реализации. +Преимущества: + +- минимальные изменения кода; +- удобная модель shared memory; +- хорошая масштабируемость. +Недостатки: +- overhead создания parallel region; +- чувствительность к распределению нагрузки. + +TBB +TBB обеспечивает автоматическое управление потоками. +Преимущества: + +- балансировка нагрузки; +- work-stealing; +- удобный runtime. +Недостатки: +- дополнительные накладные расходы runtime. + +STL +Реализация на std::thread требует ручного управления потоками. +Преимущества: + +- полный контроль; +- стандартные средства языка. +Недостатки: +- необходимость ручного разбиения задач; +- более сложная синхронизация; +- накладные расходы на создание потоков. + +ALL +Гибридная реализация сочетает MPI и OpenMP. +Преимущества: + +- возможность работы на распределённых системах; +- использование межпроцессного и внутрипроцессного параллелизма. +Недостатки: +- стоимость MPI-коммуникаций; +- сложность синхронизации. + +## 7. Репродуцируемость + +### Сборка + +cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release +cmake --build build --config Release --parallel + +### Запуск тестов + +cd build/bin +mpiexec -n 4 ./ppc_func_tests --gtest_filter="*GoriachevaKViolationOrderElemVecFuncTests*" +mpiexec -n 4 ./ppc_perf_tests --gtest_filter="*GoriachevaKMultSparseComplexMatrixCcsPerfTest*" + +## 8. Заключение + +В ходе работы был реализован и исследован алгоритм умножения разреженных комплексных матриц в +формате CCS. +Были разработаны пять реализаций: + +- последовательная; +- OpenMP; +- TBB; +- STL Threads; +- гибридная MPI + OpenMP. +Все реализации успешно прошли функциональные тесты. +Параллельные версии продемонстрировали корректную работу и возможность использования +многопоточных вычислений. +На данной задаче наибольший практический интерес представляют OpenMP и гибридная реализация. + +## 9. Источники + + [Документация курса «Параллельное программирование»](https://learning-process.github.io/parallel_programming_course/ru/) diff --git a/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/seq/report.md b/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/seq/report.md new file mode 100644 index 0000000000..87b43cba74 --- /dev/null +++ b/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/seq/report.md @@ -0,0 +1,223 @@ +# Умножение разреженных матриц. Элементы комплексного типа. Формат хранения матрицы – столбцовый (CCS) - SEQ + +- Student: Горячева Ксения Александровна +- Technology: SEQ +- Variant: 7 + +## 1. Контекст + +Разреженные матрицы широко используются в научных вычислениях, +численных методах, моделировании физических процессов, +графовых алгоритмах и задачах линейной алгебры. + +При хранении матриц с большим количеством нулевых элементов +использование плотного формата приводит к избыточному расходу памяти +и увеличению времени вычислений. + +Для уменьшения затрат памяти используется формат CCS +(Compressed Column Storage), +в котором сохраняются только ненулевые элементы матрицы. + +В данной работе реализовано последовательное умножение +разреженных комплексных матриц, +представленных в формате CCS. + +Последовательная версия используется как эталон корректности +и база для сравнения производительности параллельных реализаций. + +## 2. Постановка задачи + +### Входные данные + +Две разреженные комплексные матрицы: + +- A — комплексная матрица размера m × n +- B — комплексная матрица размера n × k + представленные в формате CCS: + +```cpp +struct SparseMatrixCCS { +int rows; +int cols; + +std::vector values; +std::vector row_ind; +std::vector col_ptr; +}; +``` + +где: +values — ненулевые элементы; +row_ind — индексы строк элементов; +col_ptr — указатели начала столбцов. + +### Выходные данные + +Матрица: +C = A \* B +в формате CCS. + +### Ограничения + +- число столбцов матрицы A должно совпадать с числом строк матрицы B; +- массивы col_ptr не должны быть пустыми. + +### Крайние случаи + +| Случай | Ожидаемое поведение | +| ---------------------------------------- | -------------------------------------- | +| Несовместимые размеры | ValidationImpl() возвращает false | +| Пустые col_ptr | ValidationImpl() возвращает false | +| Полностью нулевая результирующая матрица | values и row_ind пустые | +| Матрица 1×1 | Корректное умножение комплексных чисел | + +## 3. Базовый алгоритм + +### Идея алгоритма + +Умножение выполняется по столбцам матрицы B. +Для каждого столбца: + +1. Выбираются ненулевые элементы текущего столбца B; +2. Для каждого элемента находится соответствующий столбец матрицы A; +3. Производится накопление результата через временный аккумулятор; +4. Ненулевые значения сохраняются в CCS-структуру результата. + +### Пошаговое описание + +1. Проверить корректность входных данных. +2. Инициализировать структуру результата. +3. Для каждого столбца матрицы B: + +- пройти по ненулевым элементам; +- выполнить умножение элементов; +- накопить значения в accumulator. + +4. Отсортировать индексы строк. +5. Сохранить ненулевые значения в матрицу результата. + +### Асимптотика + +Пусть: + +- nnz(A) — число ненулевых элементов A; +- nnz(B) — число ненулевых элементов B. + +Тогда: + +- Время: + O(nnz(A) \* nnz(B)) +- Память: + O(rows) + для временного аккумулятора. + +### Инварианты корректности + +- структура CCS сохраняется; +- индексы строк внутри столбца отсортированы; +- нулевые элементы не сохраняются; +- размеры результирующей матрицы корректны. + +## 4. Детали реализации + +- `seq/include/ops_seq.hpp` — объявление класса `GoriachevaKMultSparseComplexMatrixCcsSEQ`. +- `seq/src/ops_seq.cpp` — реализация методов. + +| Метод | Назначение | +| ---------------------- | --------------------------------------------------------- | +| `ValidationImpl()` | Проверяет совместимость размеров и корректность структуры | +| `PreProcessingImpl()` | Инициализирует выходную матрицу | +| `RunImpl()` | Выполняет последовательное умножение | +| `PostProcessingImpl()` | Завершает обработку | + +### Особенности реализации + +Для накопления значений используются: +`std::vector accumulator(a.rows);` +`std::vector marker(a.rows, -1);` +marker позволяет избежать повторной инициализации элементов аккумулятора. + +## 5. Проверка корректности + +### Способ проверки + +Корректность реализации проверялась с помощью функциональных тестов Google Test. + +Проверялись: +размеры результирующей матрицы; +массив col_ptr; +массив row_ind; +значения комплексных элементов с точностью 1e-9. + +Сравнение результатов реализовано функцией: +CompareMatrices(...) + +### Функциональные тесты + +Используется 14 тестов. + +| № теста | Описание | Проверяемый случай | +| ------- | -------------------------------- | ----------------------------------- | +| 0 | Матрицы 1×1 | Базовое умножение комплексных чисел | +| 1 | Диагональные 2×2 | Корректность умножения | +| 2 | Нулевой результат | Отсутствие ненулевых элементов | +| 3 | Мнимые числа | Работа с чисто мнимыми значениями | +| 4 | Комплексные коэффициенты | Корректность комплексной арифметики | +| 5 | Прямоугольные матрицы | Умножение матриц разных размеров | +| 6 | Разреженные 4×4 | Работа CCS-структуры | +| 7 | Один ненулевой столбец | Частично заполненная матрица | +| 8 | Крайние индексы | Проверка граничных строк | +| 9 | Несколько потоковых блоков | Корректность распределения | +| 10 | Полностью заполненная диагональ | Проверка накопления | +| 11 | Центральный элемент | Корректность индексации | +| 12 | Разреженные комплексные значения | Проверка знаков и мнимой части | +| 13 | Несортированные элементы столбца | Проверка сортировки строк | + +Все функциональные тесты успешно пройдены + +## 6. Экспериментальная среда + +CPU: Intel(R) Core(TM) i5-9300H CPU @ 2.40GHz +OS: Windows 10 +Compiler: MinGW-w64 GCC, C++17 +Build type: Release +CMake: 4.0.1 +RAM: 32 GB + +### Сборка + +cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release +cmake --build build --config Release --parallel + +### Запуск тестов + +cd build/bin +mpiexec -n 4 ./ppc*func_tests --gtest_filter="\_GoriachevaKViolationOrderElemVecFuncTests*" +mpiexec -n 4 ./ppc*perf_tests --gtest_filter="\_GoriachevaKMultSparseComplexMatrixCcsPerfTest*" + +## 7. Результаты + +В таблице представлены результаты замеров времени выполнения последовательной версии алгоритма + +| Режим | Workers (p) | Время (сек) | Speedup | Efficiency | +| :---------------------------: | :---------: | :---------: | :-----: | :--------: | +| Последовательная версия (SEQ) | 1 | 0.000166 | 1.0 | 1.0 | + +Наиболее затратным участком является двойной проход по ненулевым элементам столбцов. + +## 8. Выводы + +В ходе работы была реализована последовательная версия алгоритма +умножения разреженных комплексных матриц в формате CCS. +Реализация корректно выполняет обработку ненулевых элементов, +формирует результирующую матрицу в столбцовом формате хранения и +поддерживает операции над комплексными числами. + +Проведённое тестирование подтвердило корректность работы алгоритма для различных типов входных данных: + +- квадратных и прямоугольных матриц; +- полностью и частично разреженных структур; +- комплексных значений; +- случаев с нулевым результатом. + +Последовательная версия используется как базовая реализация для сравнения эффективности параллельных алгоритмов. diff --git a/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/stl/report.md b/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/stl/report.md new file mode 100644 index 0000000000..b53e016ef5 --- /dev/null +++ b/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/stl/report.md @@ -0,0 +1,188 @@ +# Умножение разреженных матриц. Элементы комплексного типа. Формат хранения матрицы – столбцовый (CCS) - STL + +- Student: Горячева Ксения Александровна +- Technology: STL +- Variant: 7 + +## 1. Контекст + +Стандартная библиотека C++ предоставляет механизм +многопоточности через `std::thread`. + +В данной реализации +распараллеливание выполняется вручную. + +## 2. Постановка задачи + +Полная постановка задачи приведена в отчёте для SEQ-версии [seq/report.md](../seq/report.md). +Кратко: + +Входные данные: +Две разреженные комплексные матрицы: + +- A — комплексная матрица размера m × n +- B — комплексная матрица размера n × k +представленные в формате CCS: + +Выходные данные: +Матрица: +C = A * B +в формате CCS. + +OpenMP-реализация должна давать **идентичный** SEQ результат при любом количестве потоков. + +## 3. Базовый алгоритм + +Последовательная версия (SEQ): + +1. Проверить корректность входных данных. +2. Инициализировать структуру результата. +3. Для каждого столбца матрицы B: + +- пройти по ненулевым элементам; +- выполнить умножение элементов; +- накопить значения в accumulator. + +4. Отсортировать индексы строк. +5. Сохранить ненулевые значения в матрицу результата. + +## 4. Схема распараллеливания + +В STL-версии распараллеливание выполнено с использованием std::thread. Все столбцы результирующей +матрицы делятся между потоками блоками: +int cols_per_thread = (b.cols + num_threads - 1) / num_threads; +Каждый поток: + +1. Получает диапазон столбцов; +2. Выполняет вычисление соответствующих столбцов результата; +3. Сохраняет локальные значения в отдельные контейнеры. +После завершения вычислений выполняется: + +1) ожидание потоков (join); +2) последовательная сборка результирующей CCS-матрицы. + +## 5. Детали реализации + +- `stl/include/ops_stl.hpp` — объявление класса `GoriachevaKMultSparseComplexMatrixCcsSTL`. +- `stl/src/ops_stl.cpp` — реализация методов. + +ValidationImpl +Проверяется: + +- совместимость размеров матриц; +- корректность массивов col_ptr. +PreProcessingImpl +Выполняется очистка выходной структуры. +RunImpl +Основной вычислительный этап: +- определяется число потоков; +- столбцы распределяются между потоками; +- каждый поток вызывает ProcessColumn; + +### Распределение нагрузки + +Используется блочное разбиение: + +cols_per_thread = + (b.cols + num_threads - 1) / num_threads; + +### Синхронизация + +Во время вычислений синхронизация не требуется, +так как потоки работают с независимыми столбцами. + +После завершения выполняется: + +th.join(); + +## 6. Проверка корректности + +STL-реализация проходит все функциональные тесты, +используемые для последовательной версии. + +### Способ проверки + +Корректность реализации проверялась с помощью функциональных тестов Google Test. + +Проверялись: +размеры результирующей матрицы; +массив col_ptr; +массив row_ind; +значения комплексных элементов с точностью 1e-9. + +Сравнение результатов реализовано функцией: +CompareMatrices(...) + +### Функциональные тесты + +Используется 14 тестов. + +| № теста | Описание | Проверяемый случай | +| ------- | -------------------------------- | ----------------------------------- | +| 0 | Матрицы 1×1 | Базовое умножение комплексных чисел | +| 1 | Диагональные 2×2 | Корректность умножения | +| 2 | Нулевой результат | Отсутствие ненулевых элементов | +| 3 | Мнимые числа | Работа с чисто мнимыми значениями | +| 4 | Комплексные коэффициенты | Корректность комплексной арифметики | +| 5 | Прямоугольные матрицы | Умножение матриц разных размеров | +| 6 | Разреженные 4×4 | Работа CCS-структуры | +| 7 | Один ненулевой столбец | Частично заполненная матрица | +| 8 | Крайние индексы | Проверка граничных строк | +| 9 | Несколько потоковых блоков | Корректность распределения | +| 10 | Полностью заполненная диагональ | Проверка накопления | +| 11 | Центральный элемент | Корректность индексации | +| 12 | Разреженные комплексные значения | Проверка знаков и мнимой части | +| 13 | Несортированные элементы столбца | Проверка сортировки строк | + +Все функциональные тесты успешно пройдены +Результаты совпадают с SEQ +с точностью 1e-9. + +## 7. Экспериментальная среда + +CPU: Intel(R) Core(TM) i5-9300H CPU @ 2.40GHz +OS: Windows 10 +Compiler: MinGW-w64 GCC, C++17 +Build type: Release +CMake: 4.0.1 +RAM: 32 GB + +### Сборка + +cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release +cmake --build build --config Release --parallel + +### Запуск тестов + +cd build/bin +mpiexec -n 4 ./ppc_func_tests --gtest_filter="*GoriachevaKViolationOrderElemVecFuncTests*" +mpiexec -n 4 ./ppc_perf_tests --gtest_filter="*GoriachevaKMultSparseComplexMatrixCcsPerfTest*" + +## 8. Результаты + +| Режим | Workers (p) | Time (сек) | Speedup | Efficiency | +| :---- | :---------: | :--------: | :-----: | :--------: | +| SEQ | 1 | 0.000166 | 1.00 | 1.00 | +| STL | 2 | 0.001527 | 0.11 | 0.05 | +| STL | 4 | 0.002967 | 0.06 | 0.01 | +| STL | 8 | 0.004881 | 0.03 | 0.00 | +| STL | 16 | 0.008424 | 0.02 | 0.00 | + +- Speedup = `T_seq / T_par` +- Efficiency = `Speedup / threads` +- База для `T_seq` — замер SEQ-версии + +## 9. Выводы + +В ходе работы была разработана многопоточная версия алгоритма с использованием стандартных средств C++ (std::thread). +Использование STL-потоков позволяет реализовать переносимое распараллеливание без привязки к +конкретной библиотеке параллельного программирования. +Преимуществами реализации являются: + +- гибкость; +- контроль над потоками; +- независимость от OpenMP и TBB. +Недостатками являются: +- необходимость ручного управления потоками; +- более сложная синхронизация; +- большие накладные расходы по сравнению с OpenMP. diff --git a/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/tbb/report.md b/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/tbb/report.md new file mode 100644 index 0000000000..3e8a038fc6 --- /dev/null +++ b/tasks/goriacheva_k_mult_sparse_complex_matrix_ccs/tbb/report.md @@ -0,0 +1,190 @@ +# Умножение разреженных матриц. Элементы комплексного типа. Формат хранения матрицы – столбцовый (CCS) - TBB + +- Student: Горячева Ксения Александровна +- Technology: TBB +- Variant: 7 + +## 1. Контекст + +oneTBB предоставляет высокоуровневую модель +параллельного программирования, +основанную на задачах. + +Для задачи умножения CCS-матриц +используется `parallel_for`, +который автоматически распределяет нагрузку между потоками. + +## 2. Постановка задачи + +Полная постановка задачи приведена в отчёте для SEQ-версии [seq/report.md](../seq/report.md). +Кратко: + +Входные данные: +Две разреженные комплексные матрицы: + +- A — комплексная матрица размера m × n +- B — комплексная матрица размера n × k +представленные в формате CCS: + +Выходные данные: +Матрица: +C = A * B +в формате CCS. + +OpenMP-реализация должна давать **идентичный** SEQ результат при любом количестве потоков. + +## 3. Базовый алгоритм + +Последовательная версия (SEQ): + +1. Проверить корректность входных данных. +2. Инициализировать структуру результата. +3. Для каждого столбца матрицы B: + +- пройти по ненулевым элементам; +- выполнить умножение элементов; +- накопить значения в accumulator. + +4. Отсортировать индексы строк. +5. Сохранить ненулевые значения в матрицу результата. + +## 4. Схема распараллеливания + +В версии TBB используется параллельный цикл: +oneapi::tbb::parallel_for(...) +Каждый столбец результирующей матрицы вычисляется независимо. +Для хранения промежуточных результатов используются локальные контейнеры: + +- local_values; +- local_rows. +После завершения параллельного участка выполняется последовательная сборка итоговой CCS-матрицы. + +Особенности реализации: + +- отсутствуют общие изменяемые данные внутри вычисления столбца; +- каждая задача использует локальные структуры хранения; +- после завершения parallel_for результаты объединяются последовательно. + +Параллелизм достигается за счёт динамического распределения задач планировщиком TBB. + +## 5. Детали реализации + +- `tbb/include/ops_tbb.hpp` — объявление класса `GoriachevaKMultSparseComplexMatrixCcsTBB`. +- `tbb/src/ops_tbb.cpp` — реализация методов. + +ValidationImpl +Проверяется совместимость размеров и корректность структуры CCS. +RunImpl +Вычисления выполняются через: +oneapi::tbb::parallel_for(0, b.cols, [&](int j) { +ProcessColumn(j, a, b, local_values[j], local_rows[j]); +}); +Каждая итерация отвечает за отдельный столбец результата. + +Преимущества TBB + +- автоматическая балансировка нагрузки; +- work-stealing scheduler; +- отсутствие ручного управления потоками. + +Локальные структуры +Для каждого столбца выделяются: + +- local_values[j] +- local_rows[j] +что исключает гонки данных. + +## 6. Проверка корректности + +TBB-реализация проходит все функциональные тесты, +используемые для последовательной версии. + +### Способ проверки + +Корректность реализации проверялась с помощью функциональных тестов Google Test. + +Проверялись: +размеры результирующей матрицы; +массив col_ptr; +массив row_ind; +значения комплексных элементов с точностью 1e-9. + +Сравнение результатов реализовано функцией: +CompareMatrices(...) + +### Функциональные тесты + +Используется 14 тестов. + +| № теста | Описание | Проверяемый случай | +| ------- | -------------------------------- | ----------------------------------- | +| 0 | Матрицы 1×1 | Базовое умножение комплексных чисел | +| 1 | Диагональные 2×2 | Корректность умножения | +| 2 | Нулевой результат | Отсутствие ненулевых элементов | +| 3 | Мнимые числа | Работа с чисто мнимыми значениями | +| 4 | Комплексные коэффициенты | Корректность комплексной арифметики | +| 5 | Прямоугольные матрицы | Умножение матриц разных размеров | +| 6 | Разреженные 4×4 | Работа CCS-структуры | +| 7 | Один ненулевой столбец | Частично заполненная матрица | +| 8 | Крайние индексы | Проверка граничных строк | +| 9 | Несколько потоковых блоков | Корректность распределения | +| 10 | Полностью заполненная диагональ | Проверка накопления | +| 11 | Центральный элемент | Корректность индексации | +| 12 | Разреженные комплексные значения | Проверка знаков и мнимой части | +| 13 | Несортированные элементы столбца | Проверка сортировки строк | + +Все функциональные тесты успешно пройдены +Результаты совпадают с SEQ +с точностью 1e-9. + +## 7. Экспериментальная среда + +CPU: Intel(R) Core(TM) i5-9300H CPU @ 2.40GHz +OS: Windows 10 +Compiler: MinGW-w64 GCC, C++17 +Build type: Release +CMake: 4.0.1 +RAM: 32 GB + +### Сборка + +cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release +cmake --build build --config Release --parallel + +### Запуск тестов + +cd build/bin +mpiexec -n 4 ./ppc_func_tests --gtest_filter="*GoriachevaKViolationOrderElemVecFuncTests*" +mpiexec -n 4 ./ppc_perf_tests --gtest_filter="*GoriachevaKMultSparseComplexMatrixCcsPerfTest*" + +## 8. Результаты + +| Режим | Workers (p) | Time (сек) | Speedup | Efficiency | +| :---- | :---------: | :--------: | :-----: | :--------: | +| SEQ | 1 | 0.000166 | 1.00 | 1.00 | +| TBB | 2 | 0.001216 | 0.14 | 0.07 | +| TBB | 4 | 0.001273 | 0.13 | 0.03 | +| TBB | 8 | 0.001195 | 0.14 | 0.02 | +| TBB | 16 | 0.001162 | 0.14 | 0.01 | + +- Speedup = `T_seq / T_par` +- Efficiency = `Speedup / threads` +- База для `T_seq` — замер SEQ-версии + +TBB обеспечивает хорошую балансировку +при неравномерном распределении ненулевых элементов. + +Однако runtime TBB создаёт дополнительные накладные расходы на управление задачами. + +## 9. Выводы + +В работе была реализована версия алгоритма с использованием библиотеки Intel oneTBB. +TBB предоставляет удобный и современный механизм распараллеливания вычислений. +Преимущества: + +- автоматическое управление потоками; +- балансировка нагрузки; +- хорошая масштабируемость. +Недостатки: +- дополнительная зависимость от библиотеки; +- более сложная настройка среды. diff --git a/tasks/kopilov_d_vertical_gauss_filter/tests/performance/main.cpp b/tasks/kopilov_d_vertical_gauss_filter/tests/performance/main.cpp index 58df52a2b2..ff020734e3 100644 --- a/tasks/kopilov_d_vertical_gauss_filter/tests/performance/main.cpp +++ b/tasks/kopilov_d_vertical_gauss_filter/tests/performance/main.cpp @@ -1,15 +1,10 @@ #include #include -#include +#include #include #include -#include -#include -#include -#include -#include -#include +#include #include #include "kopilov_d_vertical_gauss_filter/all/include/ops_all.hpp" @@ -18,32 +13,28 @@ #include "kopilov_d_vertical_gauss_filter/seq/include/ops_seq.hpp" #include "kopilov_d_vertical_gauss_filter/stl/include/ops_stl.hpp" #include "kopilov_d_vertical_gauss_filter/tbb/include/ops_tbb.hpp" -#include "util/include/func_test_util.hpp" -#include "util/include/util.hpp" +#include "util/include/perf_test_util.hpp" namespace kopilov_d_vertical_gauss_filter { -class VerticalGaussFilterTaskTest : public ppc::util::BaseRunFuncTests { - public: - static std::string PrintTestParam(const TestType ¶m) { - const auto &matrix = std::get<0>(param); - std::string res = std::to_string(matrix.width) + "x" + std::to_string(matrix.height); - if (matrix.data.empty()) { - res.append("_empty"); - } else { - res.append("_v").append(std::to_string(matrix.data[0])); - } - return res; - } - +class GaussFilterPerfTester : public ppc::util::BaseRunPerfTests { protected: void SetUp() override { - const auto &test_params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); - input_ = std::get<0>(test_params); - reference_ = std::get<1>(test_params); + constexpr int kTargetWidth = 8192; + constexpr int kTargetHeight = 8192; + + source_image_.width = kTargetWidth; + source_image_.height = kTargetHeight; + source_image_.data.resize(static_cast(kTargetWidth) * static_cast(kTargetHeight)); + + std::random_device rd; + std::mt19937 rand_engine(rd()); + std::uniform_int_distribution color_dist(0, 255); + + std::ranges::generate(source_image_.data, [&]() { return static_cast(color_dist(rand_engine)); }); } - bool CheckTestOutputData(OutType &actual) final { + bool CheckTestOutputData(OutType &out) final { int is_mpi_initialized = 0; MPI_Initialized(&is_mpi_initialized); if (is_mpi_initialized != 0) { @@ -53,101 +44,31 @@ class VerticalGaussFilterTaskTest : public ppc::util::BaseRunFuncTests &raw) { - return Matrix{.width = w, .height = h, .data = raw}; -} - -const std::array kFunctionalScenarios = { - std::make_tuple(CreateMatrix(1, 1, {100}), CreateMatrix(1, 1, {100})), - std::make_tuple(CreateMatrix(2, 2, {1, 2, 3, 4}), CreateMatrix(2, 2, {1, 2, 2, 3})), - std::make_tuple(CreateMatrix(3, 3, std::vector(9, 16)), CreateMatrix(3, 3, std::vector(9, 16))), - std::make_tuple(CreateMatrix(3, 3, std::vector(9, 42)), CreateMatrix(3, 3, std::vector(9, 42))), - std::make_tuple(CreateMatrix(4, 4, std::vector(16, 100)), - CreateMatrix(4, 4, std::vector(16, 100)))}; - -const auto kTaskProviders = std::tuple_cat(ppc::util::AddFuncTask( - kFunctionalScenarios, PPC_SETTINGS_kopilov_d_vertical_gauss_filter), - ppc::util::AddFuncTask( - kFunctionalScenarios, PPC_SETTINGS_kopilov_d_vertical_gauss_filter), - ppc::util::AddFuncTask( - kFunctionalScenarios, PPC_SETTINGS_kopilov_d_vertical_gauss_filter), - ppc::util::AddFuncTask( - kFunctionalScenarios, PPC_SETTINGS_kopilov_d_vertical_gauss_filter), - ppc::util::AddFuncTask( - kFunctionalScenarios, PPC_SETTINGS_kopilov_d_vertical_gauss_filter)); - -INSTANTIATE_TEST_SUITE_P(GaussFilters, VerticalGaussFilterTaskTest, ppc::util::ExpandToValues(kTaskProviders), - VerticalGaussFilterTaskTest::PrintFuncTestName); - -} // namespace - -namespace { - -struct ValidationCase { - std::function(const Matrix &)> generator; - Matrix test_input; - std::string tag; -}; - -void PrintTo(const ValidationCase &c, std::ostream *os) { - *os << c.tag; -} - -class VerticalGaussFilterValidationTest : public testing::TestWithParam {}; - -TEST_P(VerticalGaussFilterValidationTest, DetectsInvalidInput) { - const auto &p = GetParam(); - EXPECT_FALSE(p.generator(p.test_input)->Validation()); -} - -std::vector GetValidationData() { - std::vector cases; - - std::vector> bad_inputs = { - {"ZeroSize", {.width = 0, .height = 0, .data = {}}}, - {"ZeroWidth", {.width = 0, .height = 10, .data = std::vector(10, 0)}}, - {"ZeroHeight", {.width = 10, .height = 0, .data = std::vector(10, 0)}}, - {"SizeMismatch", {.width = 3, .height = 3, .data = {255, 128}}}, - {"NegativeW", {.width = -1, .height = 1, .data = {0}}}, - {"NegativeH", {.width = 1, .height = -1, .data = {0}}}}; - - auto register_impl = [&](const std::string &name, auto factory) { - for (const auto &item : bad_inputs) { - std::string full_tag = name; - full_tag.append("_").append(item.first); - cases.push_back({factory, item.second, full_tag}); - } - }; - - register_impl("SEQ", [](const Matrix &m) { return std::make_shared(m); }); - register_impl("OMP", [](const Matrix &m) { return std::make_shared(m); }); - register_impl("STL", [](const Matrix &m) { return std::make_shared(m); }); - register_impl("TBB", [](const Matrix &m) { return std::make_shared(m); }); - register_impl("ALL", [](const Matrix &m) { return std::make_shared(m); }); - - return cases; -} +const auto kPerformanceTasks = + ppc::util::MakeAllPerfTasks(PPC_SETTINGS_kopilov_d_vertical_gauss_filter); -INSTANTIATE_TEST_SUITE_P(NegativeTests, VerticalGaussFilterValidationTest, testing::ValuesIn(GetValidationData()), - [](const testing::TestParamInfo &info) { return info.param.tag; }); +INSTANTIATE_TEST_SUITE_P(GaussFilterPerf, GaussFilterPerfTester, ppc::util::TupleToGTestValues(kPerformanceTasks), + GaussFilterPerfTester::CustomPerfTestName); } // namespace diff --git a/tasks/krasnopevtseva_v_hoare_batcher_sort/all/report.md b/tasks/krasnopevtseva_v_hoare_batcher_sort/all/report.md new file mode 100644 index 0000000000..bb8eef0ae6 --- /dev/null +++ b/tasks/krasnopevtseva_v_hoare_batcher_sort/all/report.md @@ -0,0 +1,354 @@ +# Гибридная сортировка Хоара-Бэтчера (Quick Batcher Sort) + +- Студент: Краснопевцева Вероника Дмитриевна, группа 3823Б1ПМоп3 +- Технология: ALL (STL + OMP) +- Вариант: 14 + +## 1. Введение + +Сортировка массивов данных — одна из фундаментальных задач в области +программирования. Быстрая сортировка Хоара известна своей эффективностью +в среднем случае, однако её производительность может деградировать при +неудачном выборе опорного элемента. Сортировка Бэтчера представляет собой +алгоритм сортировки слиянием с хорошими свойствами параллелизации, но +может быть избыточна для небольших массивов. + +Целью данной работы является реализация комбинированного параллельного +гибридного алгоритма, использующего возможности как OpenMP, так и STL, +сочетающего адаптивный итеративный QuickSort с последующим слиянием +Бэтчера для улучшения общей производительности. Ожидаемый результат — +устойчивая работа алгоритма на массивах различных размеров с +использованием оптимизаций, таких как сортировка вставками для малых +подмассивов и эффективное параллельное слияние, а также адаптивный выбор +стратегии в зависимости от размера данных. + +## 2. Постановка задачи + +### Формальное определение + +Необходимо реализовать комбинированный параллельный алгоритм сортировки +массива целых чисел на основе комбинации алгоритмов Хоара и Бэтчера с +использованием технологий OpenMP и STL. + +### Входные данные + +- Вектор целых чисел `std::vector` произвольной длины. + +### Выходные данные + +- Отсортированный по неубыванию вектор целых чисел. + +### Ограничения + +- Алгоритм должен корректно обрабатывать массивы любого размера. +- Для малых подмассивов используется сортировка вставками. +- Параллельная обработка выполняется на этапе начальной сортировки + подмассивов и на этапе слияния. +- Количество потоков определяется через `omp_get_max_threads()`. +- Реализована адаптивная стратегия: для малых массивов (n < 1000) + используется последовательная сортировка. + +## 3. Базовый алгоритм (последовательная версия) + +### 3.1 Быстрая сортировка (итеративная версия) + +Используется нерекурсивная реализация через стек: + +1. В стек помещается пара `(left, right)` для всего массива. +2. Пока стек не пуст: + - Извлекается диапазон `[l, r]`. + - Если `l >= r` — пропуск. + - Если размер диапазона небольшой - вызывается сортировка вставками. + - Выбирается опорный элемент (последний элемент массива). + - Выполняется разбиение Хоара. + - Меньший поддиапазон помещается в стек первым (для балансировки). + +### 3.2 Сортировка вставками + +Для малых подмассивов (размер < 16) применяется классическая сортировка +вставками, так как она показывает хорошие результаты на почти +отсортированных данных и малых размерах. + +### 3.3 Слияние Бэтчера + +После завершения быстрой сортировки выполняется слияние отсортированных +блоков с использованием алгоритма Бэтчера с последовательным применением +`std::inplace_merge`. + +## 4. Схема параллелизации + +### 4.1 Общая стратегия + +Параллелизация реализована по принципу "разделяй и властвуй" с +комбинированным использованием OpenMP и STL: + +1. **Адаптивный выбор стратегии**: Если размер массива менее 1000 + элементов, используется последовательная сортировка (QuickSort), + иначе — параллельная. +2. **Определение количества потоков**: Количество потоков определяется + через `omp_get_max_threads()`. +3. **Разбиение данных**: Исходный массив разделяется на `N` блоков, + где `N` — количество доступных потоков. +4. **Параллельная сортировка**: Каждый блок сортируется с + использованием директивы `#pragma omp parallel for`. +5. **Параллельное слияние**: Отсортированные блоки попарно сливаются + с использованием иерархической схемы Бэтчера, где каждая пара + сливается параллельно через OpenMP. + +### 4.2 Распределение данных + +- Размер блока для каждого потока: `base_size = n / numthreads` +- Остаток от деления добавляется к последнему блоку: + `last_size = base_size + remainder` +- Каждый поток получает указатель на начало своего блока и его размер +- OpenMP автоматически распределяет итерации по доступным потокам + +### 4.3 Коммуникационная схема + +- На этапе сортировки: блоки сортируются независимо, обмен данными + отсутствует. +- На этапе слияния: выполняется попарное слияние блоков с + использованием `std::inplace_merge`. +- Слияние организовано в виде иерархического дерева (парное слияние + на каждом уровне). +- OpenMP обеспечивает параллельное выполнение операций слияния на + каждом уровне. + +### 4.4 Синхронизация + +- Используется `#pragma omp parallel for` с явным указанием + shared-переменных. +- Отсутствие гонок данных гарантируется тем, что каждый поток работает + со своим выделенным диапазоном памяти. +- На этапе слияния используется условное распараллеливание через + параметр `par_if_greater` (порог 32). +- Адаптивная стратегия позволяет избежать накладных расходов на малых + массивах. + +## 5. Детали реализации + +### 5.1 Структура кода + +- **Файл**: `krasnopevtseva_v_hoare_batcher_sort/all/include/ops_all.hpp`, + `krasnopevtseva_v_hoare_batcher_sort/all/ops_all.cpp` +- **Пространство имён**: `krasnopevtseva_v_hoare_batcher_sort` +- **Класс**: `KrasnopevtsevaVHoareBatcherSortALL` + +### 5.2 Основные методы + +|Метод|Назначение| +|-|-| +|`ValidationImpl()`|Проверка входных данных (массив не пуст)| +|`PreProcessingImpl()`|Инициализация выходного вектора| +|`RunImpl()`|Запуск комбинированной сортировки| +|`SortLocalData()`|Адаптивный выбор стратегии сортировки| +|`ParallelSortChunks()`|Параллельная сортировка блоков| +|`QuickSort()`|Итеративная быстрая сортировка| +|`Partition()`|Разбиение Хоара| +|`InsertionSort()`|Сортировка вставками| +|`BatcherMergeBlocksStep()`|Слияние двух блоков| +|`BatcherMerge()`|Параллельное слияние блоков| + +### 5.3 Важные особенности + +- **Адаптивная стратегия**: Для массивов размером менее 1000 элементов + используется последовательная сортировка, что позволяет избежать + накладных расходов на создание потоков. +- **Выбор опорного элемента**: Используется последний элемент массива, + что упрощает реализацию. +- **Порог сортировки вставками**: 16 элементов. +- **Условное распараллеливание слияния**: Слияние выполняется параллельно + только если `(thread_input_size / step) > 32`. +- **Итеративный QuickSort**: Избегает переполнения стека вызовов. +- **Комбинированный подход**: Сочетает директивы OpenMP для параллельных + регионов и стандартную библиотеку для базовых операций. + +### 5.4 Использование памяти + +- Исходный массив сортируется на месте. +- Для отслеживания границ блоков используются массивы `pointers` и + `sizes`. +- OpenMP управляет созданием и распределением потоков. +- Дополнительная память: O(numthreads) для хранения указателей на + блоки. + +## 6. Окружение и тестирование + +### 6.1 Аппаратное и программное обеспечение + +- **Процессор**: AMD Ryzen 7 5700X 8-Core Processor +- **ОЗУ**: 32.0 ГБ +- **ОС**: Windows 11 Pro 25H2 +- **Набор инструментов**: DevContainer с компилятором GCC + (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 +- **Тип сборки**: Release +- **Фреймворк тестирования**: Google Test (gtest) +- **Технологии**: OpenMP + STL + +### 6.2 Переменные окружения + +- `OMP_NUM_THREADS` — количество потоков OpenMP + +### 6.3 Тестовые данные и верификация + +#### Функциональное тестирование + +Для проверки базовой корректности алгоритма был разработан набор из 6 +тестовых сценариев, покрывающих различные типы входных данных: + +|№|Тип массива|Описание|Размер| +|-|-|-|-:| +|1|`sorted_array`|Уже отсортированный по возрастанию массив|16| +|2|`default_array`|Случайный массив с произвольным порядком|20| +|3|`short_array`|Короткий массив минимального размера|4| +|4|`long_array`|Длинный массив с повторениями|50| +|5|`two_number_array`|Массив только из двух значений (10 и 20)|9| +|6|`end_to_begin_array`|Массив, отсортированный по убыванию|24| + +#### Верификация результата + +Для каждого тестового набора проверялось выполнение основного инварианта +сортировки: + +- Для всех индексов `i` от 0 до `size-2` выполняется условие + `output[i] <= output[i+1]` + +Дополнительно проводилось сравнение с эталонной реализацией `std::sort` +на тех же входных данных для всех тестовых случаев. + +#### Тестирование производительности + +Для оценки производительности был реализован отдельный тестовый набор, +генерирующий массив из **100 000** случайных целых чисел в диапазоне +`[0, 100 000 000]`. Генерация выполняется с использованием генератора +псевдослучайных чисел `std::mt19937` для обеспечения воспроизводимости +результатов. + +**Результаты корректности:** Все 6 функциональных тестов успешно пройдены +для ALL-версии. Перфоманс-тесты также подтверждают корректную сортировку +массива из 100 000 элементов. + +## 7. Результаты и обсуждение + +### 7.1 Корректность + +Корректность комбинированной реализации на ALL (OpenMP + STL) подтверждена: + +- Сравнением результатов с последовательной версией на идентичных + входных данных. +- Проверкой инварианта отсортированности для всех выходных массивов. +- Успешным прохождением всех функциональных тестов. +- Проверкой адаптивной стратегии на массивах различных размеров. + +### 7.2 Производительность + +Результаты для массива из **100 000** случайных целых чисел: + +|Реализация|Потоков|Время, мс|Ускорение|Эффективность| +|-|-:|-:|-:|-:| +|SEQ|1|32.0|1.00|N/A| +|ALL|8|22.0|1.45|18.2%| + +### 7.3 Анализ производительности + +#### Наблюдаемые эффекты + +- **Ускорение ALL версии около 1.45x** при использовании 8 потоков. + Это соответствует эффективности около 18.2%. + +#### Факторы, влияющие на производительность ALL версии + +|Фактор|Влияние на ALL версию| +|-|-| +|Адаптивная стратегия (n < 1000)|Положительное| +|Накладные расходы OpenMP|Умеренные| +|Параллельная сортировка блоков|Хорошее ускорение| +|Параллельное слияние|Умеренное ускорение| +|Балансировка нагрузки|Средняя| + +## 8. Заключения + +### Основные выводы + +1. Разработан комбинированный параллельный гибридный алгоритм сортировки + с использованием OpenMP и STL, сочетающий QuickSort, InsertionSort + и BatcherMerge. +2. Алгоритм корректен и демонстрирует ускорение до 1.45x на 8 потоках + для массива из 100 000 элементов. +3. Реализована адаптивная стратегия: для массивов размером менее 1000 + элементов используется последовательная сортировка, что позволяет + избежать накладных расходов на создание потоков. +4. Сортировка вставками для подмассивов размера < 16 даёт выигрыш по + сравнению с рекурсивным QuickSort. +5. Условное распараллеливание слияния позволяет адаптироваться под + размер обрабатываемых данных. + +## Приложение + +```cpp +bool KrasnopevtsevaVHoareBatcherSortALL::RunImpl() { + const auto &input = GetInput(); + std::vector result = input; + + if (result.size() <= 1) { + GetOutput() = result; + return true; + } + + SortLocalData(result); + GetOutput() = std::move(result); + return true; +} + +void KrasnopevtsevaVHoareBatcherSortALL::SortLocalData(std::vector &data) { + int n = static_cast(data.size()); + if (n <= 0) { + return; + } + + int numthreads = omp_get_max_threads(); + numthreads = std::min(n, numthreads); + + if (n < 1000) { + QuickSort(data, 0, n - 1); + } else { + ParallelSortChunks(data, n, numthreads); + } +} + +void KrasnopevtsevaVHoareBatcherSortALL::ParallelSortChunks( + std::vector &arr, int n, int numthreads) { + if (n <= 0) { + return; + } + + numthreads = std::min(n, numthreads); + if (numthreads <= 0) { + numthreads = 1; + } + + int thread_input_size = n / numthreads; + int thread_input_remainder_size = n % numthreads; + + std::vector pointers(numthreads); + std::vector sizes(numthreads); + + for (int i = 0; i < numthreads; ++i) { + std::ptrdiff_t offset = static_cast(i) * + static_cast(thread_input_size); + pointers[i] = arr.data() + offset; + sizes[i] = thread_input_size; + } + sizes.back() += thread_input_remainder_size; + +#pragma omp parallel for default(none) shared(arr, pointers, sizes, numthreads) + for (int i = 0; i < numthreads; ++i) { + int left = static_cast(pointers[i] - arr.data()); + int right = left + sizes[i] - 1; + if (left < right) { + QuickSort(arr, left, right); + } + } + + BatcherMerge(thread_input_size, pointers, sizes, 32); +} diff --git a/tasks/krasnopevtseva_v_hoare_batcher_sort/omp/report.md b/tasks/krasnopevtseva_v_hoare_batcher_sort/omp/report.md new file mode 100644 index 0000000000..c2c2fe3e2f --- /dev/null +++ b/tasks/krasnopevtseva_v_hoare_batcher_sort/omp/report.md @@ -0,0 +1,289 @@ +# Гибридная сортировка Хоара-Бэтчера (Quick Batcher Sort) + +- Студент: Краснопевцева Вероника Дмитриевна, группа 3823Б1ПМоп3 +- Технология: OMP +- Вариант: 14 + +## 1. Введение + +Сортировка массивов данных — одна из фундаментальных задач в области +программирования. Быстрая сортировка Хоара известна своей эффективностью +в среднем случае, однако её производительность может деградировать при +неудачном выборе опорного элемента. Сортировка Бэтчера представляет собой +алгоритм сортировки слиянием с хорошими свойствами параллелизации, но +может быть избыточна для небольших массивов. + +Целью данной работы является реализация параллельного гибридного +алгоритма с использованием технологии OpenMP, сочетающего адаптивный +итеративный QuickSort с последующим слиянием Бэтчера для улучшения +общей производительности. Ожидаемый результат — устойчивая работа +алгоритма на массивах различных размеров с использованием оптимизаций, +таких как сортировка вставками для малых подмассивов и эффективное +параллельное слияние. + +## 2. Постановка задачи + +### Формальное определение + +Необходимо реализовать параллельный алгоритм сортировки массива целых +чисел на основе комбинации алгоритмов Хоара и Бэтчера с использованием +технологии OpenMP. + +### Входные данные + +- Вектор целых чисел `std::vector` произвольной длины. + +### Выходные данные + +- Отсортированный по неубыванию вектор целых чисел. + +### Ограничения + +- Алгоритм должен корректно обрабатывать массивы любого размера. +- Для малых подмассивов используется сортировка вставками. +- Параллельная обработка выполняется на этапе начальной сортировки + подмассивов и на этапе слияния. + +## 3. Базовый алгоритм (последовательная версия) + +### 3.1 Быстрая сортировка (итеративная версия) + +Используется нерекурсивная реализация через стек: + +1. В стек помещается пара `(left, right)` для всего массива. +2. Пока стек не пуст: + - Извлекается диапазон `[l, r]`. + - Если `l >= r` — пропуск. + - Если размер диапазона небольшой - вызывается сортировка вставками. + - Выбирается опорный элемент (последний элемент массива). + - Выполняется разбиение Хоара. + - Меньший поддиапазон помещается в стек первым (для балансировки). + +### 3.2 Сортировка вставками + +Для малых подмассивов (размер < 16) применяется классическая сортировка +вставками, так как она показывает хорошие результаты на почти +отсортированных данных и малых размерах. + +### 3.3 Слияние Бэтчера + +После завершения быстрой сортировки выполняется слияние отсортированных +блоков с использованием алгоритма Бэтчера с последовательным применением +`std::inplace_merge`. + +## 4. Схема параллелизации + +### 4.1 Общая стратегия + +Параллелизация реализована по принципу "разделяй и властвуй" с +использованием OpenMP: + +1. **Разбиение данных**: Исходный массив разделяется на `N` блоков, + где `N` — количество доступных потоков. +2. **Параллельная сортировка**: Каждый поток независимо сортирует свой + блок с использованием итеративного QuickSort. +3. **Параллельное слияние**: Отсортированные блоки попарно сливаются + с использованием параллельного алгоритма Бэтчера. + +### 4.2 Распределение данных + +- Размер блока для каждого потока: `base_size = n / numthreads` +- Остаток от деления добавляется к последнему блоку: + `last_size = base_size + remainder` +- Каждый поток получает указатель на начало своего блока и его размер + +### 4.3 Коммуникационная схема + +- На этапе сортировки: потоки работают независимо, обмен данными + отсутствует. +- На этапе слияния: выполняется попарное слияние блоков с + использованием `std::inplace_merge`. +- Слияние организовано в виде иерархического дерева (парное слияние + на каждом уровне). + +### 4.4 Синхронизация + +- Используется `#pragma omp parallel for` с явным указанием + shared-переменных. +- Отсутствие гонок данных гарантируется тем, что каждый поток работает + со своим выделенным диапазоном памяти. +- На этапе слияния используется `#pragma omp parallel for` с порогом + `par_if_greater` для распараллеливания. + +## 5. Детали реализации + +### 5.1 Структура кода + +- **Файл**: `krasnopevtseva_v_hoare_batcher_sort/omp/include/ops_omp.hpp`, + `krasnopevtseva_v_hoare_batcher_sort/omp/ops_omp.cpp` +- **Пространство имён**: `krasnopevtseva_v_hoare_batcher_sort` +- **Класс**: `KrasnopevtsevaVHoareBatcherSortOMP` + +### 5.2 Основные методы + +|№|Тип массива|Описание|Размер| +|-|-|-|-:| +|1|`sorted_array`|Уже отсортированный по возрастанию массив|16| +|2|`default_array`|Случайный массив с произвольным порядком|20| +|3|`short_array`|Короткий массив минимального размера|4| +|4|`long_array`|Длинный массив с повторениями|50| +|5|`two_number_array`|Массив только из двух значений (10 и 20)|9| +|6|`end_to_begin_array`|Массив, отсортированный по убыванию|24| + +### 5.3 Важные особенности + +- **Выбор опорного элемента**: В параллельной версии используется + последний элемент массива, что упрощает реализацию. +- **Порог сортировки вставками**: 16 элементов. +- **Условное распараллеливание слияния**: Слияние выполняется параллельно + только если `thread_input_size / step > par_if_greater` (параметр 32). +- **Итеративный QuickSort**: Избегает переполнения стека вызовов. +- **Особая обработка граничных случаев**: Учитываются ситуации с + нечётным количеством блоков. + +### 5.4 Использование памяти + +- Исходный массив сортируется на месте. +- Для отслеживания границ блоков используются массивы `pointers` и + `sizes`. +- Дополнительная память: O(numthreads) для хранения указателей на + блоки. + +## 6. Окружение и тестирование + +### 6.1 Аппаратное и программное обеспечение + +- **Процессор**: AMD Ryzen 7 5700X 8-Core Processor +- **ОЗУ**: 32.0 ГБ +- **ОС**: Windows 11 Pro 25H2 +- **Набор инструментов**: DevContainer с компилятором GCC + (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 +- **Тип сборки**: Release +- **Фреймворк тестирования**: Google Test (gtest) +- **Технология**: OpenMP + +### 6.2 Переменные окружения + +- `OMP_NUM_THREADS` — количество потоков OpenMP + +### 6.3 Тестовые данные и верификация + +#### Функциональное тестирование + +Для проверки базовой корректности алгоритма был разработан набор из 6 +тестовых сценариев, покрывающих различные типы входных данных: + +|№|Тип массива|Описание|Размер| +|-|-|-|-:| +|1|`sorted_array`|Уже отсортированный по возрастанию массив|16| +|2|`default_array`|Случайный массив с произвольным порядком|20| +|3|`short_array`|Короткий массив минимального размера|4| +|4|`long_array`|Длинный массив с повторениями|50| +|5|`two_number_array`|Массив только из двух значений|9| +|6|`end_to_begin_array`|Массив, отсортированный по убыванию|24| + +#### Верификация результата + +Для каждого тестового набора проверялось выполнение основного инварианта +сортировки: + +- Для всех индексов `i` от 0 до `size-2` выполняется условие + `output[i] <= output[i+1]` + +Дополнительно проводилось сравнение с эталонной реализацией `std::sort` +на тех же входных данных для всех тестовых случаев. + +#### Тестирование производительности + +Для оценки производительности был реализован отдельный тестовый набор, +генерирующий массив из **100 000** случайных целых чисел в диапазоне +`[0, 100 000 000]`. Генерация выполняется с использованием генератора +псевдослучайных чисел `std::mt19937` для обеспечения воспроизводимости +результатов. + +**Результаты корректности:** Все 6 функциональных тестов успешно пройдены +для всех конфигураций количества потоков (1, 2, 4, 8). Перфоманс-тесты +также подтверждают корректную сортировку массива из 100 000 элементов. + +## 7. Результаты и обсуждение + +### 7.1 Корректность + +Корректность параллельной реализации подтверждена: + +- Сравнением результатов с последовательной версией на идентичных + входных данных. +- Проверкой инварианта отсортированности для всех выходных массивов. +- Успешным прохождением всех функциональных тестов для различных + конфигураций потоков. + +### 7.2 Производительность + +Результаты для массива из **100 000** случайных целых чисел: + +|Режим|Потоков|Время, мс|Ускорение|Эффективность| +|-|-:|-:|-:|-:| +|seq|1|32|1.00|N/A| +|omp|8|19|1.94|24.3%| + +### 7.3 Анализ производительности + +#### Масштабирование + +- При использовании 8 потоков ускорение достигает 1.94x + (эффективность 24.3%). + +## 8. Заключения + +### Основные выводы + +1. Разработан параллельный гибридный алгоритм сортировки с использованием + OpenMP, сочетающий QuickSort, InsertionSort и BatcherMerge. +2. При использовании 8 потоков ускорение достигает 1.94x, что подтверждает + эффективность параллелизации для массивов большого размера. + +## Приложение + +### Основной цикл параллельной сортировки + +```cpp +bool KrasnopevtsevaVHoareBatcherSortOMP::RunImpl() { + const auto &input = GetInput(); + std::size_t size = input.size(); + + if (size <= 1) { + GetOutput() = input; + return true; + } + + std::vector res = input; + + int n = static_cast(size); + int numthreads = omp_get_max_threads(); + numthreads = std::min(n, numthreads); + + int thread_input_size = n / numthreads; + int thread_input_remainder_size = n % numthreads; + + std::vector pointers(numthreads); + std::vector sizes(numthreads); + for (int i = 0; i < numthreads; ++i) { + std::ptrdiff_t offset = static_cast(i) * + static_cast(thread_input_size); + pointers[i] = res.data() + offset; + sizes[i] = thread_input_size; + } + sizes[sizes.size() - 1] += thread_input_remainder_size; + +#pragma omp parallel for default(none) shared(res, pointers, sizes, numthreads) + for (int i = 0; i < numthreads; ++i) { + int left = static_cast(pointers[i] - res.data()); + int right = left + sizes[i] - 1; + QuickSort(res, left, right); + } + + BatcherMerge(thread_input_size, pointers, sizes, 32); + + GetOutput() = std::move(res); + return true; +} diff --git a/tasks/krasnopevtseva_v_hoare_batcher_sort/report.md b/tasks/krasnopevtseva_v_hoare_batcher_sort/report.md new file mode 100644 index 0000000000..0286cec5b4 --- /dev/null +++ b/tasks/krasnopevtseva_v_hoare_batcher_sort/report.md @@ -0,0 +1,113 @@ +# Итоговый отчет по реализации гибридной сортировки Хоара-Бэтчера + +- Студент: Краснопевцева Вероника Дмитриевна, группа 3823Б1ПМоп3 +- Вариант: 14 + +## 1. Цель работы + +Реализовать и исследовать гибридный алгоритм сортировки, сочетающий +итеративную быструю сортировку Хоара с сортировкой вставками для малых +подмассивов и последующим слиянием Бэтчера. Алгоритм реализован в пяти +вариантах: последовательная версия (SEQ) и параллельные версии с +использованием OpenMP, STL, Intel TBB и комбинированной версии ALL +(OpenMP + адаптивная стратегия). Провести сравнительный анализ +производительности различных технологий параллелизации. + +## 2. Реализованные версии + +### 2.1 Общая структура алгоритма + +Все версии реализуют единую логику сортировки: + +1. Итеративная быстрая сортировка с использованием стека +2. Сортировка вставками для подмассивов размером менее 16 элементов +3. Слияние отсортированных блоков по алгоритму Бэтчера с + использованием `std::inplace_merge` + +### 2.2 Особенности реализаций + +|Версия|Технология|Ключевые особенности| +|-|-|-| +|SEQ|Последовательная|Базовый алгоритм без параллелизации| +|OMP|OpenMP|`#pragma omp parallel for`, параллельная сортировка| +|STL|std::async + future|Асинхронные задачи, аппаратное определение потоков| +|TBB|Intel TBB|`tbb::parallel_for`, `tbb::global_control`| +|ALL|OpenMP + STL|Комбинированный подход, адаптивная стратегия| + +## 3. Экспериментальная среда + +- **Процессор**: AMD Ryzen 7 5700X 8-Core Processor +- **ОЗУ**: 32.0 ГБ +- **ОС**: Windows 11 Pro 25H2 +- **Компилятор**: GCC 13.3.0 (Ubuntu 13.3.0-6ubuntu2~24.04) +- **Тип сборки**: Release +- **Размер тестовых данных**: 100 000 случайных целых чисел + +## 4. Результаты производительности + +### 4.1 Сводная таблица + +|Версия|Технология|Время, мс|Ускорение|Эффективность| +|-|-|-:|-:|-:| +|SEQ|Последовательная|32.0|1.00x|N/A| +|OMP|OpenMP|19.0|1.68x|21.0%| +|STL|std::async|18.0|1.78x|22.3%| +|TBB|Intel TBB|22.0|1.45x|18.2%| +|ALL|OpenMP + адаптивная|22.0|1.45x|18.2%| + +## 5. Сравнительный анализ + +### 5.1 Лидер производительности + +**STL (std::async)** показывает наилучший результат — 18 мс +(ускорение 1.78x). Это объясняется: + +- Минимальными накладными расходами при создании асинхронных задач +- Отсутствием дополнительных абстракций (в отличие от TBB) +- Эффективным распределением блоков между потоками + +### 5.2 Анализ эффективности + +|Версия|Эффективность|Объяснение| +|-|-|-| +|STL|22.3%|Наиболее эффективная реализация| +|OMP|21.0%|Небольшое отставание от STL| +|TBB|18.2%|Планировщик задач вносит накладные расходы| +|ALL|18.2%|Накладные расходы OpenMP + адаптивная стратегия| + +## 6. Выводы + +### 6.1 Основные результаты + +1. **Все реализации корректны** и успешно проходят функциональные тесты + на массивах различных типов. + +2. **Наилучшую производительность** для массива из 100 000 элементов + показала версия на STL — 18 мс (ускорение 1.78x относительно SEQ). + +3. **OpenMP** показал результат 19 мс (ускорение 1.68x), что лишь на + 1 мс хуже лидера. + +4. **TBB и ALL** показали одинаковый результат 22 мс + (ускорение 1.45x). + +5. **Эффективность параллелизации** составляет от 18% до 22%, что + объясняется: + - Относительно небольшим размером данных (100 000 элементов) + - Наличием последовательной части алгоритма (слияние Бэтчера) + - Накладными расходами на управление потоками + +## 7. Заключение + +В данной работе была успешно реализована и исследована гибридная +сортировка Хоара-Бэтчера в пяти различных вариантах. Наилучшую +производительность для массива из 100 000 элементов показала +реализация на STL (std::async) с временем 18 мс, что в 1.78 раза +быстрее последовательной версии. Реализация на OpenMP оказалась лишь на +1 мс медленнее, демонстрируя хороший баланс между производительностью и +простотой реализации. TBB и комбинированная версия показали одинаковый +результат 22 мс. + +Все реализации корректно работают на различных типах входных данных и +могут быть рекомендованы для использования в зависимости от конкретных +требований к производительности, переносимости и сложности реализации. diff --git a/tasks/krasnopevtseva_v_hoare_batcher_sort/seq/report.md b/tasks/krasnopevtseva_v_hoare_batcher_sort/seq/report.md new file mode 100644 index 0000000000..de01748947 --- /dev/null +++ b/tasks/krasnopevtseva_v_hoare_batcher_sort/seq/report.md @@ -0,0 +1,217 @@ +# Гибридная сортировка Хоара-Бэтчера (Quick Batcher Sort) + +- Студент: Краснопевцева Вероника Дмитриевна, группа 3823Б1ПМоп3 +- Технология: SEQ +- Вариант: 14 + +## 1. Введение + +Сортировка массивов данных — одна из фундаментальных задач в области +программирования. Быстрая сортировка Хоара известна своей эффективностью +в среднем случае, однако её производительность может деградировать при +неудачном выборе опорного элемента. Сортировка Бэтчера (Batcher's odd-even +merge sort) представляет собой алгоритм сортировки слиянием с хорошими +свойствами параллелизации, но может быть избыточна для небольших массивов. + +Целью данной работы является реализация гибридного алгоритма, сочетающего +адаптивный итеративный QuickSort с последующим слиянием Бэтчера для +улучшения общей производительности. Ожидаемый результат — устойчивая +работа алгоритма на массивах различных размеров с использованием +оптимизаций, таких как сортировка вставками для малых подмассивов и +медиана из трёх для выбора опорного элемента. + +## 2. Постановка задачи + +### Формальное определение + +Необходимо реализовать последовательный алгоритм сортировки массива целых +чисел на основе комбинации алгоритмов Хоара и Бэтчера. + +### Входные данные + +- Вектор целых чисел `std::vector` произвольной длины. + +### Выходные данные + +- Отсортированный по неубыванию вектор целых чисел. + +### Ограничения + +- Алгоритм должен корректно обрабатывать массивы любого размера. +- Для малых подмассивов используется сортировка вставками. +- Для больших массивов после завершения быстрой сортировки выполняется + слияние Бэтчера. + +## 3. Базовый алгоритм (последовательная версия) + +### 3.1 Быстрая сортировка (итеративная версия) + +Используется нерекурсивная реализация через стек: + +1. В стек помещается пара `(left, right)` для всего массива. +2. Пока стек не пуст: + - Извлекается диапазон `[l, r]`. + - Если `l >= r` — пропуск. + - Если размер диапазона небольшой - вызывается сортировка вставками. + - Выбирается опорный элемент (медиана из трёх: `arr[l]`, `arr[mid]`, + `arr[r]`). + - Выполняется разбиение Хоара. + - Меньший поддиапазон помещается в стек первым (для балансировки). + +### 3.2 Сортировка вставками + +Для малых подмассивов применяется классическая сортировка вставками, так +как она показывает хорошие результаты на почти отсортированных данных и +малых размерах. + +### 3.3 Слияние Бэтчера + +После завершения быстрой сортировки, если размер исходного диапазона +превышает 32, вызывается `BatcherMerge`: + +- Создаётся временная копия диапазона `[left, right]`. +- Рекурсивно выполняется нечётно-чётное слияние. +- Выполняется постобработка для соседних элементов с шагом 2. + +## 4. Схема параллелизации + +Данная реализация является последовательной и не использует параллельные +технологии. В рамках задания SEQ не предполагается параллелизация. + +## 5. Детали реализации + +### 5.1 Структура кода + +- **Файл**: `krasnopevtseva_v_hoare_batcher_sort/seq/include/ops_seq.hpp`, + `krasnopevtseva_v_hoare_batcher_sort/seq/ops_seq.cpp` +- **Пространство имён**: `krasnopevtseva_v_hoare_batcher_sort` +- **Класс**: `KrasnopevtsevaVHoareBatcherSortSEQ` + +### 5.2 Основные методы + +|Метод|Назначение| +|-|-| +|`ValidationImpl()`|Проверка входных данных (массив не пуст)| +|`PreProcessingImpl()`|Инициализация выходного вектора| +|`RunImpl()`|Запуск сортировки| +|`QuickBatcherSort()`|Итеративная быстрая сортировка со стеком| +|`Partition()`|Разбиение Хоара с медианой из трёх| +|`InsertionSort()`|Сортировка вставками| +|`BatcherMerge()`|Слияние Бэтчера| +|`CompareAndSwap()`|Вспомогательная функция сравнения и обмена| + +## 6. Окружение и тестирование + +### 6.1 Аппаратное и программное обеспечение + +- **Процессор**: AMD Ryzen 7 5700X 8-Core Processor +- **ОЗУ**: 32.0 ГБ +- **ОС**: Windows 11 Pro 25H2 +- **Набор инструментов**: DevContainer с компилятором GCC + (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 +- **Тип сборки**: Release +- **Фреймворк тестирования**: Google Test (gtest) +- **Режим**: SEQ + +### 6.2 Тестовые данные и верификация + +#### Функциональное тестирование + +Для проверки базовой корректности алгоритма был разработан набор из 6 +тестовых сценариев, покрывающих различные типы входных данных: + +|№|Тип массива|Описание|Размер| +|-|-|-|-:| +|1|`sorted_array`|Уже отсортированный по возрастанию массив|16| +|2|`default_array`|Случайный массив с произвольным порядком|20| +|3|`short_array`|Короткий массив минимального размера|4| +|4|`long_array`|Длинный массив с повторениями|50| +|5|`two_number_array`|Массив только из двух значений (10 и 20)|9| +|6|`end_to_begin_array`|Массив, отсортированный по убыванию|24| + +#### Верификация результата + +Для каждого тестового набора проверялось выполнение основного инварианта +сортировки: + +- Для всех индексов `i` от 0 до `size-2` выполняется условие + `output[i] <= output[i+1]` + +Дополнительно проводилось сравнение с эталонной реализацией `std::sort` +на тех же входных данных для всех тестовых случаев. + +#### Тестирование производительности + +Для оценки производительности был реализован отдельный тестовый набор, +генерирующий массив из **100 000** случайных целых чисел в диапазоне +`[0, 100 000 000]`. Генерация выполняется с использованием генератора +псевдослучайных чисел `std::mt19937` для обеспечения воспроизводимости +результатов. + +**Результаты корректности:** Все 6 функциональных тестов успешно пройдены +для SEQ. Перфоманс-тесты также подтверждают корректную сортировку массива +из 100 000 элементов. + +## 7. Производительность + +### 7.1 Результаты + +Результаты для массива из **100 000** случайных целых чисел: + +|Режим|Размер|Время, мс|Ускорение|Эффективность| +|-|-:|-:|-:|-:| +|seq|100000|32|1.00|N/A| + +*Сравнение с параллельными версиями не предусмотрено для SEQ.* + +### 7.2 Анализ производительности + +- Основной вклад вносят сравнения и перемещения во время разбиения и + слияния. + +## 8. Заключения + +### Основные выводы + +1. Разработан гибридный последовательный алгоритм сортировки, сочетающий + QuickSort, InsertionSort и BatcherMerge. +2. Алгоритм корректен и показывает приемлемую производительность для + массивов до 10^6 элементов. +3. Использование сортировки вставками для подмассивов размера < 16 даёт + выигрыш по сравнению с QuickSort. +4. Слияние Бэтчера улучшает порядок элементов после завершения QuickSort, + но ценой дополнительной памяти. + +## Приложение + +```cpp +void KrasnopevtsevaVHoareBatcherSortSEQ::QuickBatcherSort( + std::vector &arr, int left, int right) { + std::stack> stack; + stack.emplace(left, right); + + while (!stack.empty()) { + auto [l, r] = stack.top(); + stack.pop(); + + if (l >= r) continue; + if (r - l < 16) { + InsertionSort(arr, l, r); + continue; + } + + int pivot_index = Partition(arr, l, r); + + if (pivot_index - l < r - pivot_index) { + stack.emplace(pivot_index + 1, r); + stack.emplace(l, pivot_index - 1); + } else { + stack.emplace(l, pivot_index - 1); + stack.emplace(pivot_index + 1, r); + } + } + + if (right - left > 32) { + BatcherMerge(arr, left, right); + } +} diff --git a/tasks/krasnopevtseva_v_hoare_batcher_sort/stl/report.md b/tasks/krasnopevtseva_v_hoare_batcher_sort/stl/report.md new file mode 100644 index 0000000000..839702574f --- /dev/null +++ b/tasks/krasnopevtseva_v_hoare_batcher_sort/stl/report.md @@ -0,0 +1,322 @@ +# Гибридная сортировка Хоара-Бэтчера (Quick Batcher Sort) + +- Студент: Краснопевцева Вероника Дмитриевна, группа 3823Б1ПМоп3 +- Технология: STL +- Вариант: 14 + +## 1. Введение + +Сортировка массивов данных — одна из фундаментальных задач в области +программирования. Быстрая сортировка Хоара известна своей эффективностью +в среднем случае, однако её производительность может деградировать при +неудачном выборе опорного элемента. Сортировка Бэтчера представляет собой +алгоритм сортировки слиянием с хорошими свойствами параллелизации, но +может быть избыточна для небольших массивов. + +Целью данной работы является реализация параллельного гибридного +алгоритма с использованием стандартной библиотеки шаблонов C++ (STL), +в частности средств асинхронного выполнения `std::async` и `std::future`, +сочетающего адаптивный итеративный QuickSort с последующим слиянием +Бэтчера для улучшения общей производительности. Ожидаемый результат — +устойчивая работа алгоритма на массивах различных размеров с +использованием оптимизаций, таких как сортировка вставками для малых +подмассивов и эффективное параллельное слияние. + +## 2. Постановка задачи + +### Формальное определение + +Необходимо реализовать параллельный алгоритм сортировки массива целых +чисел на основе комбинации алгоритмов Хоара и Бэтчера с использованием +средств параллелизации из стандартной библиотеки C++ (STL). + +### Входные данные + +- Вектор целых чисел `std::vector` произвольной длины. + +### Выходные данные + +- Отсортированный по неубыванию вектор целых чисел. + +### Ограничения + +- Алгоритм должен корректно обрабатывать массивы любого размера. +- Для малых подмассивов используется сортировка вставками. +- Параллельная обработка выполняется на этапе начальной сортировки + подмассивов и на этапе слияния. +- Количество потоков определяется аппаратными возможностями системы. + +## 3. Базовый алгоритм (последовательная версия) + +### 3.1 Быстрая сортировка (итеративная версия) + +Используется нерекурсивная реализация через стек: + +1. В стек помещается пара `(left, right)` для всего массива. +2. Пока стек не пуст: + - Извлекается диапазон `[l, r]`. + - Если `l >= r` — пропуск. + - Если размер диапазона небольшой - вызывается сортировка вставками. + - Выбирается опорный элемент (последний элемент массива). + - Выполняется разбиение Хоара. + - Меньший поддиапазон помещается в стек первым (для балансировки). + +### 3.2 Сортировка вставками + +Для малых подмассивов (размер < 16) применяется классическая сортировка +вставками, так как она показывает хорошие результаты на почти +отсортированных данных и малых размерах. + +### 3.3 Слияние Бэтчера + +После завершения быстрой сортировки выполняется слияние отсортированных +блоков с использованием алгоритма Бэтчера с последовательным применением +`std::inplace_merge`. + +## 4. Схема параллелизации + +### 4.1 Общая стратегия + +Параллелизация реализована по принципу "разделяй и властвуй" с +использованием средств STL: + +1. **Определение количества потоков**: Автоматическое определение + аппаратной конкуренции через `std::thread::hardware_concurrency()`. +2. **Разбиение данных**: Исходный массив разделяется на `N` блоков, + где `N` — количество доступных потоков. +3. **Параллельная сортировка**: Каждый блок сортируется асинхронно с + использованием `std::async(std::launch::async, ...)`. +4. **Параллельное слияние**: Отсортированные блоки попарно сливаются + с использованием иерархической схемы Бэтчера, где каждая пара + сливается асинхронно. + +### 4.2 Распределение данных + +- Размер блока для каждого потока: `base_size = n / numthreads` +- Остаток от деления добавляется к последнему блоку: + `last_size = base_size + remainder` +- Каждый блок описывается структурой `Chunk`, содержащей указатель + на данные, размер и границы (left, right) + +### 4.3 Коммуникационная схема + +- На этапе сортировки: блоки сортируются независимо, обмен данными + отсутствует. +- На этапе слияния: выполняется попарное слияние блоков с + использованием `std::inplace_merge`. +- Слияние организовано в виде иерархического дерева (парное слияние + на каждом уровне). +- После слияния блоков на уровне структура `chunks` обновляется + (удаляются слитые блоки). + +### 4.4 Синхронизация + +- Используется `std::future` и `std::async` для асинхронного + выполнения задач. +- Синхронизация выполняется через вызов `future.wait()` для каждого + асинхронного задания. +- Отсутствие гонок данных гарантируется тем, что каждый поток работает + со своим выделенным диапазоном памяти. +- На этапе слияния используется условное распараллеливание через + параметр `par_if_greater` (порог 32). + +## 5. Детали реализации + +### 5.1 Структура кода + +- **Файл**: `krasnopevtseva_v_hoare_batcher_sort/stl/include/ops_stl.hpp`, + `krasnopevtseva_v_hoare_batcher_sort/stl/ops_stl.cpp` +- **Пространство имён**: `krasnopevtseva_v_hoare_batcher_sort` +- **Класс**: `KrasnopevtsevaVHoareBatcherSortSTL` + +### 5.2 Основные методы + +|Метод|Назначение| +|-|-| +|`ValidationImpl()`|Проверка входных данных| +|`PreProcessingImpl()`|Инициализация выходного вектора| +|`RunImpl()`|Запуск параллельной сортировки| +|`GetNumThreads()`|Определение количества потоков| +|`SetupChunks()`|Разбиение массива на блоки| +|`ParallelSortChunks()`|Асинхронная сортировка блоков| +|`QuickSort()`|Итеративная быстрая сортировка| +|`Partition()`|Разбиение Хоара| +|`InsertionSort()`|Сортировка вставками| +|`BatcherMergeBlocksStep()`|Слияние двух блоков| +|`BatcherMergeLevel()`|Параллельное слияние на уровне| + +### 5.3 Важные особенности + +- **Автоматическое определение потоков**: Количество потоков определяется + через `std::thread::hardware_concurrency()` с fallback на 1. +- **Выбор опорного элемента**: Используется последний элемент массива, + что упрощает реализацию. +- **Порог сортировки вставками**: 16 элементов. +- **Условное распараллеливание слияния**: Слияние выполняется параллельно + только если `(thread_input_size / step) > 32`. +- **Динамическое управление блоками**: Вектор `chunks` изменяет свой + размер по мере слияния блоков. +- **Итеративный QuickSort**: Избегает переполнения стека вызовов. + +### 5.4 Использование памяти + +- Для отслеживания границ блоков используется вектор `chunks` размера + O(numthreads). +- Асинхронные задачи создаются динамически на этапах сортировки и + слияния. + +## 6. Окружение и тестирование + +### 6.1 Аппаратное и программное обеспечение + +- **Процессор**: AMD Ryzen 7 5700X 8-Core Processor +- **ОЗУ**: 32.0 ГБ +- **ОС**: Windows 11 Pro 25H2 +- **Набор инструментов**: DevContainer с компилятором GCC + (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 +- **Тип сборки**: Release +- **Фреймворк тестирования**: Google Test (gtest) +- **Технология**: STL + +### 6.2 Тестовые данные и верификация + +#### Функциональное тестирование + +Для проверки базовой корректности алгоритма был разработан набор из 6 +тестовых сценариев, покрывающих различные типы входных данных: + +|№|Тип массива|Описание|Размер| +|-|-|-|-:| +|1|`sorted_array`|Уже отсортированный по возрастанию массив|16| +|2|`default_array`|Случайный массив с произвольным порядком|20| +|3|`short_array`|Короткий массив минимального размера|4| +|4|`long_array`|Длинный массив с повторениями|50| +|5|`two_number_array`|Массив только из двух значений (10 и 20)|9| +|6|`end_to_begin_array`|Массив, отсортированный по убыванию|24| + +#### Верификация результата + +Для каждого тестового набора проверялось выполнение основного инварианта +сортировки: + +- Для всех индексов `i` от 0 до `size-2` выполняется условие + `output[i] <= output[i+1]` + +Дополнительно проводилось сравнение с эталонной реализацией `std::sort` +на тех же входных данных для всех тестовых случаев. + +#### Тестирование производительности + +Для оценки производительности был реализован отдельный тестовый набор, +генерирующий массив из **100 000** случайных целых чисел в диапазоне +`[0, 100 000 000]`. Генерация выполняется с использованием генератора +псевдослучайных чисел `std::mt19937`. + +**Результаты корректности:** Все 6 функциональных тестов успешно пройдены +для STL-версии. Перфоманс-тесты также подтверждают корректную сортировку +массива из 100 000 элементов. + +## 7. Результаты и обсуждение + +### 7.1 Корректность + +Корректность параллельной реализации на STL подтверждена: + +- Сравнением результатов с последовательной версией. +- Проверкой инварианта отсортированности. +- Успешным прохождением всех функциональных тестов. + +### 7.2 Производительность + +Результаты для массива из **100 000** случайных целых чисел: + +|Реализация|Потоков|Время, мс|Ускорение|Эффективность| +|-|-|-|-|-| +|SEQ|1|32.0|1.00|N/A| +|STL|8|18.0|1.78|22.3%| + +*Примечание: Количество потоков определяется автоматически через +`std::thread::hardware_concurrency()`.* + +### 7.3 Анализ производительности + +#### Наблюдаемые эффекты + +- **Ускорение STL версии около 1.78x** при использовании 8 потоков. + Это соответствует эффективности около 22.3%. +- Для массива из 100 000 элементов время выполнения составляет 18 мс, + что в 1.78 раза быстрее последовательной версии (32 мс). + +#### Факторы, влияющие на производительность + +|Фактор|Влияние на STL версию| +|-|-| +|Накладные расходы std::async|Высокие| +|Параллельная сортировка блоков|Хорошее ускорение| +|Параллельное слияние|Умеренное ускорение| +|Балансировка нагрузки|Средняя| +|Условное распараллеливание|Положительное| + +## 8. Заключения + +### Основные выводы + +1. Разработан параллельный гибридный алгоритм сортировки с использованием + STL (std::async, std::future), сочетающий QuickSort, InsertionSort и + BatcherMerge. +2. Алгоритм корректен и демонстрирует ускорение до 1.78x на 8 потоках + для массива из 100 000 элементов. +3. Средства STL обеспечивают хорошую переносимость. +4. Сортировка вставками для подмассивов размера < 16 даёт выигрыш. +5. Условное распараллеливание слияния позволяет адаптироваться под + размер обрабатываемых данных. + +## Приложение + +```cpp +bool KrasnopevtsevaVHoareBatcherSortSTL::RunImpl() { + const auto &input = GetInput(); + std::size_t size = input.size(); + + if (size <= 1) { + GetOutput() = input; + return true; + } + + std::vector res = input; + int n = static_cast(size); + int numthreads = GetNumThreads(n); + + std::vector chunks; + SetupChunks(chunks, n, numthreads); + + for (auto &chunk : chunks) { + chunk.ptr = res.data() + chunk.left; + } + + ParallelSortChunks(res, chunks); + + for (int step = 1; static_cast(chunks.size()) > 1; step *= 2) { + int pack = static_cast(chunks.size()); + BatcherMergeLevel(step, chunks, n / numthreads, 32); + + if ((pack / 2) - 1 == 0) { + BatcherMergeBlocksStep(chunks[0].ptr, chunks[0].size, + chunks.back().ptr, chunks.back().size); + chunks[0].right = chunks.back().right; + chunks.resize(1); + } else if ((pack / 2) % 2 != 0) { + size_t idx1 = static_cast(2 * step) * + static_cast((pack / 2) - 2); + size_t idx2 = idx1 + static_cast(step); + BatcherMergeBlocksStep(chunks[idx1].ptr, chunks[idx1].size, + chunks[idx2].ptr, chunks[idx2].size); + chunks.erase(chunks.begin() + static_cast(idx2)); + } else { + chunks.resize(pack / 2); + } + } + + GetOutput() = std::move(res); + return true; +} diff --git a/tasks/krasnopevtseva_v_hoare_batcher_sort/tbb/report.md b/tasks/krasnopevtseva_v_hoare_batcher_sort/tbb/report.md new file mode 100644 index 0000000000..66b40c46bb --- /dev/null +++ b/tasks/krasnopevtseva_v_hoare_batcher_sort/tbb/report.md @@ -0,0 +1,326 @@ +# Гибридная сортировка Хоара-Бэтчера (Quick Batcher Sort) + +- Студент: Краснопевцева Вероника Дмитриевна, группа 3823Б1ПМоп3 +- Технология: TBB +- Вариант: 14 + +## 1. Введение + +Сортировка массивов данных — одна из фундаментальных задач в области +программирования. Быстрая сортировка Хоара известна своей эффективностью +в среднем случае, однако её производительность может деградировать при +неудачном выборе опорного элемента. Сортировка Бэтчера представляет собой +алгоритм сортировки слиянием с хорошими свойствами параллелизации, но +может быть избыточна для небольших массивов. + +Целью данной работы является реализация параллельного гибридного +алгоритма с использованием библиотеки Intel Threading Building Blocks +(TBB), сочетающего адаптивный итеративный QuickSort с последующим +слиянием Бэтчера для улучшения общей производительности. Ожидаемый +результат — устойчивая работа алгоритма на массивах различных размеров с +использованием оптимизаций, таких как сортировка вставками для малых +подмассивов и эффективное параллельное слияние. + +## 2. Постановка задачи + +### Формальное определение + +Необходимо реализовать параллельный алгоритм сортировки массива целых +чисел на основе комбинации алгоритмов Хоара и Бэтчера с использованием +библиотеки Intel TBB. + +### Входные данные + +- Вектор целых чисел `std::vector` произвольной длины. + +### Выходные данные + +- Отсортированный по неубыванию вектор целых чисел. + +### Ограничения + +- Алгоритм должен корректно обрабатывать массивы любого размера. +- Для малых подмассивов используется сортировка вставками. +- Параллельная обработка выполняется на этапе начальной сортировки + подмассивов и на этапе слияния. +- Количество потоков определяется аппаратными возможностями системы и + контролируется через `tbb::global_control`. + +## 3. Базовый алгоритм (последовательная версия) + +### 3.1 Быстрая сортировка (итеративная версия) + +Используется нерекурсивная реализация через стек: + +1. В стек помещается пара `(left, right)` для всего массива. +2. Пока стек не пуст: + - Извлекается диапазон `[l, r]`. + - Если `l >= r` — пропуск. + - Если размер диапазона небольшой - вызывается сортировка вставками. + - Выбирается опорный элемент (последний элемент массива). + - Выполняется разбиение Хоара. + - Меньший поддиапазон помещается в стек первым (для балансировки). + +### 3.2 Сортировка вставками + +Для малых подмассивов (размер < 16) применяется классическая сортировка +вставками, так как она показывает хорошие результаты на почти +отсортированных данных и малых размерах. + +### 3.3 Слияние Бэтчера + +После завершения быстрой сортировки выполняется слияние отсортированных +блоков с использованием алгоритма Бэтчера с последовательным применением +`std::inplace_merge`. + +## 4. Схема параллелизации + +### 4.1 Общая стратегия + +Параллелизация реализована по принципу "разделяй и властвуй" с +использованием библиотеки Intel TBB: + +1. **Определение количества потоков**: Автоматическое определение + аппаратной конкуренции через `std::thread::hardware_concurrency()` с + настройкой глобального контроля TBB через `tbb::global_control`. +2. **Разбиение данных**: Исходный массив разделяется на `N` блоков, + где `N` — количество доступных потоков. +3. **Параллельная сортировка**: Каждый блок сортируется с + использованием `tbb::parallel_for` с `simple_partitioner`. +4. **Параллельное слияние**: Отсортированные блоки попарно сливаются + с использованием иерархической схемы Бэтчера, где каждая пара + сливается параллельно через `tbb::parallel_for`. + +### 4.2 Распределение данных + +- Размер блока для каждого потока: `base_size = n / numthreads` +- Остаток от деления добавляется к последнему блоку: + `last_size = base_size + remainder` +- Каждый поток получает указатель на начало своего блока и его размер +- TBB-планировщик автоматически распределяет итерации по доступным + потокам + +### 4.3 Коммуникационная схема + +- На этапе сортировки: блоки сортируются независимо, обмен данными + отсутствует. +- На этапе слияния: выполняется попарное слияние блоков с + использованием `std::inplace_merge`. +- Слияние организовано в виде иерархического дерева (парное слияние + на каждом уровне). +- TBB обеспечивает автоматическую балансировку нагрузки на этапе + слияния. + +### 4.4 Синхронизация + +- Используется `tbb::parallel_for` с явным указанием + `simple_partitioner`. +- TBB управляет созданием потоков и распределением работы. +- Отсутствие гонок данных гарантируется тем, что каждый поток + работает со своим выделенным диапазоном памяти. +- На этапе слияния используется условное распараллеливание через + параметр `par_if_greater` (порог 32). +- Глобальный контроль параллелизма через `tbb::global_control` + ограничивает максимальное количество потоков. + +## 5. Детали реализации + +### 5.1 Структура кода + +- **Файл**: `krasnopevtseva_v_hoare_batcher_sort/tbb/include/ops_tbb.hpp`, + `krasnopevtseva_v_hoare_batcher_sort/tbb/ops_tbb.cpp` +- **Пространство имён**: `krasnopevtseva_v_hoare_batcher_sort` +- **Класс**: `KrasnopevtsevaVHoareBatcherSortTBB` + +### 5.2 Основные методы + +|Метод|Назначение| +|-|-| +|`ValidationImpl()`|Проверка входных данных| +|`PreProcessingImpl()`|Инициализация выходного вектора| +|`RunImpl()`|Запуск параллельной сортировки| +|`QuickSort()`|Итеративная быстрая сортировка| +|`Partition()`|Разбиение Хоара| +|`InsertionSort()`|Сортировка вставками| +|`BatcherMergeBlocksStep()`|Слияние двух блоков| +|`BatcherMerge()`|Параллельное слияние| + +### 5.3 Важные особенности + +- **Глобальный контроль параллелизма**: Используется `tbb::global_control` + для установки максимального количества потоков на основе аппаратной + конкуренции. +- **Планировщик TBB**: Применяется `tbb::simple_partitioner()` для + минимальных накладных расходов при распределении итераций. +- **Выбор опорного элемента**: Используется последний элемент массива, + что упрощает реализацию. +- **Порог сортировки вставками**: 16 элементов. +- **Условное распараллеливание слияния**: Слияние выполняется параллельно + только если `(thread_input_size / step) > 32`. +- **Итеративный QuickSort**: Избегает переполнения стека вызовов. + +### 5.4 Использование памяти + +- Исходный массив сортируется на месте. +- Для отслеживания границ блоков используются массивы `pointers` и + `sizes`. +- TBB управляет созданием и распределением задач с минимальными + накладными расходами. +- Дополнительная память: O(numthreads) для хранения указателей на + блоки. + +## 6. Окружение и тестирование + +### 6.1 Аппаратное и программное обеспечение + +- **Процессор**: AMD Ryzen 7 5700X 8-Core Processor +- **ОЗУ**: 32.0 ГБ +- **ОС**: Windows 11 Pro 25H2 +- **Набор инструментов**: DevContainer с компилятором GCC + (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 +- **Тип сборки**: Release +- **Фреймворк тестирования**: Google Test (gtest) +- **Технология**: TBB + +### 6.2 Тестовые данные и верификация + +#### Функциональное тестирование + +Для проверки базовой корректности алгоритма был разработан набор из 6 +тестовых сценариев, покрывающих различные типы входных данных: + +|№|Тип массива|Описание|Размер| +|-|-|-|-:| +|1|`sorted_array`|Уже отсортированный по возрастанию массив|16| +|2|`default_array`|Случайный массив с произвольным порядком|20| +|3|`short_array`|Короткий массив минимального размера|4| +|4|`long_array`|Длинный массив с повторениями|50| +|5|`two_number_array`|Массив только из двух значений (10 и 20)|9| +|6|`end_to_begin_array`|Массив, отсортированный по убыванию|24| + +#### Верификация результата + +Для каждого тестового набора проверялось выполнение основного инварианта +сортировки: + +- Для всех индексов `i` от 0 до `size-2` выполняется условие + `output[i] <= output[i+1]` + +Дополнительно проводилось сравнение с эталонной реализацией `std::sort` +на тех же входных данных для всех тестовых случаев. + +#### Тестирование производительности + +Для оценки производительности был реализован отдельный тестовый набор, +генерирующий массив из **100 000** случайных целых чисел в диапазоне +`[0, 100 000 000]`. Генерация выполняется с использованием генератора +псевдослучайных чисел `std::mt19937` для обеспечения воспроизводимости +результатов. + +**Результаты корректности:** Все 6 функциональных тестов успешно пройдены +для TBB-версии. Перфоманс-тесты также подтверждают корректную сортировку +массива из 100 000 элементов. + +## 7. Результаты и обсуждение + +### 7.1 Корректность + +Корректность параллельной реализации на TBB подтверждена: + +- Сравнением результатов с последовательной версией на идентичных + входных данных. +- Проверкой инварианта отсортированности для всех выходных массивов. +- Успешным прохождением всех функциональных тестов. + +### 7.2 Производительность + +Результаты для массива из **100 000** случайных целых чисел: + +|Реализация|Потоков|Время, мс|Ускорение|Эффективность| +|-|-:|-:|-:|-:| +|SEQ|1|32.0|1.00|N/A| +|TBB|8|22.0|1.45|18.2%| + +*Примечание: Количество потоков определяется автоматически через +`std::thread::hardware_concurrency()`.* + +### 7.3 Анализ производительности + +#### Наблюдаемые эффекты + +- **Ускорение TBB версии около 1.45x** при использовании 8 потоков. + Это соответствует эффективности около 18.2%. +- Для массива из 100 000 элементов время выполнения составляет 22 мс, + что в 1.45 раза быстрее последовательной версии (32 мс). + +#### Факторы, влияющие на производительность TBB + +|Фактор|Влияние на TBB версию| +|-|-| +|Планировщик задач|Умеренные накладные расходы| +|simple_partitioner|Минимизирует накладные расходы| +|Глобальный контроль потоков|Позволяет ограничить параллелизм| +|Параллельная сортировка|Хорошее ускорение| +|Параллельное слияние|Умеренное ускорение| + +## 8. Заключения + +### Основные выводы + +1. Разработан параллельный гибридный алгоритм сортировки с использованием + Intel TBB, сочетающий QuickSort, InsertionSort и BatcherMerge. +2. Алгоритм корректен и демонстрирует ускорение до 1.45x на 8 потоках + для массива из 100 000 элементов. +3. Сортировка вставками для подмассивов размера < 16 даёт выигрыш по + сравнению с рекурсивным QuickSort. +4. Условное распараллеливание слияния с использованием TBB позволяет + адаптироваться под размер обрабатываемых данных. + +## Приложение + +```cpp +bool KrasnopevtsevaVHoareBatcherSortTBB::RunImpl() { + const auto &input = GetInput(); + std::size_t size = input.size(); + + if (size <= 1) { + GetOutput() = input; + return true; + } + + int numthreads = static_cast(std::thread::hardware_concurrency()); + static tbb::global_control control( + tbb::global_control::max_allowed_parallelism, numthreads); + + std::vector res = input; + int n = static_cast(size); + numthreads = std::min(n, numthreads); + + int thread_input_size = n / numthreads; + int thread_input_remainder_size = n % numthreads; + + std::vector pointers(numthreads); + std::vector sizes(numthreads); + for (int i = 0; i < numthreads; ++i) { + auto offset = static_cast(i) * + static_cast(thread_input_size); + pointers[i] = res.data() + offset; + sizes[i] = thread_input_size; + } + sizes[sizes.size() - 1] += thread_input_remainder_size; + + tbb::parallel_for( + tbb::blocked_range(0, numthreads, 1), + [&](const tbb::blocked_range &r) { + for (int i = r.begin(); i < r.end(); ++i) { + int left = static_cast(pointers[i] - res.data()); + int right = left + sizes[i] - 1; + QuickSort(res, left, right); + } + }, + tbb::simple_partitioner()); + + BatcherMerge(thread_input_size, pointers, sizes, 32); + GetOutput() = std::move(res); + return true; +} diff --git a/tasks/mityaeva_radix/all/include/ops_all.hpp b/tasks/mityaeva_radix/all/include/ops_all.hpp deleted file mode 100644 index 61d93b38ef..0000000000 --- a/tasks/mityaeva_radix/all/include/ops_all.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "mityaeva_radix/common/include/common.hpp" -#include "task/include/task.hpp" - -namespace mityaeva_radix { - -class MityaevaRadixAll : public BaseTask { - public: - static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { - return ppc::task::TypeOfTask::kALL; - } - explicit MityaevaRadixAll(const InType &in); - - private: - bool ValidationImpl() override; - bool PreProcessingImpl() override; - bool RunImpl() override; - bool PostProcessingImpl() override; -}; - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/all/src/ops_all.cpp b/tasks/mityaeva_radix/all/src/ops_all.cpp deleted file mode 100644 index eba0af8939..0000000000 --- a/tasks/mityaeva_radix/all/src/ops_all.cpp +++ /dev/null @@ -1,157 +0,0 @@ -#include "mityaeva_radix/all/include/ops_all.hpp" - -#include - -#include -#include -#include -#include - -#include "mityaeva_radix/common/include/common.hpp" -#include "mityaeva_radix/omp/include/sorter_omp.hpp" - -namespace mityaeva_radix { - -namespace { - -void ComputeChunkParams(size_t total_size, int mpi_size, std::vector &chunk_sizes, - std::vector &offsets) { - size_t base_chunk = total_size / mpi_size; - size_t remainder = total_size % mpi_size; - - for (int i = 0; i < mpi_size; ++i) { - chunk_sizes[i] = base_chunk + (std::cmp_less(i, remainder) ? 1 : 0); - offsets[i] = (i == 0) ? 0 : offsets[i - 1] + chunk_sizes[i - 1]; - } -} - -void ScatterData(const std::vector &array, std::vector &local_data, - const std::vector &chunk_sizes, const std::vector &offsets) { - int mpi_size = static_cast(chunk_sizes.size()); - std::vector send_counts(mpi_size); - std::vector send_displs(mpi_size); - - for (int i = 0; i < mpi_size; ++i) { - send_counts[i] = static_cast(chunk_sizes[i]); - send_displs[i] = static_cast(offsets[i]); - } - - MPI_Scatterv(array.data(), send_counts.data(), send_displs.data(), MPI_DOUBLE, local_data.data(), - static_cast(local_data.size()), MPI_DOUBLE, 0, MPI_COMM_WORLD); -} - -std::vector MergeTwoSorted(const std::vector &left, const std::vector &right) { - std::vector result(left.size() + right.size()); - size_t i = 0; - size_t j = 0; - size_t k = 0; - - while (i < left.size() && j < right.size()) { - if (left[i] <= right[j]) { - result[k++] = left[i++]; - } else { - result[k++] = right[j++]; - } - } - - while (i < left.size()) { - result[k++] = left[i++]; - } - - while (j < right.size()) { - result[k++] = right[j++]; - } - - return result; -} - -void ExchangeAndMerge(int partner, std::vector &merged_data) { - size_t my_size = merged_data.size(); - size_t partner_size = 0; - - MPI_Sendrecv(&my_size, 1, MPI_UNSIGNED_LONG, partner, 0, &partner_size, 1, MPI_UNSIGNED_LONG, partner, 0, - MPI_COMM_WORLD, MPI_STATUS_IGNORE); - - std::vector partner_data(partner_size); - MPI_Sendrecv(merged_data.data(), static_cast(my_size), MPI_DOUBLE, partner, 1, partner_data.data(), - static_cast(partner_size), MPI_DOUBLE, partner, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE); - - merged_data = MergeTwoSorted(merged_data, partner_data); -} - -void ParallelHypercubeMerge(std::vector &merged_data, int mpi_rank, int mpi_size) { - int step = 1; - - while (step < mpi_size) { - int partner = mpi_rank ^ step; - - if (partner < mpi_size) { - ExchangeAndMerge(partner, merged_data); - } - - step <<= 1; - } -} - -} // namespace - -MityaevaRadixAll::MityaevaRadixAll(const InType &in) { - SetTypeOfTask(GetStaticTypeOfTask()); - GetInput() = in; - GetOutput() = {}; -} - -bool MityaevaRadixAll::ValidationImpl() { - return !GetInput().empty(); -} - -bool MityaevaRadixAll::PreProcessingImpl() { - return true; -} - -bool MityaevaRadixAll::RunImpl() { - int mpi_rank = 0; - int mpi_size = 1; - - MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank); - MPI_Comm_size(MPI_COMM_WORLD, &mpi_size); - - auto &array = GetInput(); - size_t total_size = array.size(); - - if (total_size == 0) { - GetOutput() = array; - return true; - } - - std::vector chunk_sizes(mpi_size); - std::vector offsets(mpi_size); - ComputeChunkParams(total_size, mpi_size, chunk_sizes, offsets); - - std::vector local_data(chunk_sizes[mpi_rank]); - ScatterData(array, local_data, chunk_sizes, offsets); - - SorterOmp::Sort(local_data); - - if (mpi_size == 1) { - GetOutput() = local_data; - return true; - } - - std::vector merged_data = std::move(local_data); - ParallelHypercubeMerge(merged_data, mpi_rank, mpi_size); - - if (mpi_rank == 0) { - GetOutput() = merged_data; - } else { - GetOutput() = {}; - } - - return true; -} - -bool MityaevaRadixAll::PostProcessingImpl() { - return true; -} - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/common/include/common.hpp b/tasks/mityaeva_radix/common/include/common.hpp deleted file mode 100644 index cd883aa12e..0000000000 --- a/tasks/mityaeva_radix/common/include/common.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include -#include - -#include "task/include/task.hpp" - -namespace mityaeva_radix { - -using InType = std::vector; -using OutType = std::vector; -using TestType = std::size_t; -using BaseTask = ppc::task::Task; - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/common/include/test_generator.hpp b/tasks/mityaeva_radix/common/include/test_generator.hpp deleted file mode 100644 index 9d5aa5efd7..0000000000 --- a/tasks/mityaeva_radix/common/include/test_generator.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace mityaeva_radix { - -inline std::vector GenerateTest(std::size_t length, std::size_t seed) { - std::vector input; - input.reserve(length); - std::mt19937_64 rng(seed); - std::uniform_real_distribution dist(0.0, 1.0); - for (size_t i = 0; i < length; i++) { - input.push_back(dist(rng) - 0.5); - } - - return input; -}; - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/info.json b/tasks/mityaeva_radix/info.json deleted file mode 100644 index c837fc9ec4..0000000000 --- a/tasks/mityaeva_radix/info.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "student": { - "first_name": "Дарья", - "group_number": "3823Б1ФИ2", - "last_name": "Митяева", - "middle_name": "Викторовна", - "task_number": "19" - } -} diff --git a/tasks/mityaeva_radix/omp/include/ops_omp.hpp b/tasks/mityaeva_radix/omp/include/ops_omp.hpp deleted file mode 100644 index de0ac0c08e..0000000000 --- a/tasks/mityaeva_radix/omp/include/ops_omp.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "mityaeva_radix/common/include/common.hpp" -#include "task/include/task.hpp" - -namespace mityaeva_radix { - -class MityaevaRadixOmp : public BaseTask { - public: - static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { - return ppc::task::TypeOfTask::kOMP; - } - explicit MityaevaRadixOmp(const InType &in); - - private: - bool ValidationImpl() override; - bool PreProcessingImpl() override; - bool RunImpl() override; - bool PostProcessingImpl() override; -}; - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/omp/include/sorter_omp.hpp b/tasks/mityaeva_radix/omp/include/sorter_omp.hpp deleted file mode 100644 index f86113a387..0000000000 --- a/tasks/mityaeva_radix/omp/include/sorter_omp.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include - -namespace mityaeva_radix { -class SorterOmp { - private: - static uint64_t DoubleToSortable(uint64_t x); - - static uint64_t SortableToDouble(uint64_t x); - - static void CountingPass(std::vector *current, std::vector *next, int shift, int radix, - int num_threads, size_t data_size); - - public: - static void Sort(std::vector &data); -}; -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/omp/src/ops_omp.cpp b/tasks/mityaeva_radix/omp/src/ops_omp.cpp deleted file mode 100644 index dcd9e0af3a..0000000000 --- a/tasks/mityaeva_radix/omp/src/ops_omp.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "mityaeva_radix/omp/include/ops_omp.hpp" - -#include "mityaeva_radix/common/include/common.hpp" -#include "mityaeva_radix/omp/include/sorter_omp.hpp" - -namespace mityaeva_radix { - -MityaevaRadixOmp::MityaevaRadixOmp(const InType &in) { - SetTypeOfTask(GetStaticTypeOfTask()); - GetInput() = in; - GetOutput() = {}; -} - -bool MityaevaRadixOmp::ValidationImpl() { - return !GetInput().empty(); -} - -bool MityaevaRadixOmp::PreProcessingImpl() { - return true; -} - -bool MityaevaRadixOmp::RunImpl() { - auto &array = GetInput(); - SorterOmp::Sort(array); - GetOutput() = array; - return true; -} - -bool MityaevaRadixOmp::PostProcessingImpl() { - return true; -} - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/omp/src/sorter_omp.cpp b/tasks/mityaeva_radix/omp/src/sorter_omp.cpp deleted file mode 100644 index 054a460a0d..0000000000 --- a/tasks/mityaeva_radix/omp/src/sorter_omp.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include "mityaeva_radix/omp/include/sorter_omp.hpp" - -#include - -#include -#include -#include -#include -#include - -#include "util/include/util.hpp" - -namespace mityaeva_radix { - -uint64_t SorterOmp::DoubleToSortable(uint64_t x) { - if ((x & 0x8000000000000000ULL) != 0U) { - return ~x; - } - return x | 0x8000000000000000ULL; -} - -uint64_t SorterOmp::SortableToDouble(uint64_t x) { - if ((x & 0x8000000000000000ULL) != 0U) { - return x & 0x7FFFFFFFFFFFFFFFULL; - } - return ~x; -} - -void SorterOmp::CountingPass(std::vector *current, std::vector *next, int shift, int radix, - int num_threads, size_t data_size) { - std::vector> thread_counters(num_threads, std::vector(radix, 0)); - -#pragma omp parallel default(none) shared(current, shift, radix, thread_counters, data_size, num_threads) - { - int thread_id = omp_get_thread_num(); - size_t chunk_size = data_size / static_cast(num_threads); - size_t start = static_cast(thread_id) * chunk_size; - size_t end = (thread_id == num_threads - 1) ? data_size : start + chunk_size; - - auto &local_counters = thread_counters[thread_id]; - - for (size_t i = start; i < end; i++) { - int digit = static_cast(((*current)[i] >> static_cast(shift)) & static_cast(radix - 1)); - local_counters[digit]++; - } - } - - std::vector prefix_sums(static_cast(radix * num_threads), 0); - - int total = 0; - for (int digit = 0; digit < radix; digit++) { - int digit_sum = 0; - for (int thread_idx = 0; thread_idx < num_threads; thread_idx++) { - prefix_sums[(thread_idx * radix) + digit] = total + digit_sum; - digit_sum += thread_counters[thread_idx][digit]; - } - total += digit_sum; - } - -#pragma omp parallel default(none) shared(current, next, shift, radix, prefix_sums, data_size, num_threads) - { - int thread_id = omp_get_thread_num(); - size_t chunk_size = data_size / static_cast(num_threads); - size_t start = static_cast(thread_id) * chunk_size; - size_t end = (thread_id == num_threads - 1) ? data_size : start + chunk_size; - - std::vector local_pos(radix, 0); - for (int digit = 0; digit < radix; digit++) { - local_pos[digit] = prefix_sums[(thread_id * radix) + digit]; - } - - for (size_t i = start; i < end; i++) { - int digit = static_cast(((*current)[i] >> static_cast(shift)) & static_cast(radix - 1)); - auto pos = static_cast(local_pos[digit]++); - (*next)[pos] = (*current)[i]; - } - } -} - -void SorterOmp::Sort(std::vector &data) { - if (data.size() <= 1) { - return; - } - - int num_threads = ppc::util::GetNumThreads(); - omp_set_num_threads(num_threads); - - std::vector temp(data.size()); - std::vector as_uint(data.size()); - -#pragma omp parallel for default(none) shared(data, as_uint, num_threads) - for (size_t i = 0; i < data.size(); i++) { - uint64_t bits = 0; - std::memcpy(&bits, &data[i], sizeof(double)); - as_uint[i] = DoubleToSortable(bits); - } - - const int bits_per_pass = 8; - const int radix = 1 << bits_per_pass; - const int passes = static_cast(sizeof(uint64_t) * 8 / bits_per_pass); - - std::vector uint_temp(data.size()); - std::vector *current = &as_uint; - std::vector *next = &uint_temp; - - for (int pass = 0; pass < passes; pass++) { - int shift = pass * bits_per_pass; - - CountingPass(current, next, shift, radix, num_threads, data.size()); - - std::swap(current, next); - } - -#pragma omp parallel for default(none) shared(data, current, as_uint, uint_temp) - for (size_t i = 0; i < data.size(); i++) { - uint64_t bits = 0; - if (current == &as_uint) { - bits = SortableToDouble(as_uint[i]); - } else { - bits = SortableToDouble(uint_temp[i]); - } - std::memcpy(&data[i], &bits, sizeof(double)); - } -} - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/seq/include/ops_seq.hpp b/tasks/mityaeva_radix/seq/include/ops_seq.hpp deleted file mode 100644 index 93ac3c5320..0000000000 --- a/tasks/mityaeva_radix/seq/include/ops_seq.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "mityaeva_radix/common/include/common.hpp" -#include "task/include/task.hpp" - -namespace mityaeva_radix { - -class MityaevaRadixSeq : public BaseTask { - public: - static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { - return ppc::task::TypeOfTask::kSEQ; - } - explicit MityaevaRadixSeq(const InType &in); - - private: - bool ValidationImpl() override; - bool PreProcessingImpl() override; - bool RunImpl() override; - bool PostProcessingImpl() override; -}; - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/seq/include/sorter_seq.hpp b/tasks/mityaeva_radix/seq/include/sorter_seq.hpp deleted file mode 100644 index 39c3d5f79c..0000000000 --- a/tasks/mityaeva_radix/seq/include/sorter_seq.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - -namespace mityaeva_radix { -class SorterSeq { - public: - static void CountingSortAsc(std::vector &source, std::vector &destination, int byte); - static void CountingSortDesc(std::vector &source, std::vector &destination, int byte); - static void LSDSortDouble(std::vector &inp); -}; -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/seq/src/ops_seq.cpp b/tasks/mityaeva_radix/seq/src/ops_seq.cpp deleted file mode 100644 index 2717a7bc84..0000000000 --- a/tasks/mityaeva_radix/seq/src/ops_seq.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "mityaeva_radix/seq/include/ops_seq.hpp" - -#include "mityaeva_radix/common/include/common.hpp" -#include "mityaeva_radix/seq/include/sorter_seq.hpp" - -namespace mityaeva_radix { - -MityaevaRadixSeq::MityaevaRadixSeq(const InType &in) { - SetTypeOfTask(GetStaticTypeOfTask()); - GetInput() = in; - GetOutput() = {}; -} - -bool MityaevaRadixSeq::ValidationImpl() { - return !GetInput().empty(); -} - -bool MityaevaRadixSeq::PreProcessingImpl() { - return true; -} - -bool MityaevaRadixSeq::RunImpl() { - auto &array = GetInput(); - SorterSeq::LSDSortDouble(array); - GetOutput() = array; - return true; -} - -bool MityaevaRadixSeq::PostProcessingImpl() { - return true; -} - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/settings.json b/tasks/mityaeva_radix/settings.json deleted file mode 100644 index 0be0208fc6..0000000000 --- a/tasks/mityaeva_radix/settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "tasks": { - "all": "enabled", - "omp": "enabled", - "seq": "enabled", - "stl": "enabled", - "tbb": "enabled" - }, - "tasks_type": "threads" -} diff --git a/tasks/mityaeva_radix/stl/include/ops_stl.hpp b/tasks/mityaeva_radix/stl/include/ops_stl.hpp deleted file mode 100644 index 9947ee5cd6..0000000000 --- a/tasks/mityaeva_radix/stl/include/ops_stl.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "mityaeva_radix/common/include/common.hpp" -#include "task/include/task.hpp" - -namespace mityaeva_radix { - -class MityaevaRadixStl : public BaseTask { - public: - static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { - return ppc::task::TypeOfTask::kSTL; - } - explicit MityaevaRadixStl(const InType &in); - - private: - bool ValidationImpl() override; - bool PreProcessingImpl() override; - bool RunImpl() override; - bool PostProcessingImpl() override; -}; - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/stl/include/sorter_stl.hpp b/tasks/mityaeva_radix/stl/include/sorter_stl.hpp deleted file mode 100644 index 73f0f6ee71..0000000000 --- a/tasks/mityaeva_radix/stl/include/sorter_stl.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace mityaeva_radix { -class SorterStl { - private: - static uint64_t DoubleToSortable(uint64_t x); - - static uint64_t SortableToDouble(uint64_t x); - - static void CountingPass(std::vector *current, std::vector *next, int shift, int radix, - int num_threads, size_t data_size); - - public: - static void Sort(std::vector &data); -}; -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/stl/src/ops_stl.cpp b/tasks/mityaeva_radix/stl/src/ops_stl.cpp deleted file mode 100644 index 29b0b15177..0000000000 --- a/tasks/mityaeva_radix/stl/src/ops_stl.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "mityaeva_radix/stl/include/ops_stl.hpp" - -#include "mityaeva_radix/common/include/common.hpp" -#include "mityaeva_radix/stl/include/sorter_stl.hpp" - -namespace mityaeva_radix { - -MityaevaRadixStl::MityaevaRadixStl(const InType &in) { - SetTypeOfTask(GetStaticTypeOfTask()); - GetInput() = in; - GetOutput() = {}; -} - -bool MityaevaRadixStl::ValidationImpl() { - return !GetInput().empty(); -} - -bool MityaevaRadixStl::PreProcessingImpl() { - return true; -} - -bool MityaevaRadixStl::RunImpl() { - auto &array = GetInput(); - SorterStl::Sort(array); - GetOutput() = array; - return true; -} - -bool MityaevaRadixStl::PostProcessingImpl() { - return true; -} - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/stl/src/sorter_stl.cpp b/tasks/mityaeva_radix/stl/src/sorter_stl.cpp deleted file mode 100644 index 9508ef37c9..0000000000 --- a/tasks/mityaeva_radix/stl/src/sorter_stl.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "mityaeva_radix/stl/include/sorter_stl.hpp" - -#include -#include -#include -#include -#include -#include - -#include "util/include/util.hpp" - -namespace mityaeva_radix { - -namespace { -template -void ParallelFor(Index start, Index finish, size_t num_threads, Functor f) { - if (num_threads < 2 || (finish - start) < 150) { - f(start, finish, 0); - return; - } - auto portion = (finish - start) / num_threads; - auto remainder = (finish - start) % num_threads; - - std::vector threads; - threads.reserve(num_threads); - for (auto thread_idx = 0UZ; thread_idx < num_threads; thread_idx++) { - auto begin = start + (portion * thread_idx) + std::min(thread_idx, remainder); - auto end = start + ((thread_idx + 1) * portion) + std::min(thread_idx + 1, remainder); - threads.emplace_back(f, begin, end, thread_idx); - } - - for (auto &thread : threads) { - thread.join(); - } -} -} // namespace - -uint64_t SorterStl::DoubleToSortable(uint64_t x) { - if ((x & 0x8000000000000000ULL) != 0U) { - return ~x; - } - return x | 0x8000000000000000ULL; -} - -uint64_t SorterStl::SortableToDouble(uint64_t x) { - if ((x & 0x8000000000000000ULL) != 0U) { - return x & 0x7FFFFFFFFFFFFFFFULL; - } - return ~x; -} - -void SorterStl::CountingPass(std::vector *current, std::vector *next, int shift, int radix, - int num_threads, size_t data_size) { - std::vector> thread_counters(num_threads, std::vector(radix, 0)); - - ParallelFor(0UZ, data_size, num_threads, [&](size_t start, size_t end, size_t thread_idx) { - auto &local_counters = thread_counters[thread_idx]; - for (size_t i = start; i < end; i++) { - int digit = static_cast(((*current)[i] >> static_cast(shift)) & static_cast(radix - 1)); - local_counters[digit]++; - } - }); - - std::vector prefix_sums(static_cast(radix * num_threads), 0); - - int total = 0; - for (int digit = 0; digit < radix; digit++) { - int digit_sum = 0; - for (int thread_idx = 0; thread_idx < num_threads; thread_idx++) { - prefix_sums[(thread_idx * radix) + digit] = total + digit_sum; - digit_sum += thread_counters[thread_idx][digit]; - } - total += digit_sum; - } - - ParallelFor(0UZ, data_size, num_threads, [&](size_t start, size_t end, size_t thread_idx) { - std::vector local_pos(radix, 0); - for (int digit = 0; digit < radix; digit++) { - local_pos[digit] = prefix_sums[(thread_idx * radix) + digit]; - } - - for (size_t i = start; i < end; i++) { - int digit = static_cast(((*current)[i] >> static_cast(shift)) & static_cast(radix - 1)); - auto pos = static_cast(local_pos[digit]++); - (*next)[pos] = (*current)[i]; - } - }); -} - -void SorterStl::Sort(std::vector &data) { - if (data.size() <= 1) { - return; - } - - int num_threads = ppc::util::GetNumThreads(); - std::vector temp(data.size()); - std::vector as_uint(data.size()); - ParallelFor(0UZ, data.size(), num_threads, [&](size_t start, size_t end, size_t) { - for (auto i = start; i < end; i++) { - uint64_t bits = 0; - std::memcpy(&bits, &data[i], sizeof(double)); - as_uint[i] = DoubleToSortable(bits); - } - }); - const int bits_per_pass = 8; - const int radix = 1 << bits_per_pass; - const int passes = static_cast(sizeof(uint64_t) * 8 / bits_per_pass); - - std::vector uint_temp(data.size()); - std::vector *current = &as_uint; - std::vector *next = &uint_temp; - - for (int pass = 0; pass < passes; pass++) { - int shift = pass * bits_per_pass; - CountingPass(current, next, shift, radix, num_threads, data.size()); - std::swap(current, next); - } - - ParallelFor(0UZ, data.size(), num_threads, [&](size_t start, size_t end, size_t) { - for (auto i = start; i < end; i++) { - uint64_t bits = 0; - if (current == &as_uint) { - bits = SortableToDouble(as_uint[i]); - } else { - bits = SortableToDouble(uint_temp[i]); - } - std::memcpy(&data[i], &bits, sizeof(double)); - } - }); -} - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/tbb/include/ops_tbb.hpp b/tasks/mityaeva_radix/tbb/include/ops_tbb.hpp deleted file mode 100644 index 9008edfc5b..0000000000 --- a/tasks/mityaeva_radix/tbb/include/ops_tbb.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "mityaeva_radix/common/include/common.hpp" -#include "task/include/task.hpp" - -namespace mityaeva_radix { - -class MityaevaRadixTbb : public BaseTask { - public: - static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { - return ppc::task::TypeOfTask::kTBB; - } - explicit MityaevaRadixTbb(const InType &in); - - private: - bool ValidationImpl() override; - bool PreProcessingImpl() override; - bool RunImpl() override; - bool PostProcessingImpl() override; -}; - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/tbb/include/sorter_tbb.hpp b/tasks/mityaeva_radix/tbb/include/sorter_tbb.hpp deleted file mode 100644 index 2a326a1fa1..0000000000 --- a/tasks/mityaeva_radix/tbb/include/sorter_tbb.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace mityaeva_radix { -class SorterTbb { - private: - static uint64_t DoubleToSortable(uint64_t x); - - static uint64_t SortableToDouble(uint64_t x); - - static void CountingPass(std::vector *current, std::vector *next, int shift, int radix, - int num_threads, size_t data_size); - - public: - static void Sort(std::vector &data); -}; -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/tbb/src/ops_tbb.cpp b/tasks/mityaeva_radix/tbb/src/ops_tbb.cpp deleted file mode 100644 index 62d6509d7f..0000000000 --- a/tasks/mityaeva_radix/tbb/src/ops_tbb.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "mityaeva_radix/tbb/include/ops_tbb.hpp" - -#include "mityaeva_radix/common/include/common.hpp" -#include "mityaeva_radix/tbb/include/sorter_tbb.hpp" - -namespace mityaeva_radix { - -MityaevaRadixTbb::MityaevaRadixTbb(const InType &in) { - SetTypeOfTask(GetStaticTypeOfTask()); - GetInput() = in; - GetOutput() = {}; -} - -bool MityaevaRadixTbb::ValidationImpl() { - return !GetInput().empty(); -} - -bool MityaevaRadixTbb::PreProcessingImpl() { - return true; -} - -bool MityaevaRadixTbb::RunImpl() { - auto &array = GetInput(); - SorterTbb::Sort(array); - GetOutput() = array; - return true; -} - -bool MityaevaRadixTbb::PostProcessingImpl() { - return true; -} - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/tbb/src/sorter_tbb.cpp b/tasks/mityaeva_radix/tbb/src/sorter_tbb.cpp deleted file mode 100644 index 4658aa0c0d..0000000000 --- a/tasks/mityaeva_radix/tbb/src/sorter_tbb.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "mityaeva_radix/tbb/include/sorter_tbb.hpp" - -#include -#include -#include -#include -#include - -#include "oneapi/tbb/parallel_for.h" -#include "oneapi/tbb/partitioner.h" -#include "util/include/util.hpp" - -namespace mityaeva_radix { - -uint64_t SorterTbb::DoubleToSortable(uint64_t x) { - if ((x & 0x8000000000000000ULL) != 0U) { - return ~x; - } - return x | 0x8000000000000000ULL; -} - -uint64_t SorterTbb::SortableToDouble(uint64_t x) { - if ((x & 0x8000000000000000ULL) != 0U) { - return x & 0x7FFFFFFFFFFFFFFFULL; - } - return ~x; -} - -void SorterTbb::CountingPass(std::vector *current, std::vector *next, int shift, int radix, - int num_threads, size_t data_size) { - std::vector> thread_counters(num_threads, std::vector(radix, 0)); - - tbb::parallel_for(0, num_threads, [&](int thread_id) { - size_t chunk_size = data_size / static_cast(num_threads); - size_t start = static_cast(thread_id) * chunk_size; - size_t end = (thread_id == num_threads - 1) ? data_size : start + chunk_size; - - auto &local_counters = thread_counters[thread_id]; - - for (size_t i = start; i < end; i++) { - int digit = static_cast(((*current)[i] >> static_cast(shift)) & static_cast(radix - 1)); - local_counters[digit]++; - } - }, tbb::static_partitioner()); - - std::vector prefix_sums(static_cast(radix * num_threads), 0); - - int total = 0; - for (int digit = 0; digit < radix; digit++) { - int digit_sum = 0; - for (int thread_idx = 0; thread_idx < num_threads; thread_idx++) { - prefix_sums[(thread_idx * radix) + digit] = total + digit_sum; - digit_sum += thread_counters[thread_idx][digit]; - } - total += digit_sum; - } - - tbb::parallel_for(0, num_threads, [&](int thread_id) { - size_t chunk_size = data_size / static_cast(num_threads); - size_t start = static_cast(thread_id) * chunk_size; - size_t end = (thread_id == num_threads - 1) ? data_size : start + chunk_size; - - std::vector local_pos(radix, 0); - for (int digit = 0; digit < radix; digit++) { - local_pos[digit] = prefix_sums[(thread_id * radix) + digit]; - } - - for (size_t i = start; i < end; i++) { - int digit = static_cast(((*current)[i] >> static_cast(shift)) & static_cast(radix - 1)); - auto pos = static_cast(local_pos[digit]++); - (*next)[pos] = (*current)[i]; - } - }, tbb::static_partitioner()); -} - -void SorterTbb::Sort(std::vector &data) { - if (data.size() <= 1) { - return; - } - - int num_threads = ppc::util::GetNumThreads(); - std::vector temp(data.size()); - std::vector as_uint(data.size()); - - tbb::parallel_for(tbb::blocked_range(0, data.size()), - [&data, &as_uint](const tbb::blocked_range &range) { - for (auto i = range.begin(); i < range.end(); i++) { - uint64_t bits = 0; - std::memcpy(&bits, &data[i], sizeof(double)); - as_uint[i] = DoubleToSortable(bits); - } - }); - const int bits_per_pass = 8; - const int radix = 1 << bits_per_pass; - const int passes = static_cast(sizeof(uint64_t) * 8 / bits_per_pass); - std::vector uint_temp(data.size()); - std::vector *current = &as_uint; - std::vector *next = &uint_temp; - - for (int pass = 0; pass < passes; pass++) { - int shift = pass * bits_per_pass; - CountingPass(current, next, shift, radix, num_threads, data.size()); - std::swap(current, next); - } - - tbb::parallel_for(tbb::blocked_range(0, data.size()), - [&data, ¤t, &as_uint, &uint_temp](const tbb::blocked_range &range) { - for (auto i = range.begin(); i < range.end(); i++) { - uint64_t bits = 0; - if (current == &as_uint) { - bits = SortableToDouble(as_uint[i]); - } else { - bits = SortableToDouble(uint_temp[i]); - } - std::memcpy(&data[i], &bits, sizeof(double)); - } - }); -} - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/tests/functional/main.cpp b/tasks/mityaeva_radix/tests/functional/main.cpp deleted file mode 100644 index ef2d64e908..0000000000 --- a/tasks/mityaeva_radix/tests/functional/main.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include -#include - -#include -#include -#include -#include -#include - -#include "mityaeva_radix/all/include/ops_all.hpp" -#include "mityaeva_radix/common/include/common.hpp" -#include "mityaeva_radix/common/include/test_generator.hpp" -#include "mityaeva_radix/omp/include/ops_omp.hpp" -#include "mityaeva_radix/seq/include/ops_seq.hpp" -#include "mityaeva_radix/stl/include/ops_stl.hpp" -#include "mityaeva_radix/tbb/include/ops_tbb.hpp" -#include "util/include/func_test_util.hpp" -#include "util/include/util.hpp" - -namespace mityaeva_radix { - -class MityaevaRadixFunc : public ppc::util::BaseRunFuncTests { - public: - static std::string PrintTestParam(const TestType &test_param) { - return std::to_string(test_param); - } - - protected: - void SetUp() override { - auto length = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); - input_data_ = GenerateTest(length, length); - } - - bool CheckTestOutputData(OutType &output_data) final { - return std::ranges::is_sorted(output_data); - } - - InType GetTestInputData() final { - return input_data_; - } - - private: - InType input_data_; -}; - -namespace { - -TEST_P(MityaevaRadixFunc, RadixSortFunc) { - ExecuteTest(GetParam()); -} - -const std::array kTestParam = {1, 2, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100}; - -const auto kTestTasksList = - std::tuple_cat(ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_mityaeva_radix), - ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_mityaeva_radix), - ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_mityaeva_radix), - ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_mityaeva_radix), - ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_mityaeva_radix)); - -const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); - -const auto kPerfTestName = MityaevaRadixFunc::PrintFuncTestName; - -INSTANTIATE_TEST_SUITE_P(Mityaeva, MityaevaRadixFunc, kGtestValues, kPerfTestName); - -} // namespace - -} // namespace mityaeva_radix diff --git a/tasks/mityaeva_radix/tests/performance/main.cpp b/tasks/mityaeva_radix/tests/performance/main.cpp deleted file mode 100644 index cc0b551f11..0000000000 --- a/tasks/mityaeva_radix/tests/performance/main.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include - -#include -#include - -#include "mityaeva_radix/all/include/ops_all.hpp" -#include "mityaeva_radix/common/include/common.hpp" -#include "mityaeva_radix/common/include/test_generator.hpp" -#include "mityaeva_radix/omp/include/ops_omp.hpp" -#include "mityaeva_radix/seq/include/ops_seq.hpp" -#include "mityaeva_radix/stl/include/ops_stl.hpp" -#include "mityaeva_radix/tbb/include/ops_tbb.hpp" -#include "util/include/perf_test_util.hpp" - -namespace mityaeva_radix { - -class MityaevaRadixPerf : public ppc::util::BaseRunPerfTests { - const std::size_t kCount_ = 4'000'000; - InType input_data_; - - void SetUp() override { - input_data_ = GenerateTest(kCount_, kCount_); - } - - bool CheckTestOutputData(OutType &output_data) final { - return std::ranges::is_sorted(output_data, [](auto a, auto b) { return a <= b; }); - } - - InType GetTestInputData() final { - return input_data_; - } -}; - -TEST_P(MityaevaRadixPerf, RadixSortPerf) { - ExecuteTest(GetParam()); -} - -namespace { - -const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks(PPC_SETTINGS_mityaeva_radix); - -const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); - -const auto kPerfTestName = MityaevaRadixPerf::CustomPerfTestName; - -INSTANTIATE_TEST_SUITE_P(RunModeTests, MityaevaRadixPerf, kGtestValues, kPerfTestName); - -} // namespace - -} // namespace mityaeva_radix diff --git a/tasks/urin_o_graham_passage/all/report.md b/tasks/urin_o_graham_passage/all/report.md new file mode 100644 index 0000000000..0cb738a0a6 --- /dev/null +++ b/tasks/urin_o_graham_passage/all/report.md @@ -0,0 +1,131 @@ +# Построение выпуклой оболочки методом Грэхема — ALL + +- Student: Юрин Олеег Игоревич +- Technology: ALL +- Variant: 22 + +## 1. Контекст + +Каталог `all` предназначен для гибридной версии, где обычно описываются межпроцессный уровень и внутрипроцессное +распараллеливание. В текущей реализации задачи фактический вычислительный код использует oneTBB; MPI-вызовов и рабочих +OpenMP-директив в `all/src/ops_all.cpp` нет. + +## 2. Постановка задачи + +Постановка совпадает с root report и SEQ baseline: по массиву точек нужно построить выпуклую оболочку методом Грэхема. +Валидация, выходной формат и обработка крайних случаев совпадают с остальными версиями. + +## 3. Базовый алгоритм + +Базовая логика: выбрать опорную точку, отсортировать остальные точки по полярному углу, проверить коллинеарность, +построить оболочку стековым проходом. Сложность — `O(n log n)`. + +## 4. Межпроцессная схема + +В текущем `all/src/ops_all.cpp` межпроцессная схема не реализована: + +| Элемент гибридной схемы | Текущее состояние | +| --- | --- | +| `MPI_Comm_rank` | отсутствует | +| `MPI_Barrier` | отсутствует | +| `MPI_Bcast` / `MPI_Gather` / `MPI_Reduce` | отсутствуют | +| роли rank-ов | не определены | +| распределение данных между процессами | не выполняется | + +Поэтому конфигурация `ranks × threads` для текущего кода не применяется. Фактически можно считать, что версия работает в +одном процессе, а параллелизм находится только внутри процесса. + +## 5. Внутрипроцессная схема + +Внутри процесса используются TBB-примитивы: + +| Этап | Реализация | +| --- | --- | +| Поиск опорной точки | `tbb::parallel_reduce` | +| Сбор точек без `p0` | `tbb::parallel_for` + `tbb::concurrent_vector` | +| Проверка коллинеарности | `tbb::parallel_for` + `std::atomic` | +| Сортировка | последовательная `std::ranges::sort` | +| Оболочка | последовательный стековый проход | + +Локальные результаты объединяются средствами TBB: редукцией для минимума и потокобезопасным контейнером для точек. + +## 6. Детали реализации + +Файлы: `all/include/ops_all.hpp`, `all/src/ops_all.cpp`. + +MPI-точек синхронизации в коде нет, поэтому `MPI_Barrier`, `Reduce`, `Broadcast` и `Gather` не описываются как +выполненные операции. Потенциальные узкие места совпадают с TBB-версией: последовательная сортировка и стековый проход. + +Иллюстративный фрагмент: + +```cpp +// File: all/src/ops_all.cpp +tbb::parallel_for(tbb::blocked_range(0, points.size()), + [&points, &p0, &other_points_concurrent](const tbb::blocked_range &range) { + for (size_t i = range.begin(); i < range.end(); ++i) { + if (points[i] != p0) { + other_points_concurrent.push_back(points[i]); + } + } +}); +``` + +Фрагмент показывает внутрипроцессный сбор точек через TBB. Запись в общий контейнер безопасна благодаря +`tbb::concurrent_vector`. + +## 7. Проверка корректности + +Корректность ALL проверяется сравнением с SEQ на тех же входных данных. В `tests/functional/main.cpp` один и тот же +набор typed tests запускается для всех пяти реализаций. Для ALL после построения оболочки дополнительно строится эталон +через `UrinOGrahamPassageSEQ`, затем сравниваются размер оболочки и множество вершин. Такой критерий не зависит от +начальной вершины обхода и подтверждает, что ALL возвращает тот же набор точек оболочки, что последовательный baseline. + +Дополнительно результат ALL проверяется на выпуклость. Функциональные сценарии включают малые входы, невалидные входы, +коллинеарные точки, квадрат с внутренней точкой, точки на границе, точки на окружности и шестиугольник с центром. + +Проверка согласованности между rank-ами неприменима, потому что MPI-уровень не реализован. + +| Группа тестов | Примеры тестов | Ожидаемый результат | Сравнение с SEQ | +| --- | --- | --- | --- | +| Невалидные входы | `EmptyInput`, `SinglePoint`, `TwoDistinctPoints`, `AllIdenticalPoints` | `Validation()` возвращает `false` | ALL должен отклонять те же входы, что SEQ. | +| Простые оболочки | `TrianglePoints`, `SquarePoints`, `HexagonWithCenter` | Возвращаются внешние вершины | Сравнивается множество вершин ALL и SEQ. | +| Внутренние и граничные точки | `SquareWithInteriorPoint`, `PointOnBoundary`, `RectangleWithCollinearPoints` | Внутренние и промежуточные точки исключаются | Размер и набор вершин совпадают с SEQ. | +| Коллинеарность | `CollinearPoints`, `VerticalCollinear` | Возвращаются две крайние точки | ALL должен вернуть те же крайние точки, что SEQ. | +| Массовый случай | `LargeRandomSet` | 100 точек на окружности дают оболочку из 100 точек | Проверяется выпуклость и совпадение с SEQ. | + +## 8. Экспериментальная среда + +Команды: + +```bash +cmake -S . -B build -D USE_FUNC_TESTS=ON -D USE_PERF_TESTS=ON -D CMAKE_BUILD_TYPE=Release +cmake --build build --parallel +scripts/run_tests.py --running-type=threads --counts 1 2 4 +scripts/run_tests.py --running-type=performance +``` + +`PPC_NUM_PROC` для текущей ALL-версии не влияет на код, так как MPI не используется. `PPC_NUM_THREADS` может влиять на +окружение запуска, но TBB-конкуренция в коде явно не ограничивается. + +## 9. Результаты + +После обновления `tests/performance/main.cpp` ALL-версия измеряется на тех же входах. В таблице указана медиана по 5 +запускам. Так как MPI-уровень в коде отсутствует, таблица фиксирует `ranks = 1`, а `threads_per_rank` трактуется как +конкуренция TBB runtime на машине с 12 логическими потоками. + +| ranks | threads_per_rank | total_workers | size | time | speedup | efficiency | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | 12 | 12 | 100 | 1.206 | 0.904 | 0.075 | +| 1 | 12 | 12 | 500 | 1.916 | 0.814 | 0.068 | +| 1 | 12 | 12 | 1000 | 2.781 | 0.849 | 0.071 | +| 1 | 12 | 12 | 5000 | 10.618 | 0.902 | 0.075 | +| 1 | 12 | 12 | 10000 | 20.473 | 0.949 | 0.079 | + +Коммуникационные издержки MPI отсутствуют, потому что межпроцессный уровень не реализован. Основной overhead связан с +TBB runtime и последовательными частями алгоритма. + +## 10. Выводы + +Текущая ALL-версия фактически является TBB-подобной внутрипроцессной реализацией, а не полноценной гибридной MPI + +threads схемой. По медиане она близка к TBB и на размере `10000` является самой быстрой среди параллельных версий, но +все равно не обгоняет SEQ. diff --git a/tasks/urin_o_graham_passage/omp/report.md b/tasks/urin_o_graham_passage/omp/report.md new file mode 100644 index 0000000000..861466d62e --- /dev/null +++ b/tasks/urin_o_graham_passage/omp/report.md @@ -0,0 +1,149 @@ +# Построение выпуклой оболочки методом Грэхема — OMP + +- Student: Юрин Олеег Игоревич +- Technology: OMP +- Variant: 22 + +## 1. Контекст + +OpenMP-версия переносит часть подготовительных этапов последовательного алгоритма в параллельные циклы. Цель версии — +проверить, насколько полезно распараллеливать независимые операции над массивом точек, если сортировка и стековый проход +алгоритма Грэхема остаются последовательными. + +## 2. Постановка задачи + +Постановка совпадает с `seq/report.md`: по массиву точек `std::vector` нужно построить выпуклую оболочку. SEQ +считается baseline для корректности и будущего сравнения производительности. + +## 3. Базовый алгоритм + +Последовательная логика: + +1. Найти опорную точку. +2. Отобрать остальные точки. +3. Отсортировать их по полярному углу. +4. Проверить коллинеарность. +5. Построить оболочку стековым проходом. + +Сложность остается `O(n log n)`, потому что сортировка не распараллелена. + +## 4. Схема распараллеливания + +Параллелятся три этапа: + +| Этап | OpenMP-конструкция | Синхронизация | +| --- | --- | --- | +| Поиск опорной точки | `#pragma omp parallel for default(none)` | `critical` при обновлении общего индекса | +| Сбор точек без `p0` | `#pragma omp parallel`, внутри `omp for nowait` | локальные векторы + `critical` при объединении | +| Проверка коллинеарности | `#pragma omp parallel for default(none)` | `atomic write` для флага | + +Переменные `points`, `p0`, `n`, `other_points` объявляются как `shared`. Индексы циклов и локальные контейнеры являются +приватными для потока. `reduction` не используется, потому что в коде нет численного аккумулятора с ассоциативным +объединением; минимум обновляется через `critical`, а булев флаг — через `atomic`. + +Явный `schedule` в директивах не задан, используется распределение итераций OpenMP по умолчанию. В конце `parallel for` +есть неявный барьер; в сборе точек применен `nowait`, а фактическая синхронизация происходит при объединении локальных +векторов. + +Иллюстративный фрагмент: + +```cpp +// File: omp/src/ops_omp.cpp +#pragma omp parallel default(none) shared(points, p0, other_points, n) +{ + std::vector local_points; + +#pragma omp for nowait + for (int i = 0; i < n; ++i) { + if (points[i] != p0) { + local_points.push_back(points[i]); + } + } + +#pragma omp critical + { + other_points.insert(other_points.end(), local_points.begin(), local_points.end()); + } +} +``` + +Каждый поток пишет в свой `local_points`, поэтому во внутреннем цикле гонки нет. Общий `other_points` изменяется только +внутри `critical`. + +## 5. Детали реализации + +Файлы: `omp/include/ops_omp.hpp`, `omp/src/ops_omp.cpp`. + +Относительно SEQ изменены функции поиска опорной точки, подготовки массива остальных точек и проверки коллинеарности. +Сортировка и `BuildConvexHull` оставлены последовательными, потому что порядок точек и состояние стека зависят от +предыдущих шагов. + +Риски гонок устранены через: + +| Общий объект | Защита | +| --- | --- | +| `lowest_index` | `critical` | +| `other_points` | локальные буферы + `critical` | +| `all_collinear` | `atomic write` | + +## 6. Проверка корректности + +Корректность OMP проверяется сравнением с SEQ на тех же входных данных. В `tests/functional/main.cpp` один и тот же +набор typed tests запускается для `UrinOGrahamPassageSEQ`, `UrinOGrahamPassageOMP`, `UrinOGrahamPassageSTL`, +`UrinOGrahamPassageTBB` и `UrinOGrahamPassageALL`. Для OMP после построения оболочки дополнительно строится эталон через +`UrinOGrahamPassageSEQ`, затем сравниваются размер оболочки и множество ее вершин с учетом `Point::operator==` и допуска +`1e-10`. + +Дополнительно результат OMP проверяется на выпуклость функцией `IsConvexHull`: все последовательные тройки вершин не +должны образовывать правый поворот. В `tests/performance/main.cpp` такое же сравнение с SEQ выполняется на размерах +`100`, `500`, `1000`, `5000`, `10000`. + +Функциональный набор включает треугольник, квадрат с внутренней точкой, коллинеарные точки, одинаковые точки, точки на +границе, прямоугольник с точками на сторонах, вертикальную коллинеарность, точки на окружности и шестиугольник с +центром. + +| Группа тестов | Примеры тестов | Ожидаемый результат | Сравнение с SEQ | +| --- | --- | --- | --- | +| Невалидные входы | `EmptyInput`, `SinglePoint`, `TwoDistinctPoints`, `AllIdenticalPoints` | `Validation()` возвращает `false`, выход пустой | Поведение должно совпадать с SEQ. | +| Простые оболочки | `TrianglePoints`, `SquarePoints`, `HexagonWithCenter` | Размер оболочки равен числу внешних вершин | Сравнивается множество вершин OMP и SEQ. | +| Внутренние и граничные точки | `SquareWithInteriorPoint`, `PointOnBoundary`, `RectangleWithCollinearPoints` | Внутренние и промежуточные точки не входят в результат | Размер и набор вершин совпадают с SEQ. | +| Коллинеарность | `CollinearPoints`, `VerticalCollinear` | Возвращаются две крайние точки | OMP должен вернуть те же крайние точки, что SEQ. | +| Массовый случай | `LargeRandomSet` | Все 100 точек на окружности входят в оболочку | Проверяется выпуклость и совпадение с SEQ. | + +## 7. Экспериментальная среда + +Используется та же среда, что для SEQ: Windows 11, Clang 21.1.8, Release-сборка. Число потоков задается runner-ом через +`PPC_NUM_THREADS`, который также связан с `OMP_NUM_THREADS`. Число потоков: 12 + +Команды: + +```bash +cmake -S . -B build -D USE_FUNC_TESTS=ON -D USE_PERF_TESTS=ON -D CMAKE_BUILD_TYPE=Release +cmake --build build --parallel +scripts/run_tests.py --running-type=threads --counts 1 2 4 +scripts/run_tests.py --running-type=performance +``` + +## 8. Результаты + +Данные берутся из `tests/performance/main.cpp`. Тест генерирует детерминированный набор случайных точек с seed `12345` в +диапазоне `[-1000.0, 1000.0]` и измеряет полный pipeline в миллисекундах. В таблице указана медиана по 5 запускам. +Speedup считается относительно SEQ на том же размере, efficiency — как `speedup / 12`, где 12 — число логических потоков +на машине. + +| threads | size | time, ms | speedup | efficiency | hull size | +| ---: | ---: | ---: | ---: | ---: | ---: | +| 12 | 100 | 4.265 | 0.256 | 0.021 | 14 | +| 12 | 500 | 2.044 | 0.763 | 0.064 | 18 | +| 12 | 1000 | 2.961 | 0.798 | 0.066 | 19 | +| 12 | 5000 | 18.232 | 0.525 | 0.044 | 21 | +| 12 | 10000 | 25.996 | 0.747 | 0.062 | 20 | + +Масштабируемость ограничена тем, что сортировка и стековый проход остаются последовательными, а `critical` в поиске +минимума может добавлять накладные расходы. + +## 9. Выводы + +OpenMP-версия корректно переносит независимые подготовительные операции в параллельные области, но на измеренных +размерах она не обогнала SEQ. Основные причины — малая доля распараллеленной работы, критические секции и +последовательная сортировка. diff --git a/tasks/urin_o_graham_passage/report.md b/tasks/urin_o_graham_passage/report.md new file mode 100644 index 0000000000..4b5226a77b --- /dev/null +++ b/tasks/urin_o_graham_passage/report.md @@ -0,0 +1,222 @@ +# Построение выпуклой оболочки методом Грэхема + +- Student: Юрин Олеег Игоревич, 3823Б1ПР4 +- Variant: 22 +- Local reports: seq/report.md, omp/report.md, tbb/report.md, stl/report.md, all/report.md + +## 1. Введение + +Задача построения выпуклой оболочки используется для сравнения разных моделей параллелизма, потому что часть операций +является независимыми проходами по массиву точек, а часть имеет последовательную зависимость. Это позволяет увидеть +границы ускорения: поиск опорной точки и проверки можно распараллелить, но сортировка и стековый проход остаются +основными ограничителями. + +## 2. Единая постановка задачи + +Вход: `InType = std::vector`, где `Point` содержит координаты `double x`, `double y`. + +Выход: `OutType = std::vector` — вершины выпуклой оболочки. + +Ограничения: + +| Случай | Требуемое поведение | +| --- | --- | +| `n < 3` | отказ валидации | +| все точки совпадают | отказ валидации | +| все точки коллинеарны | две крайние точки | +| есть внутренние точки | исключаются из оболочки | + +Критерий корректности: результат является выпуклой оболочкой, а последовательные тройки вершин не образуют правого +поворота. + +## 3. Единая методика эксперимента + +Окружение: + +| Параметр | Значение | +| --- | --- | +| CPU | 12th Gen Intel(R) Core(TM) i5-12450H | +| RAM | 16 GB | +| OS | Windows 11 | +| Compiler | Clang 21.1.8, C++23 | +| Build type | Release | + +Переменные окружения: для потоковых запусков используется `PPC_NUM_THREADS`; для гибридной схемы должен использоваться +`PPC_NUM_PROC`, но текущий `all` не содержит MPI-уровня. + +Данные в текущем performance-тесте генерируются случайно: координаты равномерно выбираются из диапазона `[-1000.0, +1000.0]`. Размеры задач: `100`, `500`, `1000`, `5000`, `10000`. + +Формулы: + +```text +speedup = T_seq / T_parallel +efficiency = speedup / workers +``` + +`tests/performance/main.cpp` обновлен так, чтобы в одном запуске измерялись `SEQ`, `OMP`, `STL`, `TBB` и `ALL` на +одинаковых входных данных. Для таблиц выполнено 5 запусков, используется медиана. Число логических потоков на машине — +12; для efficiency параллельных версий используется нормировка на 12 workers. + +## 4. Сводка корректности + +Функциональные тесты в `tests/functional/main.cpp` оформлены как typed tests и запускают один и тот же набор сценариев +для `SEQ`, `OMP`, `STL`, `TBB` и `ALL`. Они проверяют пустой вход, 1 и 2 точки, коллинеарность, одинаковые точки, +треугольник, квадрат, прямоугольник с точками на сторонах, точку на границе, точки на окружности и шестиугольник с +центром. Для всех параллельных backend-ов результат дополнительно сравнивается с SEQ по множеству вершин оболочки. + +| backend | functional tests | performance sanity check | ограничение | +| --- | --- | --- | --- | +| SEQ | есть, 13 сценариев | есть | baseline | +| OMP | есть, 13 сценариев + сравнение с SEQ | есть | OpenMP | +| STL | есть, 13 сценариев + сравнение с SEQ | есть | `std::thread` | +| TBB | есть, 13 сценариев + сравнение с SEQ | есть | oneTBB | +| ALL | есть, 13 сценариев + сравнение с SEQ | есть | MPI-уровень отсутствует | + +Сами functional-сценарии сведены в таблицу: + +| Группа тестов | Тесты | Назначение | +| --- | --- | --- | +| Невалидные входы | `EmptyInput`, `SinglePoint`, `TwoDistinctPoints`, `AllIdenticalPoints` | Проверить отказ валидации и пустой выход. | +| Базовые фигуры | `TrianglePoints`, `SquarePoints`, `HexagonWithCenter` | Проверить построение оболочки для простых выпуклых фигур. | +| Лишние точки | `SquareWithInteriorPoint`, `PointOnBoundary`, `RectangleWithCollinearPoints` | Проверить исключение внутренних и промежуточных граничных точек. | +| Коллинеарность | `CollinearPoints`, `VerticalCollinear` | Проверить возврат двух крайних точек. | +| Массовый случай | `LargeRandomSet` | Проверить выпуклость на 100 точках окружности. | + +## 5. Агрегированные результаты + +Текущий performance-файл содержит только OMP-измерения полного pipeline. + +| backend | mode | size | workers | time, ms | speedup vs seq | efficiency | notes | +| --- | --- | ---: | --- | ---: | ---: | ---: | --- | +| SEQ | pipeline | 100 | 1 | 1.090 | 1.000 | 1.000 | baseline | +| OMP | pipeline | 100 | 12 | 4.265 | 0.256 | 0.021 | OpenMP | +| STL | pipeline | 100 | 12 | 4.051 | 0.269 | 0.022 | `std::thread` | +| TBB | pipeline | 100 | 12 | 1.727 | 0.631 | 0.053 | oneTBB | +| ALL | pipeline | 100 | 12 | 1.206 | 0.904 | 0.075 | TBB-like | +| SEQ | pipeline | 500 | 1 | 1.559 | 1.000 | 1.000 | baseline | +| OMP | pipeline | 500 | 12 | 2.044 | 0.763 | 0.064 | OpenMP | +| STL | pipeline | 500 | 12 | 3.406 | 0.458 | 0.038 | `std::thread` | +| TBB | pipeline | 500 | 12 | 1.973 | 0.790 | 0.066 | oneTBB | +| ALL | pipeline | 500 | 12 | 1.916 | 0.814 | 0.068 | TBB-like | +| SEQ | pipeline | 1000 | 1 | 2.362 | 1.000 | 1.000 | baseline | +| OMP | pipeline | 1000 | 12 | 2.961 | 0.798 | 0.066 | OpenMP | +| STL | pipeline | 1000 | 12 | 4.262 | 0.554 | 0.046 | `std::thread` | +| TBB | pipeline | 1000 | 12 | 2.920 | 0.809 | 0.067 | oneTBB | +| ALL | pipeline | 1000 | 12 | 2.781 | 0.849 | 0.071 | TBB-like | +| SEQ | pipeline | 5000 | 1 | 9.579 | 1.000 | 1.000 | baseline | +| OMP | pipeline | 5000 | 12 | 18.232 | 0.525 | 0.044 | OpenMP | +| STL | pipeline | 5000 | 12 | 12.285 | 0.780 | 0.065 | `std::thread` | +| TBB | pipeline | 5000 | 12 | 10.442 | 0.917 | 0.076 | oneTBB | +| ALL | pipeline | 5000 | 12 | 10.618 | 0.902 | 0.075 | TBB-like | +| SEQ | pipeline | 10000 | 1 | 19.432 | 1.000 | 1.000 | baseline | +| OMP | pipeline | 10000 | 12 | 25.996 | 0.747 | 0.062 | OpenMP | +| STL | pipeline | 10000 | 12 | 23.069 | 0.842 | 0.070 | `std::thread` | +| TBB | pipeline | 10000 | 12 | 20.539 | 0.946 | 0.079 | oneTBB | +| ALL | pipeline | 10000 | 12 | 20.473 | 0.949 | 0.079 | TBB-like | + +Таблица содержит полный pipeline. Отдельный режим `task_run` пока не измеряется, потому что performance-тест использует +ручной замер через `std::chrono`, а не общий каркас `BaseRunPerfTests`. + +## 6. Интерпретация различий + +SEQ показывает базовую корректную схему алгоритма и дает baseline времени. + +OMP распараллеливает поиск опорной точки, сбор точек и проверку коллинеарности. Слабые места — `critical` при обновлении +минимума и последовательная сортировка. + +TBB использует `parallel_reduce`, `parallel_for` и `concurrent_vector`. Его преимущество — автоматическое планирование +задач, но текущий код не задает `grainsize` и не ограничивает конкуренцию явно. + +STL вручную создает потоки, делит диапазоны и вызывает `join` после запуска всех потоков этапа. Основная цена — создание +потоков и объединение локальных контейнеров. + +ALL в текущем состоянии не является полноценной гибридной MPI-версией: в коде нет rank-ов, обменов и барьеров. +Фактически она повторяет TBB-подход внутри одного процесса и показывает близкие к TBB результаты. + +## 7. Репродуцируемость + +Команды сборки: + +```bash +cmake -S . -B build -D USE_FUNC_TESTS=ON -D USE_PERF_TESTS=ON -D CMAKE_BUILD_TYPE=Release +cmake --build build --parallel +``` + +Команды запуска тестов: + +```bash +scripts/run_tests.py --running-type=threads --counts 1 2 4 +scripts/run_tests.py --running-type=performance +``` + +Команда получения основных замеров: + +```bash +.\build\bin\ppc_perf_tests.exe --gtest_filter=*UrinOGrahamPassagePerfTest* +``` + +Тест выводит строки `PERF_RESULT backend=<...> size=<...> time_ms=<...> hull_size=<...>` для всех пяти backend-ов. + +## 8. Заключение + +На измерениях этой машины лучший результат на размере `10000` среди параллельных реализаций показала ALL/TBB-подобная +версия (`20.473 ms`), но она все равно медленнее SEQ (`19.432 ms`). Это означает, что для данного размера и такой +реализации накладные расходы параллелизма не окупаются полностью. + +Для улучшения сравнения нужно: + +1. Добавить все backend-ы в functional-тесты. +2. Перевести performance-тесты на единый каркас с режимами `task` и `pipeline`. +3. Выполнить серию повторов и использовать медиану, а не один запуск. +4. Для `all` либо реализовать настоящий MPI-уровень, либо явно оставить его как TBB-подобную версию без гибридных + метрик. + +## 9. Источники + +- Методическое руководство по отчётам к задачам параллельного программирования. +- Документация курса `ppc-2026-threads`. +- OpenMP specification. +- oneTBB documentation. +- C++ reference documentation for `std::thread`. + +## 10. Приложение + +Структура отчетов: + +```mermaid +flowchart TD + ROOT["tasks/urin_o_graham_passage/report.md"] --> SEQ["seq/report.md"] + ROOT --> OMP["omp/report.md"] + ROOT --> TBB["tbb/report.md"] + ROOT --> STL["stl/report.md"] + ROOT --> ALL["all/report.md"] + ROOT --> TESTS["tests/functional + tests/performance"] +``` + +Ключевой общий фрагмент алгоритма — стековое удаление вершин при не левом повороте; он остается последовательным во всех +реализациях. + +## Инструкция для тестов + +func tests: + +```text +cd C:\parallel-programming-threads\ppc-2026-threads +cmake --build build --target urin_o_graham_passage_seq urin_o_graham_passage_stl urin_o_graham_passage_omp urin_o_graham_passage_tbb urin_o_graham_passage_all --config Debug -- /m:1 +``` + +```text +cmake --build build --target ppc_func_tests --config Debug -- /m:1 +``` + +```text +.\build\bin\ppc_func_tests.exe --gtest_filter="UrinOGrahamPassage.*" +``` + +perf tests: + +```text +cmake --build build --target ppc_perf_tests --config Debug -- /m:1 +.\build\bin\ppc_perf_tests.exe --gtest_filter="UrinOGrahamPassagePerfTest.*" +``` diff --git a/tasks/urin_o_graham_passage/seq/report.md b/tasks/urin_o_graham_passage/seq/report.md new file mode 100644 index 0000000000..43f58c462d --- /dev/null +++ b/tasks/urin_o_graham_passage/seq/report.md @@ -0,0 +1,150 @@ +# Построение выпуклой оболочки методом Грэхема — SEQ + +- Student: Юрин Олеег Игоревич +- Technology: SEQ +- Variant: 22 + +## 1. Контекст + +Задача состоит в построении выпуклой оболочки множества точек на плоскости. Последовательная версия нужна как эталон: по +ней фиксируется корректная логика алгоритма Грэхема, правила обработки крайних случаев и базовая точка сравнения для +параллельных реализаций. + +## 2. Постановка задачи + +Входные данные: `InType = std::vector`, где `Point` содержит координаты `double x` и `double y`. + +Выходные данные: `OutType = std::vector` — вершины выпуклой оболочки. + +Ограничения и крайние случаи: + +| Случай | Поведение | +| --- | --- | +| меньше 3 точек | `ValidationImpl` возвращает `false` | +| все точки совпадают | `ValidationImpl` возвращает `false` | +| все точки коллинеарны | возвращаются две крайние точки | +| внутренние точки | не попадают в оболочку | +| точки на стороне оболочки | не нарушают выпуклость результата | + +## 3. Базовый алгоритм + +Алгоритм: + +1. Найти опорную точку `p0` с минимальной координатой `y`, при равенстве — с минимальной координатой `x`. +2. Удалить из рассмотрения точки, совпадающие с `p0`. +3. Отсортировать остальные точки по полярному углу относительно `p0`. +4. При одинаковом угле упорядочить точки по расстоянию от `p0`. +5. Проверить случай полной коллинеарности. +6. Построить оболочку стековым проходом: пока очередная точка образует не левый поворот, верхняя точка удаляется. + +Асимптотика по времени: `O(n log n)`, так как доминирует сортировка. + +Асимптотика по памяти: `O(n)`. + +Инвариант алгоритма: после обработки очередной точки стек содержит выпуклую цепочку уже рассмотренных точек. Критерий +корректности результата — все последовательные тройки вершин оболочки не образуют правого поворота. + +## 4. Детали реализации + +Файлы: `seq/include/ops_seq.hpp`, `seq/src/ops_seq.cpp`. + +| Метод | Роль | +| --- | --- | +| `ValidationImpl` | Проверяет достаточное число точек и отсутствие случая, когда все точки одинаковые. | +| `PreProcessingImpl` | Очищает выходной контейнер. | +| `RunImpl` | Выполняет поиск `p0`, сортировку, проверку коллинеарности и стековый проход. | +| `PostProcessingImpl` | Проверяет, что выходной контейнер не пуст. | + +Иллюстративный фрагмент: + +```cpp +// File: seq/src/ops_seq.cpp +for (size_t i = 1; i < points.size(); ++i) { + while (hull.size() >= 2) { + const Point &p = hull[hull.size() - 2]; + const Point &q = hull.back(); + if (Orientation(p, q, points[i]) <= 0) { + hull.pop_back(); + } else { + break; + } + } + hull.push_back(points[i]); +} +``` + +Фрагмент показывает основную стековую часть Graham scan. Удаление вершины при `Orientation <= 0` исключает правый +поворот и коллинеарные промежуточные точки. + +## 5. Проверка корректности + +Функциональные тесты находятся в `tests/functional/main.cpp` и оформлены как typed tests для `SEQ`, `OMP`, `STL`, `TBB` +и `ALL`. Для последовательной версии проверяется ожидаемый размер оболочки и выпуклость результата; для остальных +backend-ов дополнительно выполняется сравнение множества вершин с SEQ. + +Характерные примеры: + +| Тест | Вход | Ожидаемый результат | +| --- | --- | --- | +| `TrianglePoints` | 3 вершины треугольника | оболочка из 3 точек | +| `SquareWithInteriorPoint` | квадрат и точка внутри | оболочка из 4 точек | +| `CollinearPoints` | 5 точек на одной прямой | 2 крайние точки | +| `AllIdenticalPoints` | 5 одинаковых точек | отказ валидации | +| `LargeRandomSet` | 100 точек на окружности | оболочка из 100 точек | + +Сводка functional-сценариев: + +| Группа тестов | Имена тестов | Что проверяется | +| --- | --- | --- | +| Невалидные входы | `EmptyInput`, `SinglePoint`, `TwoDistinctPoints`, `AllIdenticalPoints` | Валидация отклоняет входы, для которых оболочка не строится. | +| Простые фигуры | `TrianglePoints`, `SquarePoints`, `HexagonWithCenter` | Все внешние вершины попадают в оболочку, внутренняя точка центра исключается. | +| Внутренние и граничные точки | `SquareWithInteriorPoint`, `PointOnBoundary`, `RectangleWithCollinearPoints` | Точки внутри и промежуточные точки на стороне не увеличивают размер оболочки. | +| Коллинеарность | `CollinearPoints`, `VerticalCollinear` | Для полностью коллинеарного набора возвращаются две крайние точки. | +| Массовый случай | `LargeRandomSet` | 100 точек на окружности все являются вершинами оболочки. | + +Результат дополнительно проверяется функцией `IsConvexHull`: для каждой вершины берутся соседние вершины, и правый +поворот считается ошибкой. + +## 6. Экспериментальная среда + +Среда, указанная в существующих отчетных материалах задачи: + +| Параметр | Значение | +| --- | --- | +| CPU | 12th Gen Intel(R) Core(TM) i5-12450H | +| RAM | 16 GB | +| OS | Windows 11 | +| Compiler | Clang 21.1.8, C++23 | +| CMake build type | Release | + +Команды запуска: + +```bash +cmake -S . -B build -D USE_FUNC_TESTS=ON -D USE_PERF_TESTS=ON -D CMAKE_BUILD_TYPE=Release +cmake --build build --parallel +scripts/run_tests.py --running-type=threads --counts 1 2 4 +scripts/run_tests.py --running-type=performance +``` + +Размеры задач в текущем performance-файле: `100`, `500`, `1000`, `5000`, `10000` точек. + +## 7. Результаты + +После обновления `tests/performance/main.cpp` SEQ измеряется в одном запуске с OMP, STL, TBB и ALL на одинаковом +детерминированном наборе точек. Время измеряется для полного pipeline в миллисекундах. В таблице указана медиана по 5 +запускам. + +| backend | workers | size | time, ms | speedup | efficiency | hull size | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| SEQ | 1 | 100 | 1.090 | 1.000 | 1.000 | 14 | +| SEQ | 1 | 500 | 1.559 | 1.000 | 1.000 | 18 | +| SEQ | 1 | 1000 | 2.362 | 1.000 | 1.000 | 19 | +| SEQ | 1 | 5000 | 9.579 | 1.000 | 1.000 | 21 | +| SEQ | 1 | 10000 | 19.432 | 1.000 | 1.000 | 20 | + +Самый дорогой фрагмент алгоритма — сортировка точек по полярному углу, так как она имеет сложность `O(n log n)`. + +## 8. Выводы + +SEQ-версия задает корректный baseline алгоритма и используется как знаменатель для расчета ускорения OMP, STL, TBB и +ALL. На размере `10000` медианное baseline-время полного pipeline составило `19.432 ms`. diff --git a/tasks/urin_o_graham_passage/stl/report.md b/tasks/urin_o_graham_passage/stl/report.md new file mode 100644 index 0000000000..cd57ea08c5 --- /dev/null +++ b/tasks/urin_o_graham_passage/stl/report.md @@ -0,0 +1,136 @@ +# Построение выпуклой оболочки методом Грэхема — STL + +- Student: Юрин Олеег Игоревич +- Technology: STL +- Variant: 22 + +## 1. Контекст + +STL-версия использует `std::thread` и показывает ручное управление потоками: создание потоков, разбиение входного +диапазона, локальные результаты, объединение и `join`. Эта версия нужна для сравнения с OpenMP и TBB, где часть +управления берет на себя runtime. + +## 2. Постановка задачи + +По входному массиву `std::vector` нужно построить выпуклую оболочку. SEQ считается эталоном результата и +алгоритмической логики. + +## 3. Базовый алгоритм + +Базовая последовательная логика: поиск опорной точки, сортировка по полярному углу, проверка коллинеарности, стековый +проход. STL-версия распараллеливает только независимые подготовительные проходы по массиву. + +## 4. Схема распараллеливания + +Число потоков берется из `std::thread::hardware_concurrency()`. Если значение равно `0`, используется `2`. + +Разбиение диапазона: + +```text +block_size = points.size() / num_threads +start = thread_idx * block_size +end = thread_idx == num_threads - 1 ? points.size() : start + block_size +``` + +Карта потоков: + +| Поток | Диапазон | +| --- | --- | +| `0` | `[0, block_size)` | +| `1` | `[block_size, 2 * block_size)` | +| `...` | `...` | +| `num_threads - 1` | `[start, points.size())` | + +Локальные результаты хранятся в `local_mins` для поиска минимума и в `local_points[thread_idx]` для фильтрации точек. +`join` вызывается после запуска всех потоков этапа. + +Иллюстративный фрагмент: + +```cpp +// File: stl/src/ops_stl.cpp +for (unsigned int thread_idx = 0; thread_idx < num_threads; ++thread_idx) { + size_t start = thread_idx * block_size; + size_t end = (thread_idx == num_threads - 1) ? points.size() : start + block_size; + + threads.emplace_back([&points, start, end, &local_mins, thread_idx]() { + local_mins[thread_idx] = FindLocalMinimum(points, start, end); + }); +} + +for (auto &th : threads) { + th.join(); +} +``` + +Синхронизация минимальна: каждый поток пишет в свою ячейку или свой локальный вектор. Для проверки коллинеарности +используется `std::atomic`. + +## 5. Детали реализации + +Файлы: `stl/include/ops_stl.hpp`, `stl/src/ops_stl.cpp`. + +| Функция | Роль | +| --- | --- | +| `FindLocalMinimum` | Локальный поиск опорной точки в блоке. | +| `CombineMinimums` | Объединение локальных минимумов. | +| `FindLowestPointParallel` | Запуск потоков для поиска `p0`. | +| `PrepareOtherPointsParallel` | Локальная фильтрация точек и объединение в общий вектор. | +| `AreAllCollinear` | Проверка коллинеарности с атомарным флагом. | + +После объединения локальных результатов сортировка и стековый проход выполняются последовательно. + +## 6. Проверка корректности + +Корректность STL проверяется сравнением с SEQ на тех же входных данных. В `tests/functional/main.cpp` один и тот же +набор typed tests запускается для всех пяти реализаций. Для STL после построения оболочки дополнительно строится эталон +через `UrinOGrahamPassageSEQ`, затем сравниваются размер оболочки и множество вершин. Сравнение выполняется не по +порядку обхода, а по набору точек, потому что для выпуклой оболочки важен состав вершин. + +Дополнительно результат STL проверяется на выпуклость. Функциональный набор включает пустой вход, 1 и 2 точки, +одинаковые точки, коллинеарность, треугольник, квадрат, внутренние точки, точки на границе и точки на окружности. + +Проверка на отсутствие гонок основана на том, что общие контейнеры не изменяются одновременно несколькими потоками: +записи идут в разные элементы `local_mins` или разные `local_points`. + +| Группа тестов | Примеры тестов | Ожидаемый результат | Сравнение с SEQ | +| --- | --- | --- | --- | +| Невалидные входы | `EmptyInput`, `SinglePoint`, `TwoDistinctPoints`, `AllIdenticalPoints` | `Validation()` возвращает `false` | STL должен отклонять те же входы, что SEQ. | +| Простые оболочки | `TrianglePoints`, `SquarePoints`, `HexagonWithCenter` | Оболочка содержит только внешние вершины | Сравнивается множество вершин STL и SEQ. | +| Внутренние и граничные точки | `SquareWithInteriorPoint`, `PointOnBoundary`, `RectangleWithCollinearPoints` | Лишние точки не попадают в оболочку | Размер и набор вершин совпадают с SEQ. | +| Коллинеарность | `CollinearPoints`, `VerticalCollinear` | Результат состоит из двух крайних точек | STL должен вернуть те же крайние точки, что SEQ. | +| Массовый случай | `LargeRandomSet` | 100 точек на окружности дают оболочку из 100 точек | Проверяется выпуклость и совпадение с SEQ. | + +## 7. Экспериментальная среда + +Команды запуска: + +```bash +cmake -S . -B build -D USE_FUNC_TESTS=ON -D USE_PERF_TESTS=ON -D CMAKE_BUILD_TYPE=Release +cmake --build build --parallel +scripts/run_tests.py --running-type=threads --counts 1 2 4 +scripts/run_tests.py --running-type=performance +``` + +Число потоков внутри реализации определяется `std::thread::hardware_concurrency()`, а не напрямую `PPC_NUM_THREADS`. + +## 8. Результаты + +После обновления `tests/performance/main.cpp` STL-версия измеряется на тех же входах, что SEQ и остальные backend-ы. В +таблице указана медиана по 5 запускам. Speedup считается относительно SEQ, efficiency — как `speedup / 12`. + +| threads | size | time, ms | speedup | efficiency | hull size | +| ---: | ---: | ---: | ---: | ---: | ---: | +| 12 | 100 | 4.051 | 0.269 | 0.022 | 14 | +| 12 | 500 | 3.406 | 0.458 | 0.038 | 18 | +| 12 | 1000 | 4.262 | 0.554 | 0.046 | 19 | +| 12 | 5000 | 12.285 | 0.780 | 0.065 | 21 | +| 12 | 10000 | 23.069 | 0.842 | 0.070 | 20 | + +Основные накладные расходы STL-версии — создание потоков, `join`, объединение локальных контейнеров и последовательная +сортировка. + +## 9. Выводы + +Ручная thread-based версия явно показывает цену управления потоками и необходимость аккуратного разбиения данных. На +размере `10000` STL ближе к SEQ, чем OMP, но все равно медленнее baseline из-за накладных расходов и последовательной +части алгоритма. diff --git a/tasks/urin_o_graham_passage/tbb/report.md b/tasks/urin_o_graham_passage/tbb/report.md new file mode 100644 index 0000000000..8ec2f6fb7d --- /dev/null +++ b/tasks/urin_o_graham_passage/tbb/report.md @@ -0,0 +1,125 @@ +# Построение выпуклой оболочки методом Грэхема — TBB + +- Student: Юрин Олеег Игоревич +- Technology: TBB +- Variant: 22 + +## 1. Контекст + +TBB-версия использует задачно-ориентированную модель oneTBB для независимых проходов по массиву точек. Она нужна для +сравнения ручного и директивного параллелизма с runtime, который сам планирует задачи. + +## 2. Постановка задачи + +Постановка совпадает с SEQ: по `std::vector` построить выпуклую оболочку. `seq/report.md` является baseline для +алгоритма и корректности. + +## 3. Базовый алгоритм + +Последовательная версия находит опорную точку, сортирует остальные точки по полярному углу и строит оболочку стековым +проходом. TBB-версия сохраняет эту структуру, но распараллеливает независимые подготовительные проходы. + +## 4. Схема распараллеливания + +Используемые примитивы: + +| Этап | Примитив | +| --- | --- | +| Поиск опорной точки | `tbb::parallel_reduce` | +| Сбор точек без `p0` | `tbb::parallel_for` | +| Проверка коллинеарности | `tbb::parallel_for` | + +Диапазон работы задается через `tbb::blocked_range`. Явный `grainsize` в коде не задан, поэтому используется +стандартное разбиение oneTBB. Явный partitioner также не передается; используется поведение runtime по умолчанию. +Конкуренция через `tbb::global_control` в текущей реализации не ограничивается явно. + +Иллюстративный фрагмент: + +```cpp +// File: tbb/src/ops_tbb.cpp +return tbb::parallel_reduce( + tbb::blocked_range(0, points.size()), points[0], + [&points](const tbb::blocked_range &range, Point current_min) { + for (size_t i = range.begin(); i < range.end(); ++i) { + if (points[i].y < current_min.y - 1e-10 || + (std::abs(points[i].y - current_min.y) < 1e-10 && points[i].x < current_min.x - 1e-10)) { + current_min = points[i]; + } + } + return current_min; + }, + [](const Point &a, const Point &b) { return /* minimum by y, then x */; }); +``` + +Фрагмент показывает редукцию по минимуму: каждый диапазон возвращает локальную опорную точку, затем TBB объединяет +частичные результаты. + +## 5. Детали реализации + +Файлы: `tbb/include/ops_tbb.hpp`, `tbb/src/ops_tbb.cpp`. + +| Функция | Роль | +| --- | --- | +| `FindLowestPointParallel` | Поиск `p0` через `parallel_reduce`. | +| `PrepareOtherPointsParallel` | Параллельный сбор точек в `tbb::concurrent_vector`. | +| `AreAllCollinear` | Параллельная проверка с `std::atomic`. | +| `BuildConvexHull` | Последовательное построение оболочки. | + +Память для параллельного сбора защищена структурой `tbb::concurrent_vector`. После сбора данные копируются в обычный +`std::vector` и сортируются последовательно. + +## 6. Проверка корректности + +Корректность TBB проверяется сравнением с SEQ на тех же входных данных. В `tests/functional/main.cpp` один и тот же +набор typed tests запускается для всех пяти реализаций. Для TBB после построения оболочки дополнительно строится эталон +через `UrinOGrahamPassageSEQ`, затем сравниваются размер оболочки и множество вершин с учетом допуска `1e-10`. + +Дополнительно результат TBB проверяется на выпуклость. Функциональный набор включает невалидные входы, коллинеарность, +треугольник, квадрат с внутренней точкой, точки на границе и точки на окружности. + +Отсутствие гонок в параллельном сборе обеспечивается `concurrent_vector`, а в проверке коллинеарности — атомарным +флагом. + +| Группа тестов | Примеры тестов | Ожидаемый результат | Сравнение с SEQ | +| --- | --- | --- | --- | +| Невалидные входы | `EmptyInput`, `SinglePoint`, `TwoDistinctPoints`, `AllIdenticalPoints` | `Validation()` возвращает `false`, оболочка не строится | Поведение TBB должно совпадать с SEQ. | +| Простые оболочки | `TrianglePoints`, `SquarePoints`, `HexagonWithCenter` | Возвращаются внешние вершины фигуры | Сравнивается множество вершин TBB и SEQ. | +| Внутренние и граничные точки | `SquareWithInteriorPoint`, `PointOnBoundary`, `RectangleWithCollinearPoints` | Внутренние и промежуточные точки исключаются | Размер и набор вершин совпадают с SEQ. | +| Коллинеарность | `CollinearPoints`, `VerticalCollinear` | Возвращаются две крайние точки | TBB должен вернуть те же точки, что SEQ. | +| Массовый случай | `LargeRandomSet` | Оболочка содержит 100 точек окружности | Проверяется выпуклость и совпадение с SEQ. | + +## 7. Экспериментальная среда + +Сборка: + +```bash +cmake -S . -B build -D USE_FUNC_TESTS=ON -D USE_PERF_TESTS=ON -D CMAKE_BUILD_TYPE=Release +cmake --build build --parallel +scripts/run_tests.py --running-type=threads --counts 1 2 4 +scripts/run_tests.py --running-type=performance +``` + +Размеры задач из текущего performance-файла: `100`, `500`, `1000`, `5000`, `10000`. Ограничение конкуренции TBB в коде +не задано явно. Число потоков: 12 + +## 8. Результаты + +После обновления `tests/performance/main.cpp` TBB-версия измеряется на тех же детерминированных входах, что SEQ, OMP, +STL и ALL. В таблице указана медиана по 5 запускам. Speedup считается относительно SEQ, efficiency — как `speedup / 12`. + +| workers | size | time, ms | speedup | efficiency | hull size | +| ---: | ---: | ---: | ---: | ---: | ---: | +| 12 | 100 | 1.727 | 0.631 | 0.053 | 14 | +| 12 | 500 | 1.973 | 0.790 | 0.066 | 18 | +| 12 | 1000 | 2.920 | 0.809 | 0.067 | 19 | +| 12 | 5000 | 10.442 | 0.917 | 0.076 | 21 | +| 12 | 10000 | 20.539 | 0.946 | 0.079 | 20 | + +Ожидаемое узкое место — последовательная сортировка и последовательный стековый проход. На малых размерах overhead +планировщика TBB может быть заметен сильнее полезной работы. + +## 9. Выводы + +TBB-версия хорошо выражает независимые проходы через `parallel_reduce` и `parallel_for`, но по медиане на всех +измеренных размерах уступает SEQ. На размере `10000` результат близок к baseline, однако последовательная сортировка и +накладные расходы runtime не дают устойчивого ускорения. diff --git a/tasks/urin_o_graham_passage/tests/functional/main.cpp b/tasks/urin_o_graham_passage/tests/functional/main.cpp index 6e882f9be4..20e8fde4c7 100644 --- a/tasks/urin_o_graham_passage/tests/functional/main.cpp +++ b/tasks/urin_o_graham_passage/tests/functional/main.cpp @@ -6,12 +6,20 @@ #include #include +#include "urin_o_graham_passage/all/include/ops_all.hpp" #include "urin_o_graham_passage/common/include/common.hpp" +#include "urin_o_graham_passage/omp/include/ops_omp.hpp" +#include "urin_o_graham_passage/seq/include/ops_seq.hpp" #include "urin_o_graham_passage/stl/include/ops_stl.hpp" +#include "urin_o_graham_passage/tbb/include/ops_tbb.hpp" namespace urin_o_graham_passage { namespace { +double Orientation(const Point &p, const Point &q, const Point &r) { + return ((q.x - p.x) * (r.y - p.y)) - ((q.y - p.y) * (r.x - p.x)); +} + bool IsConvexHull(const std::vector &hull) { if (hull.size() < 3) { return true; @@ -21,46 +29,55 @@ bool IsConvexHull(const std::vector &hull) { size_t prev = (i == 0) ? hull.size() - 1 : i - 1; size_t next = (i + 1) % hull.size(); - if (UrinOGrahamPassageSTL::Orientation(hull[prev], hull[i], hull[next]) < 0) { + if (Orientation(hull[prev], hull[i], hull[next]) < -1e-10) { return false; } } return true; } -bool ValidateTask(const std::shared_ptr &task) { +template +bool ValidateTask(const std::shared_ptr &task) { return task->Validation(); } -bool PreProcessTask(const std::shared_ptr &task) { +template +bool PreProcessTask(const std::shared_ptr &task) { return task->PreProcessing(); } -bool RunTask(const std::shared_ptr &task) { +template +bool RunTask(const std::shared_ptr &task) { return task->Run(); } -bool PostProcessTask(const std::shared_ptr &task) { +template +bool PostProcessTask(const std::shared_ptr &task) { return task->PostProcessing(); } -void ExpectValidation(const std::shared_ptr &task) { +template +void ExpectValidation(const std::shared_ptr &task) { EXPECT_TRUE(ValidateTask(task)); } -void ExpectPreProcessing(const std::shared_ptr &task) { +template +void ExpectPreProcessing(const std::shared_ptr &task) { EXPECT_TRUE(PreProcessTask(task)); } -void ExpectRun(const std::shared_ptr &task) { +template +void ExpectRun(const std::shared_ptr &task) { EXPECT_TRUE(RunTask(task)); } -void ExpectPostProcessing(const std::shared_ptr &task) { +template +void ExpectPostProcessing(const std::shared_ptr &task) { EXPECT_TRUE(PostProcessTask(task)); } -void ExecuteTaskPipeline(const std::shared_ptr &task) { +template +void ExecuteTaskPipeline(const std::shared_ptr &task) { ExpectValidation(task); ExpectPreProcessing(task); ExpectRun(task); @@ -80,73 +97,90 @@ void VerifyHull(const std::vector &hull, size_t expected_size) { CheckHullConvexity(hull); } +template void RunAndCheckHull(const InType &points, size_t expected_size) { - auto task = std::make_shared(points); + auto task = std::make_shared(points); ExecuteTaskPipeline(task); VerifyHull(task->GetOutput(), expected_size); } +template void RunAndExpectFailure(const InType &points) { - auto task = std::make_shared(points); + auto task = std::make_shared(points); EXPECT_FALSE(task->Validation()); EXPECT_TRUE(task->GetOutput().empty()); } -// Тесты -TEST(UrinOGrahamPassageStl, EmptyInput) { - RunAndExpectFailure({}); +void RunAndCheckAllImplementations(const InType &points, size_t expected_size) { + RunAndCheckHull(points, expected_size); + RunAndCheckHull(points, expected_size); + RunAndCheckHull(points, expected_size); + RunAndCheckHull(points, expected_size); + RunAndCheckHull(points, expected_size); +} + +void RunAndExpectAllImplementationsFailure(const InType &points) { + RunAndExpectFailure(points); + RunAndExpectFailure(points); + RunAndExpectFailure(points); + RunAndExpectFailure(points); + RunAndExpectFailure(points); +} + +TEST(UrinOGrahamPassage, EmptyInput) { + RunAndExpectAllImplementationsFailure({}); } -TEST(UrinOGrahamPassageStl, SinglePoint) { - RunAndExpectFailure({Point(5.0, 3.0)}); +TEST(UrinOGrahamPassage, SinglePoint) { + RunAndExpectAllImplementationsFailure({Point(5.0, 3.0)}); } -TEST(UrinOGrahamPassageStl, TwoDistinctPoints) { - RunAndExpectFailure({Point(0.0, 0.0), Point(3.0, 4.0)}); +TEST(UrinOGrahamPassage, TwoDistinctPoints) { + RunAndExpectAllImplementationsFailure({Point(0.0, 0.0), Point(3.0, 4.0)}); } -TEST(UrinOGrahamPassageStl, CollinearPoints) { +TEST(UrinOGrahamPassage, CollinearPoints) { InType pts = {Point(0.0, 0.0), Point(1.0, 0.0), Point(2.0, 0.0), Point(3.0, 0.0), Point(4.0, 0.0)}; - RunAndCheckHull(pts, 2); + RunAndCheckAllImplementations(pts, 2); } -TEST(UrinOGrahamPassageStl, TrianglePoints) { +TEST(UrinOGrahamPassage, TrianglePoints) { InType pts = {Point(0.0, 0.0), Point(4.0, 0.0), Point(2.0, 3.0)}; - RunAndCheckHull(pts, 3); + RunAndCheckAllImplementations(pts, 3); } -TEST(UrinOGrahamPassageStl, SquarePoints) { +TEST(UrinOGrahamPassage, SquarePoints) { InType pts = {Point(0.0, 0.0), Point(4.0, 0.0), Point(4.0, 4.0), Point(0.0, 4.0)}; - RunAndCheckHull(pts, 4); + RunAndCheckAllImplementations(pts, 4); } -TEST(UrinOGrahamPassageStl, SquareWithInteriorPoint) { +TEST(UrinOGrahamPassage, SquareWithInteriorPoint) { InType pts = {Point(0.0, 0.0), Point(4.0, 0.0), Point(4.0, 4.0), Point(0.0, 4.0), Point(2.0, 2.0)}; - RunAndCheckHull(pts, 4); + RunAndCheckAllImplementations(pts, 4); } -TEST(UrinOGrahamPassageStl, RectangleWithCollinearPoints) { +TEST(UrinOGrahamPassage, RectangleWithCollinearPoints) { InType pts = {Point(0.0, 0.0), Point(1.0, 0.0), Point(2.0, 0.0), Point(3.0, 0.0), Point(3.0, 1.0), Point(2.0, 1.0), Point(1.0, 1.0), Point(0.0, 1.0)}; - RunAndCheckHull(pts, 4); + RunAndCheckAllImplementations(pts, 4); } -TEST(UrinOGrahamPassageStl, AllIdenticalPoints) { +TEST(UrinOGrahamPassage, AllIdenticalPoints) { InType pts = {Point(3.0, 3.0), Point(3.0, 3.0), Point(3.0, 3.0), Point(3.0, 3.0), Point(3.0, 3.0)}; - RunAndExpectFailure(pts); + RunAndExpectAllImplementationsFailure(pts); } -TEST(UrinOGrahamPassageStl, PointOnBoundary) { +TEST(UrinOGrahamPassage, PointOnBoundary) { InType pts = {Point(0.0, 0.0), Point(4.0, 0.0), Point(2.0, 0.0), Point(4.0, 4.0), Point(0.0, 4.0)}; - RunAndCheckHull(pts, 4); + RunAndCheckAllImplementations(pts, 4); } -TEST(UrinOGrahamPassageStl, VerticalCollinear) { +TEST(UrinOGrahamPassage, VerticalCollinear) { InType pts = {Point(0.0, 0.0), Point(0.0, 1.0), Point(0.0, 2.0), Point(0.0, 5.0)}; - RunAndCheckHull(pts, 2); + RunAndCheckAllImplementations(pts, 2); } -TEST(UrinOGrahamPassageStl, LargeRandomSet) { +TEST(UrinOGrahamPassage, LargeRandomSet) { InType pts; const int num_points = 100; pts.reserve(static_cast(num_points)); @@ -156,13 +190,13 @@ TEST(UrinOGrahamPassageStl, LargeRandomSet) { pts.emplace_back(std::cos(angle) * 10.0, std::sin(angle) * 10.0); } - RunAndCheckHull(pts, 100); + RunAndCheckAllImplementations(pts, 100); } -TEST(UrinOGrahamPassageStl, HexagonWithCenter) { +TEST(UrinOGrahamPassage, HexagonWithCenter) { InType pts = {Point(2.0, 0.0), Point(1.0, 1.73), Point(-1.0, 1.73), Point(-2.0, 0.0), Point(-1.0, -1.73), Point(1.0, -1.73), Point(0.0, 0.0)}; - RunAndCheckHull(pts, 6); + RunAndCheckAllImplementations(pts, 6); } } // namespace diff --git a/tasks/urin_o_graham_passage/tests/performance/main.cpp b/tasks/urin_o_graham_passage/tests/performance/main.cpp index 6ea7aa7720..ad55823d0a 100644 --- a/tasks/urin_o_graham_passage/tests/performance/main.cpp +++ b/tasks/urin_o_graham_passage/tests/performance/main.cpp @@ -6,14 +6,23 @@ #include #include #include +#include #include +#include "urin_o_graham_passage/all/include/ops_all.hpp" #include "urin_o_graham_passage/common/include/common.hpp" +#include "urin_o_graham_passage/omp/include/ops_omp.hpp" +#include "urin_o_graham_passage/seq/include/ops_seq.hpp" #include "urin_o_graham_passage/stl/include/ops_stl.hpp" +#include "urin_o_graham_passage/tbb/include/ops_tbb.hpp" namespace urin_o_graham_passage { namespace { +double Orientation(const Point &p, const Point &q, const Point &r) { + return ((q.x - p.x) * (r.y - p.y)) - ((q.y - p.y) * (r.x - p.x)); +} + bool IsConvexHull(const std::vector &hull) { if (hull.size() < 3) { return true; @@ -23,7 +32,7 @@ bool IsConvexHull(const std::vector &hull) { size_t prev = (i == 0) ? hull.size() - 1 : i - 1; size_t next = (i + 1) % hull.size(); - if (UrinOGrahamPassageSTL::Orientation(hull[prev], hull[i], hull[next]) < 0) { + if (Orientation(hull[prev], hull[i], hull[next]) < -1e-10) { return false; } } @@ -48,43 +57,29 @@ class UrinOGrahamPassagePerfTest : public ::testing::Test { } }; -bool ValidateTask(UrinOGrahamPassageSTL &task) { +template +bool ValidateTask(TaskType &task) { return task.Validation(); } -bool PreProcessTask(UrinOGrahamPassageSTL &task) { +template +bool PreProcessTask(TaskType &task) { return task.PreProcessing(); } -bool RunTask(UrinOGrahamPassageSTL &task) { +template +bool RunTask(TaskType &task) { return task.Run(); } -bool PostProcessTask(UrinOGrahamPassageSTL &task) { +template +bool PostProcessTask(TaskType &task) { return task.PostProcessing(); } -void ExpectValidation(UrinOGrahamPassageSTL &task) { - EXPECT_TRUE(ValidateTask(task)); -} - -void ExpectPreProcessing(UrinOGrahamPassageSTL &task) { - EXPECT_TRUE(PreProcessTask(task)); -} - -void ExpectRun(UrinOGrahamPassageSTL &task) { - EXPECT_TRUE(RunTask(task)); -} - -void ExpectPostProcessing(UrinOGrahamPassageSTL &task) { - EXPECT_TRUE(PostProcessTask(task)); -} - -void RunTaskPipeline(UrinOGrahamPassageSTL &task) { - ExpectValidation(task); - ExpectPreProcessing(task); - ExpectRun(task); - ExpectPostProcessing(task); +template +bool RunTaskPipeline(TaskType &task) { + return ValidateTask(task) && PreProcessTask(task) && RunTask(task) && PostProcessTask(task); } void CheckHullValidity(const std::vector &hull) { @@ -92,25 +87,54 @@ void CheckHullValidity(const std::vector &hull) { EXPECT_TRUE(IsConvexHull(hull)); } -void PrintPerformanceResult(size_t num_points, int64_t ms, size_t hull_size) { - std::cout << "STL version with " << num_points << " points took " << ms << " ms\n"; +void PrintPerformanceResult(std::string_view version, size_t num_points, int64_t ms, size_t hull_size) { + std::cout << version << " version with " << num_points << " points took " << ms << " ms\n"; std::cout << "Convex hull size: " << hull_size << "\n"; } -TEST_F(UrinOGrahamPassagePerfTest, StlPerformance) { - const size_t num_points = 10000; - InType input_points = GenerateRandomPoints(num_points); - - UrinOGrahamPassageSTL task(input_points); +template +void RunPerformanceCase(std::string_view version, const InType &input_points) { + TaskType task(input_points); auto start = std::chrono::high_resolution_clock::now(); - RunTaskPipeline(task); + EXPECT_TRUE(RunTaskPipeline(task)); auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(end - start); const auto &hull = task.GetOutput(); CheckHullValidity(hull); - PrintPerformanceResult(num_points, static_cast(duration.count()), hull.size()); + PrintPerformanceResult(version, input_points.size(), static_cast(duration.count()), hull.size()); +} + +template +void RunDifferentSizeCase(std::string_view version, const InType &test_points) { + TaskType task(test_points); + + auto start = std::chrono::high_resolution_clock::now(); + EXPECT_TRUE(RunTaskPipeline(task)); + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end - start); + + const auto &hull = task.GetOutput(); + + if (hull.size() >= static_cast(3)) { + std::cout << version << " size " << test_points.size() << ": " << duration.count() << " ms, " + << "hull size: " << hull.size() << "\n"; + } else { + std::cout << version << " size " << test_points.size() << ": " << duration.count() << " ms, " + << "hull size: " << hull.size() << " (invalid)\n"; + } +} + +TEST_F(UrinOGrahamPassagePerfTest, Performance) { + const size_t num_points = 10000; + InType input_points = GenerateRandomPoints(num_points); + + RunPerformanceCase("SEQ", input_points); + RunPerformanceCase("STL", input_points); + RunPerformanceCase("OMP", input_points); + RunPerformanceCase("TBB", input_points); + RunPerformanceCase("ALL", input_points); } TEST_F(UrinOGrahamPassagePerfTest, DifferentSizes) { @@ -120,22 +144,11 @@ TEST_F(UrinOGrahamPassagePerfTest, DifferentSizes) { for (size_t size : sizes) { InType test_points = GenerateRandomPoints(size); - UrinOGrahamPassageSTL task(test_points); - - auto start = std::chrono::high_resolution_clock::now(); - RunTaskPipeline(task); - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - - const auto &hull = task.GetOutput(); - - if (hull.size() >= static_cast(3)) { - std::cout << "Size " << size << ": " << duration.count() << " ms, " - << "hull size: " << hull.size() << "\n"; - } else { - std::cout << "Size " << size << ": " << duration.count() << " ms, " - << "hull size: " << hull.size() << " (invalid)\n"; - } + RunDifferentSizeCase("SEQ", test_points); + RunDifferentSizeCase("STL", test_points); + RunDifferentSizeCase("OMP", test_points); + RunDifferentSizeCase("TBB", test_points); + RunDifferentSizeCase("ALL", test_points); } }