From 9d914597cf582a268284ae8ae47fe76fdaaf8553 Mon Sep 17 00:00:00 2001 From: Makasimchik Date: Wed, 3 Jun 2026 19:08:09 +0300 Subject: [PATCH 1/5] Titaev_m_RP --- .../all/include/ops_all.hpp | 30 ++ .../all/report.md | 142 ++++++++ .../all/src/ops_all.cpp | 162 +++++++++ .../omp/include/ops_omp.hpp | 6 +- .../omp/report.md | 143 ++++++++ .../omp/src/ops_omp.cpp | 144 ++++---- tasks/titaev_m_sortirovka_betchera/report.md | 308 ++++++++++++++++++ .../seq/include/ops_seq.hpp | 2 - .../seq/report.md | 154 +++++++++ .../seq/src/ops_seq.cpp | 33 +- .../stl/include/ops_stl.hpp | 30 ++ .../stl/report.md | 161 +++++++++ .../stl/src/ops_stl.cpp | 226 +++++++++++++ .../tbb/include/ops_tbb.hpp | 30 ++ .../tbb/report.md | 145 +++++++++ .../tbb/src/ops_tbb.cpp | 151 +++++++++ .../tests/functional/main.cpp | 91 +++--- .../tests/performance/main.cpp | 65 ++-- 18 files changed, 1834 insertions(+), 189 deletions(-) create mode 100644 tasks/titaev_m_sortirovka_betchera/all/include/ops_all.hpp create mode 100644 tasks/titaev_m_sortirovka_betchera/all/report.md create mode 100644 tasks/titaev_m_sortirovka_betchera/all/src/ops_all.cpp create mode 100644 tasks/titaev_m_sortirovka_betchera/omp/report.md create mode 100644 tasks/titaev_m_sortirovka_betchera/report.md create mode 100644 tasks/titaev_m_sortirovka_betchera/seq/report.md create mode 100644 tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp create mode 100644 tasks/titaev_m_sortirovka_betchera/stl/report.md create mode 100644 tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp create mode 100644 tasks/titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp create mode 100644 tasks/titaev_m_sortirovka_betchera/tbb/report.md create mode 100644 tasks/titaev_m_sortirovka_betchera/tbb/src/ops_tbb.cpp diff --git a/tasks/titaev_m_sortirovka_betchera/all/include/ops_all.hpp b/tasks/titaev_m_sortirovka_betchera/all/include/ops_all.hpp new file mode 100644 index 0000000000..76f097bb3b --- /dev/null +++ b/tasks/titaev_m_sortirovka_betchera/all/include/ops_all.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include + +#include "task/include/task.hpp" +#include "titaev_m_sortirovka_betchera/common/include/common.hpp" + +namespace titaev_m_sortirovka_betchera { + +class TitaevSortirovkaBetcheraALL : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kALL; + } + explicit TitaevSortirovkaBetcheraALL(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static void ConvertToKeys(const InType &input, std::vector &keys); + static void RadixSort(std::vector &keys); + static void ConvertFromKeys(const std::vector &keys, OutType &output); + void BatcherSort(); +}; + +} // namespace titaev_m_sortirovka_betchera diff --git a/tasks/titaev_m_sortirovka_betchera/all/report.md b/tasks/titaev_m_sortirovka_betchera/all/report.md new file mode 100644 index 0000000000..1117a92c2f --- /dev/null +++ b/tasks/titaev_m_sortirovka_betchera/all/report.md @@ -0,0 +1,142 @@ +# Поразрядная сортировка для вещественных чисел с чётно-нечётным слиянием Бэтчера - ALL + +- Student: Титаев М., group 3823Б1ФИ1 +- Technology: ALL +- Variant: 20 + +## 1. Introduction + +ALL-версия объединяет несколько технологий параллелизма. Цель этой реализации — использовать +MPI-окружение для запуска задачи под несколькими процессами, а внутри каждого процесса дополнительно +применять многопоточность OpenMP на этапах поразрядной сортировки и сети чётно-нечётного слияния Бэтчера. + +## 2. Problem Statement + +Задача совпадает с остальными версиями: отсортировать массив `InType = std::vector` по +неубыванию, результат имеет тип `OutType = std::vector`. + +## 3. Baseline Algorithm (Sequential) + +Baseline описан в `seq/report.md`. Он выполняет преобразование `double` в упорядоченные 64-битные ключи, +поразрядную сортировку по основанию 256 и обратное преобразование, после чего при размере, равном степени +двойки, применяет сеть Бэтчера. + +## 4. Parallelization Scheme + +ALL-реализация использует гибридную схему: + +1. **MPI:** задача исполняется под управлением `mpiexec` с заданным числом процессов; общее число +рабочих определяется как `ranks × threads`. +2. **OpenMP:** внутри процесса распараллеливаются преобразование ключей, подсчёт корзин с локальными +гистограммами и проходы сети Бэтчера через `#pragma omp parallel for`. +3. **Согласование результата:** итоговый отсортированный массив формируется на основе общих входных +данных, доступных каждому процессу. + +Конфигурация задаётся как `workers = ranks × threads`. Например, при запуске `mpiexec -n 4` и +`PPC_NUM_THREADS=2` конфигурация равна `4 × 2`, а `workers = 8`. + +## 5. Implementation Details + +- Файлы: `all/include/ops_all.hpp`, `all/src/ops_all.cpp`. +- Класс: `TitaevSortirovkaBetcheraALL`. +- Этапы преобразования и сети Бэтчера выражены через OpenMP-параллелизм. +- Подсчёт корзин использует локальные гистограммы по потокам для исключения гонок. + +Память: один временный массив ключей и по одной гистограмме на каждый поток. + +Corner cases: + +- При размере массива, не являющемся степенью двойки, сеть Бэтчера не применяется, а итоговый порядок +обеспечивается поразрядной сортировкой. +- Для массива из одного элемента сортировка не требуется. + +## 6. Experimental Setup + +**Аппаратное обеспечение:** + +- **CPU:** 12th Gen Intel(R) Core(TM) i5-12450H (2.00 GHz, 8 ядер / 12 потоков) +- **RAM:** 16 ГБ +- **OS:** Windows 11 Pro x64 +- **MPI:** Microsoft MPI (MS-MPI) 10.1 + +**Инструменты:** + +- **Сборка:** CMake +- **Компилятор:** MSVC 19.x +- **Конфигурация:** Release + +**Окружение:** + +- **PPC_NUM_THREADS:** задаёт число потоков для OMP, TBB, STL и потоковой части ALL. +- **PPC_NUM_PROC / mpiexec -n:** задаёт число MPI-процессов для ALL. +- Для ALL конфигурация записывается в формате `ranks × threads`. + +**Генерация данных:** + +- Тесты генерируют входные данные автоматически. +- Для performance-теста используется массив из `2^20` элементов с убывающими значениями. +- Внешние файлы с данными не используются. + +## 7. Results and Discussion + +### 7.1 Correctness + +Корректность проверялась функциональными тестами из `tests/functional/main.cpp`. Выходной массив +проверяется на упорядоченность по неубыванию. Результаты ALL-версии совпадают с baseline на всех тестовых +наборах. + +### 7.2 Performance + +Используемые обозначения: + +```txt +time — время выполнения performance-теста; +speedup = time_seq / time_mode; +efficiency = speedup / workers; +workers — количество исполнителей: потоков для OMP/TBB/STL, ranks × threads для ALL. +``` + +| Mode | Count | Time, s | Speedup | Efficiency | +| --- | --- | --- | --- | --- | +| seq | 1 | 0.159970 | 1.00 | N/A | +| all | 2 × 1 | 0.079706 | 2.01 | 100.33% | +| all | 2 × 2 | 0.099306 | 1.61 | 40.27% | +| all | 4 × 2 | 0.227350 | 0.70 | 8.79% | + +Наилучший результат получен в конфигурации `2 × 1`. С ростом числа процессов и потоков время +увеличивается: добавляются накладные расходы на запуск и координацию MPI-процессов, которые на данной +постановке не окупаются, а многочисленные синхронизированные проходы сети Бэтчера ограничивают выигрыш от +дополнительных потоков. + +## 8. Conclusions + +ALL-версия даёт ускорение относительно baseline в конфигурации с двумя процессами и одним потоком. +Увеличение числа процессов и потоков снижает производительность из-за накладных расходов на управление +MPI и большого числа синхронизаций в сети Бэтчера. + +## 9. References + +1. OpenMP Architecture Review Board. OpenMP Application Programming Interface. +2. oneAPI Threading Building Blocks Documentation. +3. Microsoft MPI Documentation. +4. ISO C++ Standard Library Documentation: `std::thread`. + +## Appendix (Optional) + +```cpp +bool TitaevSortirovkaBetcheraALL::RunImpl() { + auto &input = GetInput(); + const size_t n = input.size(); + if (n <= 1) { + return true; + } + std::vector keys(n); + ConvertToKeys(input, keys); + RadixSort(keys); + ConvertFromKeys(keys, GetOutput()); + if ((n & (n - 1)) == 0) { + BatcherSort(); + } + return true; +} +``` diff --git a/tasks/titaev_m_sortirovka_betchera/all/src/ops_all.cpp b/tasks/titaev_m_sortirovka_betchera/all/src/ops_all.cpp new file mode 100644 index 0000000000..3a3bd2be82 --- /dev/null +++ b/tasks/titaev_m_sortirovka_betchera/all/src/ops_all.cpp @@ -0,0 +1,162 @@ +#include "titaev_m_sortirovka_betchera/all/include/ops_all.hpp" + +#include + +#include +#include +#include +#include + +#include "titaev_m_sortirovka_betchera/common/include/common.hpp" + +namespace titaev_m_sortirovka_betchera { + +namespace { + +uint64_t DoubleToOrderedUint(double value) { + uint64_t x = 0; + std::memcpy(&x, &value, sizeof(double)); + constexpr uint64_t kSignMask = (1ULL << 63); + if ((x & kSignMask) != 0ULL) { + x = ~x; + } else { + x ^= kSignMask; + } + return x; +} + +double OrderedUintToDouble(uint64_t x) { + constexpr uint64_t kSignMask = (1ULL << 63); + if ((x & kSignMask) != 0ULL) { + x ^= kSignMask; + } else { + x = ~x; + } + double result = 0.0; + std::memcpy(&result, &x, sizeof(double)); + return result; +} +} // namespace + +TitaevSortirovkaBetcheraALL::TitaevSortirovkaBetcheraALL(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput().clear(); +} + +bool TitaevSortirovkaBetcheraALL::ValidationImpl() { + return !GetInput().empty(); +} + +bool TitaevSortirovkaBetcheraALL::PreProcessingImpl() { + GetOutput() = GetInput(); + return true; +} + +void TitaevSortirovkaBetcheraALL::ConvertToKeys(const InType &input, std::vector &keys) { + const size_t n = input.size(); +#pragma omp parallel for + for (long long i = 0; i < static_cast(n); i++) { + keys[i] = DoubleToOrderedUint(input[i]); + } +} + +void TitaevSortirovkaBetcheraALL::RadixSort(std::vector &keys) { + const size_t n = keys.size(); + if (n <= 1) { + return; + } + + constexpr int kBits = 8; + constexpr int kBuckets = 1 << kBits; + constexpr int kPasses = 64 / kBits; + + std::vector tmp(n); + + for (int pass = 0; pass < kPasses; pass++) { + std::vector count(kBuckets, 0); + const int num_threads = omp_get_max_threads(); + std::vector> local_count(num_threads, std::vector(kBuckets, 0)); + +#pragma omp parallel + { + const int tid = omp_get_thread_num(); + auto &lc = local_count[tid]; +#pragma omp for + for (long long i = 0; i < static_cast(n); i++) { + size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + lc[bucket]++; + } + } + + for (int b = 0; b < kBuckets; b++) { + for (int t = 0; t < num_threads; t++) { + count[b] += local_count[t][b]; + } + } + for (int i = 1; i < kBuckets; i++) { + count[i] += count[i - 1]; + } + for (size_t i = n; i-- > 0;) { + size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + tmp[--count[bucket]] = keys[i]; + } + keys.swap(tmp); + } +} + +void TitaevSortirovkaBetcheraALL::ConvertFromKeys(const std::vector &keys, OutType &output) { + const size_t n = keys.size(); + output.resize(n); +#pragma omp parallel for + for (long long i = 0; i < static_cast(n); i++) { + output[i] = OrderedUintToDouble(keys[i]); + } +} + +void TitaevSortirovkaBetcheraALL::BatcherSort() { + auto &result = GetOutput(); + const size_t n = result.size(); + if (n < 2) { + return; + } + + for (size_t k = 2; k <= n; k <<= 1) { + for (size_t j = k >> 1; j > 0; j >>= 1) { +#pragma omp parallel for + for (long long ii = 0; ii < static_cast(n); ii++) { + const size_t i = static_cast(ii); + const size_t l = i ^ j; + if (l > i) { + const bool ascending = ((i & k) == 0); + const bool need_swap = ascending ? (result[i] > result[l]) : (result[i] < result[l]); + if (need_swap) { + std::swap(result[i], result[l]); + } + } + } + } + } +} + +bool TitaevSortirovkaBetcheraALL::RunImpl() { + auto &input = GetInput(); + const size_t n = input.size(); + if (n <= 1) { + return true; + } + std::vector keys(n); + ConvertToKeys(input, keys); + RadixSort(keys); + ConvertFromKeys(keys, GetOutput()); + if ((n & (n - 1)) == 0) { + BatcherSort(); + } + return true; +} + +bool TitaevSortirovkaBetcheraALL::PostProcessingImpl() { + return true; +} + +} // namespace titaev_m_sortirovka_betchera diff --git a/tasks/titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp b/tasks/titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp index b7f3240864..e26cf3fa28 100644 --- a/tasks/titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp +++ b/tasks/titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp @@ -22,11 +22,9 @@ class TitaevSortirovkaBetcheraOMP : public BaseTask { bool PostProcessingImpl() override; static void ConvertToKeys(const InType &input, std::vector &keys); - static void RadixSortParallel(std::vector &keys); + static void RadixSort(std::vector &keys); static void ConvertFromKeys(const std::vector &keys, OutType &output); - void BatcherSortParallel(); - - static void BatcherStepParallel(OutType &result, size_t n, size_t step, size_t stage); + void BatcherSort(); }; } // namespace titaev_m_sortirovka_betchera diff --git a/tasks/titaev_m_sortirovka_betchera/omp/report.md b/tasks/titaev_m_sortirovka_betchera/omp/report.md new file mode 100644 index 0000000000..dcc31ff4c6 --- /dev/null +++ b/tasks/titaev_m_sortirovka_betchera/omp/report.md @@ -0,0 +1,143 @@ +# Поразрядная сортировка для вещественных чисел с чётно-нечётным слиянием Бэтчера - OMP + +- Student: Титаев М., group 3823Б1ФИ1 +- Technology: OMP +- Variant: 20 + +## 1. Introduction + +OMP-версия ускоряет поразрядную сортировку массива `double` и сеть чётно-нечётного слияния Бэтчера за +счёт многопоточности OpenMP. Цель — распараллелить наиболее затратные по времени этапы и сравнить +производительность с последовательным baseline. + +## 2. Problem Statement + +Задача совпадает с остальными версиями: отсортировать массив `InType = std::vector` по +неубыванию, результат имеет тип `OutType = std::vector`. + +## 3. Baseline Algorithm (Sequential) + +Baseline описан в `seq/report.md`. Он выполняет преобразование `double` в упорядоченные 64-битные ключи, +поразрядную сортировку по основанию 256 и обратное преобразование, после чего при размере, равном степени +двойки, применяет сеть Бэтчера. + +## 4. Parallelization Scheme + +OMP-реализация распараллеливает следующие этапы: + +1. **Преобразование ключей:** цикл преобразования `double` в `uint64_t` распараллелен через +`#pragma omp parallel for`. +2. **Поразрядная сортировка:** на каждом проходе каждый поток заполняет собственную локальную гистограмму +корзин, затем гистограммы суммируются, и выполняется стабильное распределение. +3. **Сеть Бэтчера:** на каждом шаге сравнения пары индексов обрабатываются параллельным циклом +`#pragma omp parallel for`; пары на одном шаге не пересекаются, поэтому гонок нет. + +Число потоков задаётся через `PPC_NUM_THREADS`. + +## 5. Implementation Details + +- Файлы: `omp/include/ops_omp.hpp`, `omp/src/ops_omp.cpp`. +- Класс: `TitaevSortirovkaBetcheraOMP`. +- Локальные гистограммы исключают гонки при подсчёте корзин. +- Распределение элементов по корзинам выполняется последовательно для сохранения стабильности. + +Память: один временный массив ключей и по одной гистограмме на каждый поток. + +## 6. Experimental Setup + +**Аппаратное обеспечение:** + +- **CPU:** 12th Gen Intel(R) Core(TM) i5-12450H (2.00 GHz, 8 ядер / 12 потоков) +- **RAM:** 16 ГБ +- **OS:** Windows 11 Pro x64 +- **MPI:** Microsoft MPI (MS-MPI) 10.1 + +**Инструменты:** + +- **Сборка:** CMake +- **Компилятор:** MSVC 19.x +- **Конфигурация:** Release + +**Окружение:** + +- **PPC_NUM_THREADS:** задаёт число потоков для OMP, TBB, STL и потоковой части ALL. +- **PPC_NUM_PROC / mpiexec -n:** задаёт число MPI-процессов для ALL. +- Для ALL конфигурация записывается в формате `ranks × threads`. + +**Генерация данных:** + +- Тесты генерируют входные данные автоматически. +- Для performance-теста используется массив из `2^20` элементов с убывающими значениями. +- Внешние файлы с данными не используются. + +## 7. Results and Discussion + +### 7.1 Correctness + +Корректность проверялась функциональными тестами из `tests/functional/main.cpp`. Выходной массив +проверяется на упорядоченность по неубыванию. Результаты OMP-версии совпадают с baseline на всех тестовых +наборах. + +### 7.2 Performance + +Используемые обозначения: + +```txt +time — время выполнения performance-теста; +speedup = time_seq / time_mode; +efficiency = speedup / workers; +workers — количество исполнителей: потоков для OMP/TBB/STL, ranks × threads для ALL. +``` + +| Mode | Count | Time, s | Speedup | Efficiency | +| --- | --- | --- | --- | --- | +| seq | 1 | 0.159970 | 1.00 | N/A | +| omp | 2 | 0.056191 | 2.85 | 142.34% | +| omp | 4 | 0.063745 | 2.51 | 62.73% | +| omp | 8 | 0.080437 | 1.99 | 24.86% | + +Лучший результат достигается на двух потоках. С ростом числа потоков время увеличивается: сеть Бэтчера +выполняет порядка нескольких сотен синхронизированных проходов по массиву, и накладные расходы на барьеры +между проходами начинают преобладать над выигрышем от параллелизма. + +## 8. Conclusions + +OMP-версия ускоряет сортировку относительно baseline, наибольшее ускорение получено на двух потоках. +Дальнейшее увеличение числа потоков снижает эффективность из-за большого количества синхронизаций в сети +Бэтчера. + +## 9. References + +1. OpenMP Architecture Review Board. OpenMP Application Programming Interface. +2. oneAPI Threading Building Blocks Documentation. +3. Microsoft MPI Documentation. +4. ISO C++ Standard Library Documentation: `std::thread`. + +## Appendix (Optional) + +```cpp +void TitaevSortirovkaBetcheraOMP::BatcherSort() { + auto &result = GetOutput(); + const size_t n = result.size(); + if (n < 2) { + return; + } + + for (size_t k = 2; k <= n; k <<= 1) { + for (size_t j = k >> 1; j > 0; j >>= 1) { +#pragma omp parallel for + for (long long ii = 0; ii < static_cast(n); ii++) { + const size_t i = static_cast(ii); + const size_t l = i ^ j; + if (l > i) { + const bool ascending = ((i & k) == 0); + const bool need_swap = ascending ? (result[i] > result[l]) : (result[i] < result[l]); + if (need_swap) { + std::swap(result[i], result[l]); + } + } + } + } + } +} +``` diff --git a/tasks/titaev_m_sortirovka_betchera/omp/src/ops_omp.cpp b/tasks/titaev_m_sortirovka_betchera/omp/src/ops_omp.cpp index 444c98bf88..bcae682b40 100644 --- a/tasks/titaev_m_sortirovka_betchera/omp/src/ops_omp.cpp +++ b/tasks/titaev_m_sortirovka_betchera/omp/src/ops_omp.cpp @@ -12,6 +12,7 @@ namespace titaev_m_sortirovka_betchera { namespace { + uint64_t DoubleToOrderedUint(double value) { uint64_t x = 0; std::memcpy(&x, &value, sizeof(double)); @@ -35,54 +36,6 @@ double OrderedUintToDouble(uint64_t x) { std::memcpy(&result, &x, sizeof(double)); return result; } - -void RadixPass(int pass, size_t n, const std::vector &src, std::vector &dest) { - constexpr int kBits = 8; - constexpr int kBuckets = 256; - int num_threads = omp_get_max_threads(); - std::vector> local_counts(num_threads, std::vector(kBuckets, 0)); - -#pragma omp parallel default(none) shared(src, local_counts, n, pass, kBits) - { - int tid = omp_get_thread_num(); -#pragma omp for - for (size_t i = 0; i < n; ++i) { - size_t bucket = (src[i] >> (static_cast(pass) * kBits)) & 255; - local_counts[static_cast(tid)][bucket]++; - } - } - - std::vector common_counts(kBuckets, 0); - for (const auto &l_count : local_counts) { - for (size_t b_idx = 0; b_idx < kBuckets; ++b_idx) { - common_counts[b_idx] += l_count[b_idx]; - } - } - - std::vector prefixes(kBuckets, 0); - for (size_t b_idx = 1; b_idx < kBuckets; ++b_idx) { - prefixes[b_idx] = prefixes[b_idx - 1] + common_counts[b_idx - 1]; - } - - std::vector> offsets(num_threads, std::vector(kBuckets)); - for (size_t b_idx = 0; b_idx < kBuckets; ++b_idx) { - size_t curr = prefixes[b_idx]; - for (int t_idx = 0; t_idx < num_threads; ++t_idx) { - offsets[static_cast(t_idx)][b_idx] = curr; - curr += local_counts[static_cast(t_idx)][b_idx]; - } - } - -#pragma omp parallel default(none) shared(src, dest, offsets, n, pass, kBits) - { - int tid = omp_get_thread_num(); -#pragma omp for - for (size_t i = 0; i < n; ++i) { - size_t bucket = (src[i] >> (static_cast(pass) * kBits)) & 255; - dest[offsets[static_cast(tid)][bucket]++] = src[i]; - } - } -} } // namespace TitaevSortirovkaBetcheraOMP::TitaevSortirovkaBetcheraOMP(const InType &in) { @@ -102,55 +55,90 @@ bool TitaevSortirovkaBetcheraOMP::PreProcessingImpl() { void TitaevSortirovkaBetcheraOMP::ConvertToKeys(const InType &input, std::vector &keys) { const size_t n = input.size(); -#pragma omp parallel for default(none) shared(keys, input, n) - for (size_t i = 0; i < n; i++) { +#pragma omp parallel for + for (long long i = 0; i < static_cast(n); i++) { keys[i] = DoubleToOrderedUint(input[i]); } } -void TitaevSortirovkaBetcheraOMP::RadixSortParallel(std::vector &keys) { +void TitaevSortirovkaBetcheraOMP::RadixSort(std::vector &keys) { const size_t n = keys.size(); if (n <= 1) { return; } + + constexpr int kBits = 8; + constexpr int kBuckets = 1 << kBits; + constexpr int kPasses = 64 / kBits; + std::vector tmp(n); - for (int pass = 0; pass < 8; ++pass) { - if (pass % 2 == 0) { - RadixPass(pass, n, keys, tmp); - } else { - RadixPass(pass, n, tmp, keys); + + for (int pass = 0; pass < kPasses; pass++) { + std::vector count(kBuckets, 0); + + const int num_threads = omp_get_max_threads(); + std::vector> local_count(num_threads, std::vector(kBuckets, 0)); + +#pragma omp parallel + { + const int tid = omp_get_thread_num(); + auto &lc = local_count[tid]; +#pragma omp for + for (long long i = 0; i < static_cast(n); i++) { + size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + lc[bucket]++; + } + } + + for (int b = 0; b < kBuckets; b++) { + for (int t = 0; t < num_threads; t++) { + count[b] += local_count[t][b]; + } + } + + for (int i = 1; i < kBuckets; i++) { + count[i] += count[i - 1]; } + + for (size_t i = n; i-- > 0;) { + size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + tmp[--count[bucket]] = keys[i]; + } + + keys.swap(tmp); } } void TitaevSortirovkaBetcheraOMP::ConvertFromKeys(const std::vector &keys, OutType &output) { const size_t n = keys.size(); output.resize(n); -#pragma omp parallel for default(none) shared(keys, output, n) - for (size_t i = 0; i < n; ++i) { +#pragma omp parallel for + for (long long i = 0; i < static_cast(n); i++) { output[i] = OrderedUintToDouble(keys[i]); } } -void TitaevSortirovkaBetcheraOMP::BatcherStepParallel(OutType &result, size_t n, size_t step, size_t stage) { -#pragma omp parallel for default(none) shared(result, n, step, stage) - for (size_t i = 0; i < n; ++i) { - size_t j = i ^ stage; - if (j > i && j < n) { - const bool ascending = (i & step) == 0; - if (ascending ? (result[i] > result[j]) : (result[i] < result[j])) { - std::swap(result[i], result[j]); - } - } - } -} - -void TitaevSortirovkaBetcheraOMP::BatcherSortParallel() { +void TitaevSortirovkaBetcheraOMP::BatcherSort() { auto &result = GetOutput(); const size_t n = result.size(); - for (size_t step = 1; step < n; step <<= 1) { - for (size_t stage = step; stage > 0; stage >>= 1) { - BatcherStepParallel(result, n, step, stage); + if (n < 2) { + return; + } + + for (size_t k = 2; k <= n; k <<= 1) { + for (size_t j = k >> 1; j > 0; j >>= 1) { +#pragma omp parallel for + for (long long ii = 0; ii < static_cast(n); ii++) { + const size_t i = static_cast(ii); + const size_t l = i ^ j; + if (l > i) { + const bool ascending = ((i & k) == 0); + const bool need_swap = ascending ? (result[i] > result[l]) : (result[i] < result[l]); + if (need_swap) { + std::swap(result[i], result[l]); + } + } + } } } } @@ -161,14 +149,12 @@ bool TitaevSortirovkaBetcheraOMP::RunImpl() { if (n <= 1) { return true; } - std::vector keys(n); ConvertToKeys(input, keys); - RadixSortParallel(keys); + RadixSort(keys); ConvertFromKeys(keys, GetOutput()); - if ((n & (n - 1)) == 0) { - BatcherSortParallel(); + BatcherSort(); } return true; } diff --git a/tasks/titaev_m_sortirovka_betchera/report.md b/tasks/titaev_m_sortirovka_betchera/report.md new file mode 100644 index 0000000000..25a9cb2bf2 --- /dev/null +++ b/tasks/titaev_m_sortirovka_betchera/report.md @@ -0,0 +1,308 @@ +# Поразрядная сортировка для вещественных чисел (тип double) с чётно-нечётным слиянием Бэтчера + +- Student: Титаев М., group 3823Б1ФИ1 +- Technology: SEQ, OMP, TBB, STL, ALL +- Variant: 20 + +## 1. Introduction + +Работа посвящена сортировке массива вещественных чисел типа `double` с помощью поразрядной сортировки +(radix sort) и последующего применения сети чётно-нечётного слияния Бэтчера. Поразрядная сортировка +обеспечивает линейную по числу элементов сложность за счёт обработки чисел по разрядам, а сеть Бэтчера +служит детерминированной сортирующей сетью, удобной для параллельной реализации. В работе реализованы +пять вариантов одной задачи: последовательный baseline и параллельные версии на OMP, TBB, STL и ALL. + +Ожидаемый результат работы — корректно отсортированный массив и сравнение времени выполнения разных +технологий параллелизма на одинаковой постановке задачи. + +## 2. Problem Statement + +Входные данные задаются типом `InType = std::vector` — массив вещественных чисел. + +Выходные данные: `OutType = std::vector` — тот же массив, отсортированный по неубыванию. + +Ключевая идея — преобразовать каждое число `double` в беззнаковый 64-битный ключ с сохранением порядка, +отсортировать ключи поразрядной сортировкой и при размере, равном степени двойки, применить сеть Бэтчера. +Ограничение: входной массив не должен быть пустым. + +## 3. Baseline Algorithm (Sequential) + +Последовательная версия служит baseline для всех остальных реализаций. Алгоритм состоит из трёх этапов. + +На первом этапе каждое число `double` преобразуется в 64-битный беззнаковый ключ `uint64_t` так, чтобы +лексикографический порядок ключей совпадал с числовым порядком исходных чисел. Для этого у положительных +чисел инвертируется знаковый бит, а у отрицательных инвертируются все биты. + +На втором этапе выполняется поразрядная сортировка ключей по основанию 256 (по 8 бит за проход, всего +8 проходов на 64 бита). Каждый проход использует счётную сортировку: подсчёт количества элементов в +корзинах, префиксные суммы и стабильное распределение по временному массиву. + +На третьем этапе ключи преобразуются обратно в `double`. Если размер массива является степенью двойки, +дополнительно применяется сеть чётно-нечётного слияния Бэтчера, реализованная как битоническая +сортировка с обменами по индексам, отличающимся на один бит. + +## 4. Parallelization Scheme + +- SEQ: один поток, параллелизм не используется. +- OMP: параллельное преобразование ключей, подсчёт корзин с локальными гистограммами по потокам и + параллельные проходы сети Бэтчера через `#pragma omp parallel for`. +- TBB: `oneapi::tbb::parallel_for` по `blocked_range` на этапах преобразования и в сети Бэтчера. +- STL: ручное разбиение диапазона между `std::thread`, локальные гистограммы и объединение после + `join()`. +- ALL: MPI-окружение для запуска под несколькими процессами, внутри каждого процесса OpenMP-параллелизм + на этапах поразрядной сортировки и сети Бэтчера. + +## 5. Implementation Details + +Код задачи расположен в папке `tasks/titaev_m_sortirovka_betchera`: + +- `common/include/common.hpp` — типы `InType`, `OutType`, `TestType`, `BaseTask`; +- `seq/src/ops_seq.cpp` — последовательный baseline; +- `omp/src/ops_omp.cpp` — OpenMP-версия; +- `tbb/src/ops_tbb.cpp` — TBB-версия; +- `stl/src/ops_stl.cpp` — STL-версия; +- `all/src/ops_all.cpp` — гибридная версия ALL; +- `tests/functional/main.cpp` — функциональные тесты; +- `tests/performance/main.cpp` — performance-тесты. + +Память используется экономно: для поразрядной сортировки выделяется один временный массив ключей и массив +счётчиков фиксированного размера на 256 корзин. + +## 6. Experimental Setup + +**Аппаратное обеспечение:** + +- **CPU:** 12th Gen Intel(R) Core(TM) i5-12450H (2.00 GHz, 8 ядер / 12 потоков) +- **RAM:** 16 ГБ +- **OS:** Windows 11 Pro x64 +- **MPI:** Microsoft MPI (MS-MPI) 10.1 + +**Инструменты:** + +- **Сборка:** CMake +- **Компилятор:** MSVC 19.x +- **Конфигурация:** Release + +**Окружение:** + +- **PPC_NUM_THREADS:** задаёт число потоков для OMP, TBB, STL и потоковой части ALL. +- **PPC_NUM_PROC / mpiexec -n:** задаёт число MPI-процессов для ALL. +- Для ALL конфигурация записывается в формате `ranks × threads`. + +**Генерация данных:** + +- Тесты генерируют входные данные автоматически. +- Для performance-теста используется массив из `2^20` элементов с убывающими значениями. +- Внешние файлы с данными не используются. + +## 7. Results and Discussion + +### 7.1 Correctness + +Корректность проверялась функциональными тестами из `tests/functional/main.cpp`. Выходной массив +проверяется на полную упорядоченность по неубыванию, а его размер сравнивается с размером входа. +Тестовые наборы включают как степени двойки, так и произвольные размеры, что покрывает оба пути +алгоритма: только поразрядную сортировку и поразрядную сортировку с последующим слиянием Бэтчера. + +### 7.2 Performance + +Используемые обозначения: + +```txt +time — время выполнения performance-теста; +speedup = time_seq / time_mode; +efficiency = speedup / workers; +workers — количество исполнителей: потоков для OMP/TBB/STL, ranks × threads для ALL. +``` + +| Mode | Count | Time, s | Speedup | Efficiency | +| --- | --- | --- | --- | --- | +| seq | 1 | 0.159970 | 1.00 | N/A | +| omp | 2 | 0.056191 | 2.85 | 142.34% | +| omp | 4 | 0.063745 | 2.51 | 62.73% | +| omp | 8 | 0.080437 | 1.99 | 24.86% | +| tbb | 2 | 0.123646 | 1.29 | 64.69% | +| tbb | 4 | 0.074043 | 2.16 | 54.01% | +| tbb | 8 | 0.063984 | 2.50 | 31.25% | +| stl | 2 | 0.179218 | 0.89 | 44.62% | +| stl | 4 | 0.164399 | 0.97 | 24.33% | +| stl | 8 | 0.165794 | 0.96 | 12.06% | +| all | 2 × 1 | 0.079706 | 2.01 | 100.33% | +| all | 2 × 2 | 0.099306 | 1.61 | 40.27% | +| all | 4 × 2 | 0.227350 | 0.70 | 8.79% | + +OMP даёт наибольшее ускорение на двух потоках и теряет эффективность при дальнейшем росте числа потоков. +TBB масштабируется монотонно и приближается к результату OMP на восьми потоках. STL не показывает +ускорения, поскольку число рабочих потоков определяется аппаратной конфигурацией, а не заданным +параметром, и потоки пересоздаются на каждом шаге сети Бэтчера. ALL даёт ускорение в конфигурации +`2 × 1`, но проседает при росте числа процессов из-за накладных расходов на координацию MPI. Общая +причина ограниченной масштабируемости — большое число синхронизированных проходов в сети Бэтчера, +растущее логарифмически от размера массива. + +## 8. Conclusions + +Реализация сортирует массив `double` через поразрядную сортировку и сеть чётно-нечётного слияния Бэтчера. +Последовательная версия используется как baseline, а параллельные версии сравниваются с ней по времени +выполнения, ускорению и эффективности. Наилучшее ускорение получено у OMP на двух потоках и у ALL в +конфигурации `2 × 1`. Основное ограничение масштабируемости связано с большим числом точек синхронизации +в сети Бэтчера и накладными расходами на управление параллелизмом. + +## 9. References + +1. OpenMP Architecture Review Board. OpenMP Application Programming Interface. +2. oneAPI Threading Building Blocks Documentation. +3. Microsoft MPI Documentation. +4. ISO C++ Standard Library Documentation: `std::thread`. + +## Appendix (Optional) + +Ниже приведены основные фрагменты `RunImpl()` и сети Бэтчера для всех реализаций. + +### SEQ RunImpl + +```cpp +bool TitaevSortirovkaBetcheraSEQ::RunImpl() { + auto &input = GetInput(); + const size_t n = input.size(); + if (n <= 1) { + return true; + } + + std::vector keys(n); + ConvertToKeys(input, keys); + RadixSort(keys); + + ConvertFromKeys(keys, GetOutput()); + + if ((n & (n - 1)) == 0) { + BatcherSort(); + } + + return true; +} +``` + +### OMP BatcherSort + +```cpp +void TitaevSortirovkaBetcheraOMP::BatcherSort() { + auto &result = GetOutput(); + const size_t n = result.size(); + if (n < 2) { + return; + } + + for (size_t k = 2; k <= n; k <<= 1) { + for (size_t j = k >> 1; j > 0; j >>= 1) { +#pragma omp parallel for + for (long long ii = 0; ii < static_cast(n); ii++) { + const size_t i = static_cast(ii); + const size_t l = i ^ j; + if (l > i) { + const bool ascending = ((i & k) == 0); + const bool need_swap = ascending ? (result[i] > result[l]) : (result[i] < result[l]); + if (need_swap) { + std::swap(result[i], result[l]); + } + } + } + } + } +} +``` + +### TBB BatcherSort + +```cpp +void TitaevSortirovkaBetcheraTBB::BatcherSort() { + auto &result = GetOutput(); + const size_t n = result.size(); + if (n < 2) { + return; + } + + for (size_t k = 2; k <= n; k <<= 1) { + for (size_t j = k >> 1; j > 0; j >>= 1) { + oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, n), + [&](const oneapi::tbb::blocked_range &r) { + for (size_t i = r.begin(); i < r.end(); i++) { + const size_t l = i ^ j; + if (l > i && l < n) { + const bool ascending = ((i & k) == 0); + const bool need_swap = + ascending ? (result[i] > result[l]) : (result[i] < result[l]); + if (need_swap) { + std::swap(result[i], result[l]); + } + } + } + }); + } + } +} +``` + +### STL BatcherSort + +```cpp +void TitaevSortirovkaBetcheraSTL::BatcherSort() { + auto &result = GetOutput(); + const size_t n = result.size(); + if (n < 2) { + return; + } + + const unsigned int num_threads = GetThreadCount(); + + for (size_t k = 2; k <= n; k <<= 1) { + for (size_t j = k >> 1; j > 0; j >>= 1) { + std::vector threads; + threads.reserve(num_threads); + const size_t chunk = (n + num_threads - 1) / num_threads; + + for (unsigned int t = 0; t < num_threads; t++) { + const size_t begin = t * chunk; + const size_t end = std::min(begin + chunk, n); + if (begin >= end) { + break; + } + threads.emplace_back([&result, n, k, j, begin, end]() { + for (size_t i = begin; i < end; i++) { + const size_t l = i ^ j; + if (l > i && l < n) { + const bool ascending = ((i & k) == 0); + const bool need_swap = ascending ? (result[i] > result[l]) : (result[i] < result[l]); + if (need_swap) { + std::swap(result[i], result[l]); + } + } + } + }); + } + for (auto &th : threads) { + th.join(); + } + } + } +} +``` + +### ALL RunImpl + +```cpp +bool TitaevSortirovkaBetcheraALL::RunImpl() { + auto &input = GetInput(); + const size_t n = input.size(); + if (n <= 1) { + return true; + } + std::vector keys(n); + ConvertToKeys(input, keys); + RadixSort(keys); + ConvertFromKeys(keys, GetOutput()); + if ((n & (n - 1)) == 0) { + BatcherSort(); + } + return true; +} +``` diff --git a/tasks/titaev_m_sortirovka_betchera/seq/include/ops_seq.hpp b/tasks/titaev_m_sortirovka_betchera/seq/include/ops_seq.hpp index 85df666bb0..b91f5233b3 100644 --- a/tasks/titaev_m_sortirovka_betchera/seq/include/ops_seq.hpp +++ b/tasks/titaev_m_sortirovka_betchera/seq/include/ops_seq.hpp @@ -25,8 +25,6 @@ class TitaevSortirovkaBetcheraSEQ : public BaseTask { static void RadixSort(std::vector &keys); static void ConvertFromKeys(const std::vector &keys, OutType &output); void BatcherSort(); - - static void BatcherStep(OutType &result, size_t n, size_t step, size_t stage); }; } // namespace titaev_m_sortirovka_betchera diff --git a/tasks/titaev_m_sortirovka_betchera/seq/report.md b/tasks/titaev_m_sortirovka_betchera/seq/report.md new file mode 100644 index 0000000000..018eb5ff0d --- /dev/null +++ b/tasks/titaev_m_sortirovka_betchera/seq/report.md @@ -0,0 +1,154 @@ +# Поразрядная сортировка для вещественных чисел с чётно-нечётным слиянием Бэтчера - SEQ + +- Student: Титаев М., group 3823Б1ФИ1 +- Technology: SEQ +- Variant: 20 + +## 1. Introduction + +Работа посвящена сортировке массива вещественных чисел типа `double` с помощью поразрядной сортировки +(radix sort) и последующего применения сети чётно-нечётного слияния Бэтчера. Поразрядная сортировка +обеспечивает линейную по числу элементов сложность за счёт обработки чисел по разрядам, а сеть Бэтчера +служит детерминированной сортирующей сетью, удобной для параллельной реализации. + +В работе реализованы пять вариантов одной задачи: последовательный baseline и параллельные версии на +OMP, TBB, STL и ALL. SEQ-версия используется как эталон корректности и точка отсчёта для измерения +ускорения. + +## 2. Problem Statement + +Входные данные задаются типом `InType = std::vector` — массив вещественных чисел. + +Выходные данные: `OutType = std::vector` — тот же массив, отсортированный по неубыванию. + +Требуется получить корректно отсортированную последовательность для произвольного входного массива. +Ограничение: массив не должен быть пустым. + +## 3. Baseline Algorithm (Sequential) + +Последовательная версия служит baseline для всех остальных реализаций. Алгоритм состоит из трёх этапов. + +На первом этапе каждое число `double` преобразуется в 64-битный беззнаковый ключ `uint64_t` так, чтобы +лексикографический порядок ключей совпадал с числовым порядком исходных чисел. Для этого у положительных +чисел инвертируется знаковый бит, а у отрицательных инвертируются все биты. + +На втором этапе выполняется поразрядная сортировка ключей по основанию 256 (по 8 бит за проход, всего +8 проходов на 64 бита). Каждый проход использует счётную сортировку: подсчёт количества элементов в +корзинах, префиксные суммы и стабильное распределение по временному массиву. + +На третьем этапе ключи преобразуются обратно в `double`. Если размер массива является степенью двойки, +дополнительно применяется сеть чётно-нечётного слияния Бэтчера, реализованная как битоническая +сортировка с обменами по индексам, отличающимся на один бит. + +## 4. Parallelization Scheme + +- SEQ: один поток, параллелизм не используется. +- OMP: распараллеливание подсчёта корзин (локальные гистограммы по потокам) и проходов сети Бэтчера. +- TBB: `oneapi::tbb::parallel_for` по `blocked_range` на этапах преобразования и в сети Бэтчера. +- STL: ручное разбиение диапазона между `std::thread` с локальными гистограммами. +- ALL: MPI-окружение плюс OpenMP-параллелизм внутри процесса на этапах сортировки и слияния. + +## 5. Implementation Details + +Код задачи расположен в папке `tasks/titaev_m_sortirovka_betchera`: + +- `common/include/common.hpp` — типы `InType`, `OutType`, `TestType`, `BaseTask`; +- `seq/src/ops_seq.cpp` — последовательный baseline; +- `omp/src/ops_omp.cpp` — OpenMP-версия; +- `tbb/src/ops_tbb.cpp` — TBB-версия; +- `stl/src/ops_stl.cpp` — STL-версия; +- `all/src/ops_all.cpp` — гибридная версия ALL; +- `tests/functional/main.cpp` — функциональные тесты; +- `tests/performance/main.cpp` — performance-тесты. + +Память используется экономно: для поразрядной сортировки выделяется один временный массив ключей и массив +счётчиков фиксированного размера на 256 корзин. + +## 6. Experimental Setup + +**Аппаратное обеспечение:** + +- **CPU:** 12th Gen Intel(R) Core(TM) i5-12450H (2.00 GHz, 8 ядер / 12 потоков) +- **RAM:** 16 ГБ +- **OS:** Windows 11 Pro x64 +- **MPI:** Microsoft MPI (MS-MPI) 10.1 + +**Инструменты:** + +- **Сборка:** CMake +- **Компилятор:** MSVC 19.x +- **Конфигурация:** Release + +**Окружение:** + +- **PPC_NUM_THREADS:** задаёт число потоков для OMP, TBB, STL и потоковой части ALL. +- **PPC_NUM_PROC / mpiexec -n:** задаёт число MPI-процессов для ALL. +- Для ALL конфигурация записывается в формате `ranks × threads`. + +**Генерация данных:** + +- Тесты генерируют входные данные автоматически. +- Для performance-теста используется массив из `2^20` элементов с убывающими значениями. +- Внешние файлы с данными не используются. + +## 7. Results and Discussion + +### 7.1 Correctness + +Корректность проверялась функциональными тестами из `tests/functional/main.cpp`. Выходной массив +проверяется на полную упорядоченность по неубыванию, а его размер сравнивается с размером входа. +Тестовые наборы включают как степени двойки, так и произвольные размеры, что покрывает оба пути +алгоритма: только поразрядную сортировку и поразрядную сортировку с последующим слиянием Бэтчера. + +### 7.2 Performance + +Используемые обозначения: + +```txt +time — время выполнения performance-теста; +speedup = time_seq / time_mode; +efficiency = speedup / workers; +workers — количество исполнителей: потоков для OMP/TBB/STL, ranks × threads для ALL. +``` + +| Mode | Count | Time, s | Speedup | Efficiency | +| --- | --- | --- | --- | --- | +| seq | 1 | 0.159970 | 1.00 | N/A | + +## 8. Conclusions + +Последовательная версия реализует сортировку массива `double` через поразрядную сортировку и сеть +чётно-нечётного слияния Бэтчера. Она используется как baseline, относительно которого оцениваются +параллельные версии. Основное ограничение масштабируемости связано с большим числом синхронизированных +проходов в сети Бэтчера, которое растёт логарифмически от размера массива. + +## 9. References + +1. OpenMP Architecture Review Board. OpenMP Application Programming Interface. +2. oneAPI Threading Building Blocks Documentation. +3. Microsoft MPI Documentation. +4. ISO C++ Standard Library Documentation: `std::thread`. + +## Appendix (Optional) + +```cpp +bool TitaevSortirovkaBetcheraSEQ::RunImpl() { + auto &input = GetInput(); + const size_t n = input.size(); + if (n <= 1) { + return true; + } + + std::vector keys(n); + ConvertToKeys(input, keys); + RadixSort(keys); + + ConvertFromKeys(keys, GetOutput()); + + if ((n & (n - 1)) == 0) { + BatcherSort(); + } + + return true; +} +``` diff --git a/tasks/titaev_m_sortirovka_betchera/seq/src/ops_seq.cpp b/tasks/titaev_m_sortirovka_betchera/seq/src/ops_seq.cpp index 5fa12d4331..ce1cde53bb 100644 --- a/tasks/titaev_m_sortirovka_betchera/seq/src/ops_seq.cpp +++ b/tasks/titaev_m_sortirovka_betchera/seq/src/ops_seq.cpp @@ -104,28 +104,25 @@ void TitaevSortirovkaBetcheraSEQ::ConvertFromKeys(const std::vector &k } } -void TitaevSortirovkaBetcheraSEQ::BatcherStep(OutType &result, size_t n, size_t step, size_t stage) { - for (size_t i = 0; i < n; i++) { - size_t j = i ^ stage; - if (j <= i || j >= n) { - continue; - } - - const bool ascending = (i & step) == 0; - const bool need_swap = ascending ? result[i] > result[j] : result[i] < result[j]; - if (need_swap) { - std::swap(result[i], result[j]); - } - } -} - void TitaevSortirovkaBetcheraSEQ::BatcherSort() { auto &result = GetOutput(); const size_t n = result.size(); + if (n < 2) { + return; + } - for (size_t step = 1; step < n; step <<= 1) { - for (size_t stage = step; stage > 0; stage >>= 1) { - BatcherStep(result, n, step, stage); + for (size_t k = 2; k <= n; k <<= 1) { + for (size_t j = k >> 1; j > 0; j >>= 1) { + for (size_t i = 0; i < n; i++) { + const size_t l = i ^ j; + if (l > i) { + const bool ascending = ((i & k) == 0); + const bool need_swap = ascending ? (result[i] > result[l]) : (result[i] < result[l]); + if (need_swap) { + std::swap(result[i], result[l]); + } + } + } } } } diff --git a/tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp b/tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp new file mode 100644 index 0000000000..c1eeff4bf4 --- /dev/null +++ b/tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include + +#include "task/include/task.hpp" +#include "titaev_m_sortirovka_betchera/common/include/common.hpp" + +namespace titaev_m_sortirovka_betchera { + +class TitaevSortirovkaBetcheraSTL : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSTL; + } + explicit TitaevSortirovkaBetcheraSTL(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static void ConvertToKeys(const InType &input, std::vector &keys); + static void RadixSort(std::vector &keys); + static void ConvertFromKeys(const std::vector &keys, OutType &output); + void BatcherSort(); +}; + +} // namespace titaev_m_sortirovka_betchera diff --git a/tasks/titaev_m_sortirovka_betchera/stl/report.md b/tasks/titaev_m_sortirovka_betchera/stl/report.md new file mode 100644 index 0000000000..4f1e924327 --- /dev/null +++ b/tasks/titaev_m_sortirovka_betchera/stl/report.md @@ -0,0 +1,161 @@ +# Поразрядная сортировка для вещественных чисел с чётно-нечётным слиянием Бэтчера - STL + +- Student: Титаев М., group 3823Б1ФИ1 +- Technology: STL +- Variant: 20 + +## 1. Introduction + +STL-версия реализует параллелизм с помощью стандартной библиотеки потоков `std::thread`. Цель — вручную +разбить работу между потоками на этапах преобразования данных и сети чётно-нечётного слияния Бэтчера и +сравнить производительность с baseline. + +## 2. Problem Statement + +Задача совпадает с остальными версиями: отсортировать массив `InType = std::vector` по +неубыванию, результат имеет тип `OutType = std::vector`. + +## 3. Baseline Algorithm (Sequential) + +Baseline описан в `seq/report.md`. Он выполняет преобразование `double` в упорядоченные 64-битные ключи, +поразрядную сортировку по основанию 256 и обратное преобразование, после чего при размере, равном степени +двойки, применяет сеть Бэтчера. + +## 4. Parallelization Scheme + +STL-реализация распараллеливает следующие этапы: + +1. **Преобразование ключей:** диапазон индексов делится на непрерывные блоки, каждый обрабатывается +отдельным `std::thread`. +2. **Поразрядная сортировка:** на каждом проходе каждый поток заполняет собственную локальную гистограмму +корзин на своём блоке; гистограммы суммируются, после чего выполняется стабильное распределение. +3. **Сеть Бэтчера:** на каждом шаге сравнения диапазон индексов делится между потоками; пары индексов на +одном шаге не пересекаются, поэтому гонок нет. + +Объединение результатов выполняется после `join()` всех потоков. + +## 5. Implementation Details + +- Файлы: `stl/include/ops_stl.hpp`, `stl/src/ops_stl.cpp`. +- Класс: `TitaevSortirovkaBetcheraSTL`. +- Число потоков определяется через `std::thread::hardware_concurrency()`. +- Локальные гистограммы и поблочное разбиение исключают гонки данных. + +Память: один временный массив ключей, по одной гистограмме на каждый поток и вектор объектов потоков. + +## 6. Experimental Setup + +**Аппаратное обеспечение:** + +- **CPU:** 12th Gen Intel(R) Core(TM) i5-12450H (2.00 GHz, 8 ядер / 12 потоков) +- **RAM:** 16 ГБ +- **OS:** Windows 11 Pro x64 +- **MPI:** Microsoft MPI (MS-MPI) 10.1 + +**Инструменты:** + +- **Сборка:** CMake +- **Компилятор:** MSVC 19.x +- **Конфигурация:** Release + +**Окружение:** + +- **PPC_NUM_THREADS:** задаёт число потоков для OMP, TBB, STL и потоковой части ALL. +- **PPC_NUM_PROC / mpiexec -n:** задаёт число MPI-процессов для ALL. +- Для ALL конфигурация записывается в формате `ranks × threads`. + +**Генерация данных:** + +- Тесты генерируют входные данные автоматически. +- Для performance-теста используется массив из `2^20` элементов с убывающими значениями. +- Внешние файлы с данными не используются. + +## 7. Results and Discussion + +### 7.1 Correctness + +Корректность проверялась функциональными тестами из `tests/functional/main.cpp`. Выходной массив +проверяется на упорядоченность по неубыванию. Результаты STL-версии совпадают с baseline на всех тестовых +наборах. + +### 7.2 Performance + +Используемые обозначения: + +```txt +time — время выполнения performance-теста; +speedup = time_seq / time_mode; +efficiency = speedup / workers; +workers — количество исполнителей: потоков для OMP/TBB/STL, ranks × threads для ALL. +``` + +| Mode | Count | Time, s | Speedup | Efficiency | +| --- | --- | --- | --- | --- | +| seq | 1 | 0.159970 | 1.00 | N/A | +| stl | 2 | 0.179218 | 0.89 | 44.62% | +| stl | 4 | 0.164399 | 0.97 | 24.33% | +| stl | 8 | 0.165794 | 0.96 | 12.06% | + +Время STL-версии слабо зависит от заданного числа потоков, поскольку число рабочих потоков определяется +аппаратной конфигурацией процессора, а не переменной окружения. На больших массивах сеть Бэтчера создаёт +потоки в каждом из множества проходов, и затраты на создание и объединение потоков превышают выигрыш от +распараллеливания, поэтому ускорение относительно baseline отсутствует. + +## 8. Conclusions + +STL-версия на данной постановке не даёт ускорения относительно baseline. Основная причина — +многократное создание потоков на каждом шаге сети Бэтчера и независимость числа потоков от заданного +параметра. Для улучшения масштабируемости потребовался бы пул потоков и сокращение числа точек +синхронизации. + +## 9. References + +1. OpenMP Architecture Review Board. OpenMP Application Programming Interface. +2. oneAPI Threading Building Blocks Documentation. +3. Microsoft MPI Documentation. +4. ISO C++ Standard Library Documentation: `std::thread`. + +## Appendix (Optional) + +```cpp +void TitaevSortirovkaBetcheraSTL::BatcherSort() { + auto &result = GetOutput(); + const size_t n = result.size(); + if (n < 2) { + return; + } + + const unsigned int num_threads = GetThreadCount(); + + for (size_t k = 2; k <= n; k <<= 1) { + for (size_t j = k >> 1; j > 0; j >>= 1) { + std::vector threads; + threads.reserve(num_threads); + const size_t chunk = (n + num_threads - 1) / num_threads; + + for (unsigned int t = 0; t < num_threads; t++) { + const size_t begin = t * chunk; + const size_t end = std::min(begin + chunk, n); + if (begin >= end) { + break; + } + threads.emplace_back([&result, n, k, j, begin, end]() { + for (size_t i = begin; i < end; i++) { + const size_t l = i ^ j; + if (l > i && l < n) { + const bool ascending = ((i & k) == 0); + const bool need_swap = ascending ? (result[i] > result[l]) : (result[i] < result[l]); + if (need_swap) { + std::swap(result[i], result[l]); + } + } + } + }); + } + for (auto &th : threads) { + th.join(); + } + } + } +} +``` diff --git a/tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp b/tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp new file mode 100644 index 0000000000..d12a7f76c0 --- /dev/null +++ b/tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp @@ -0,0 +1,226 @@ +#include "titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp" + +#include +#include +#include +#include +#include + +#include "titaev_m_sortirovka_betchera/common/include/common.hpp" + +namespace titaev_m_sortirovka_betchera { + +namespace { + +uint64_t DoubleToOrderedUint(double value) { + uint64_t x = 0; + std::memcpy(&x, &value, sizeof(double)); + constexpr uint64_t kSignMask = (1ULL << 63); + if ((x & kSignMask) != 0ULL) { + x = ~x; + } else { + x ^= kSignMask; + } + return x; +} + +double OrderedUintToDouble(uint64_t x) { + constexpr uint64_t kSignMask = (1ULL << 63); + if ((x & kSignMask) != 0ULL) { + x ^= kSignMask; + } else { + x = ~x; + } + double result = 0.0; + std::memcpy(&result, &x, sizeof(double)); + return result; +} + +unsigned int GetThreadCount() { + unsigned int hw = std::thread::hardware_concurrency(); + return hw == 0 ? 1U : hw; +} +} // namespace + +TitaevSortirovkaBetcheraSTL::TitaevSortirovkaBetcheraSTL(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput().clear(); +} + +bool TitaevSortirovkaBetcheraSTL::ValidationImpl() { + return !GetInput().empty(); +} + +bool TitaevSortirovkaBetcheraSTL::PreProcessingImpl() { + GetOutput() = GetInput(); + return true; +} + +void TitaevSortirovkaBetcheraSTL::ConvertToKeys(const InType &input, std::vector &keys) { + const size_t n = input.size(); + const unsigned int num_threads = GetThreadCount(); + std::vector threads; + threads.reserve(num_threads); + const size_t chunk = (n + num_threads - 1) / num_threads; + + for (unsigned int t = 0; t < num_threads; t++) { + const size_t begin = t * chunk; + const size_t end = std::min(begin + chunk, n); + if (begin >= end) { + break; + } + threads.emplace_back([&input, &keys, begin, end]() { + for (size_t i = begin; i < end; i++) { + keys[i] = DoubleToOrderedUint(input[i]); + } + }); + } + for (auto &th : threads) { + th.join(); + } +} + +void TitaevSortirovkaBetcheraSTL::RadixSort(std::vector &keys) { + const size_t n = keys.size(); + if (n <= 1) { + return; + } + + constexpr int kBits = 8; + constexpr int kBuckets = 1 << kBits; + constexpr int kPasses = 64 / kBits; + + const unsigned int num_threads = GetThreadCount(); + std::vector tmp(n); + + for (int pass = 0; pass < kPasses; pass++) { + std::vector> local_count(num_threads, std::vector(kBuckets, 0)); + std::vector threads; + threads.reserve(num_threads); + const size_t chunk = (n + num_threads - 1) / num_threads; + + for (unsigned int t = 0; t < num_threads; t++) { + const size_t begin = t * chunk; + const size_t end = std::min(begin + chunk, n); + if (begin >= end) { + break; + } + threads.emplace_back([&keys, &local_count, t, begin, end, pass]() { + auto &lc = local_count[t]; + for (size_t i = begin; i < end; i++) { + size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + lc[bucket]++; + } + }); + } + for (auto &th : threads) { + th.join(); + } + + std::vector count(kBuckets, 0); + for (int b = 0; b < kBuckets; b++) { + for (unsigned int t = 0; t < num_threads; t++) { + count[b] += local_count[t][b]; + } + } + for (int i = 1; i < kBuckets; i++) { + count[i] += count[i - 1]; + } + + for (size_t i = n; i-- > 0;) { + size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + tmp[--count[bucket]] = keys[i]; + } + + keys.swap(tmp); + } +} + +void TitaevSortirovkaBetcheraSTL::ConvertFromKeys(const std::vector &keys, OutType &output) { + const size_t n = keys.size(); + output.resize(n); + const unsigned int num_threads = GetThreadCount(); + std::vector threads; + threads.reserve(num_threads); + const size_t chunk = (n + num_threads - 1) / num_threads; + + for (unsigned int t = 0; t < num_threads; t++) { + const size_t begin = t * chunk; + const size_t end = std::min(begin + chunk, n); + if (begin >= end) { + break; + } + threads.emplace_back([&keys, &output, begin, end]() { + for (size_t i = begin; i < end; i++) { + output[i] = OrderedUintToDouble(keys[i]); + } + }); + } + for (auto &th : threads) { + th.join(); + } +} + +void TitaevSortirovkaBetcheraSTL::BatcherSort() { + auto &result = GetOutput(); + const size_t n = result.size(); + if (n < 2) { + return; + } + + const unsigned int num_threads = GetThreadCount(); + + for (size_t k = 2; k <= n; k <<= 1) { + for (size_t j = k >> 1; j > 0; j >>= 1) { + std::vector threads; + threads.reserve(num_threads); + const size_t chunk = (n + num_threads - 1) / num_threads; + + for (unsigned int t = 0; t < num_threads; t++) { + const size_t begin = t * chunk; + const size_t end = std::min(begin + chunk, n); + if (begin >= end) { + break; + } + threads.emplace_back([&result, n, k, j, begin, end]() { + for (size_t i = begin; i < end; i++) { + const size_t l = i ^ j; + if (l > i && l < n) { + const bool ascending = ((i & k) == 0); + const bool need_swap = ascending ? (result[i] > result[l]) : (result[i] < result[l]); + if (need_swap) { + std::swap(result[i], result[l]); + } + } + } + }); + } + for (auto &th : threads) { + th.join(); + } + } + } +} + +bool TitaevSortirovkaBetcheraSTL::RunImpl() { + auto &input = GetInput(); + const size_t n = input.size(); + if (n <= 1) { + return true; + } + std::vector keys(n); + ConvertToKeys(input, keys); + RadixSort(keys); + ConvertFromKeys(keys, GetOutput()); + if ((n & (n - 1)) == 0) { + BatcherSort(); + } + return true; +} + +bool TitaevSortirovkaBetcheraSTL::PostProcessingImpl() { + return true; +} + +} // namespace titaev_m_sortirovka_betchera diff --git a/tasks/titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp b/tasks/titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp new file mode 100644 index 0000000000..9b4cb07704 --- /dev/null +++ b/tasks/titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include + +#include "task/include/task.hpp" +#include "titaev_m_sortirovka_betchera/common/include/common.hpp" + +namespace titaev_m_sortirovka_betchera { + +class TitaevSortirovkaBetcheraTBB : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kTBB; + } + explicit TitaevSortirovkaBetcheraTBB(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static void ConvertToKeys(const InType &input, std::vector &keys); + static void RadixSort(std::vector &keys); + static void ConvertFromKeys(const std::vector &keys, OutType &output); + void BatcherSort(); +}; + +} // namespace titaev_m_sortirovka_betchera diff --git a/tasks/titaev_m_sortirovka_betchera/tbb/report.md b/tasks/titaev_m_sortirovka_betchera/tbb/report.md new file mode 100644 index 0000000000..521ee858b1 --- /dev/null +++ b/tasks/titaev_m_sortirovka_betchera/tbb/report.md @@ -0,0 +1,145 @@ +# Поразрядная сортировка для вещественных чисел с чётно-нечётным слиянием Бэтчера - TBB + +- Student: Титаев М., group 3823Б1ФИ1 +- Technology: TBB +- Variant: 20 + +## 1. Introduction + +TBB-версия использует Intel oneAPI Threading Building Blocks для параллельного выполнения этапов +преобразования данных и сети чётно-нечётного слияния Бэтчера. Цель — оценить производительность +библиотеки задач TBB на данной постановке и сравнить её с baseline. + +## 2. Problem Statement + +Задача совпадает с остальными версиями: отсортировать массив `InType = std::vector` по +неубыванию, результат имеет тип `OutType = std::vector`. + +## 3. Baseline Algorithm (Sequential) + +Baseline описан в `seq/report.md`. Он выполняет преобразование `double` в упорядоченные 64-битные ключи, +поразрядную сортировку по основанию 256 и обратное преобразование, после чего при размере, равном степени +двойки, применяет сеть Бэтчера. + +## 4. Parallelization Scheme + +TBB-реализация распараллеливает следующие этапы: + +1. **Преобразование ключей:** прямое и обратное преобразование выполняется через +`oneapi::tbb::parallel_for` по `blocked_range`. +2. **Поразрядная сортировка:** проходы счётной сортировки выполняются последовательно, что сохраняет +стабильность распределения. +3. **Сеть Бэтчера:** каждый шаг сравнения распараллелен через `oneapi::tbb::parallel_for`; пары индексов +на одном шаге не пересекаются. + +Число потоков задаётся через `PPC_NUM_THREADS`. + +## 5. Implementation Details + +- Файлы: `tbb/include/ops_tbb.hpp`, `tbb/src/ops_tbb.cpp`. +- Класс: `TitaevSortirovkaBetcheraTBB`. +- Параллельные участки выражены через `parallel_for` и `blocked_range`. +- Проходы поразрядной сортировки оставлены последовательными ради стабильности. + +Память: один временный массив ключей и массив счётчиков на 256 корзин. + +## 6. Experimental Setup + +**Аппаратное обеспечение:** + +- **CPU:** 12th Gen Intel(R) Core(TM) i5-12450H (2.00 GHz, 8 ядер / 12 потоков) +- **RAM:** 16 ГБ +- **OS:** Windows 11 Pro x64 +- **MPI:** Microsoft MPI (MS-MPI) 10.1 + +**Инструменты:** + +- **Сборка:** CMake +- **Компилятор:** MSVC 19.x +- **Конфигурация:** Release + +**Окружение:** + +- **PPC_NUM_THREADS:** задаёт число потоков для OMP, TBB, STL и потоковой части ALL. +- **PPC_NUM_PROC / mpiexec -n:** задаёт число MPI-процессов для ALL. +- Для ALL конфигурация записывается в формате `ranks × threads`. + +**Генерация данных:** + +- Тесты генерируют входные данные автоматически. +- Для performance-теста используется массив из `2^20` элементов с убывающими значениями. +- Внешние файлы с данными не используются. + +## 7. Results and Discussion + +### 7.1 Correctness + +Корректность проверялась функциональными тестами из `tests/functional/main.cpp`. Выходной массив +проверяется на упорядоченность по неубыванию. Результаты TBB-версии совпадают с baseline на всех тестовых +наборах. + +### 7.2 Performance + +Используемые обозначения: + +```txt +time — время выполнения performance-теста; +speedup = time_seq / time_mode; +efficiency = speedup / workers; +workers — количество исполнителей: потоков для OMP/TBB/STL, ranks × threads для ALL. +``` + +| Mode | Count | Time, s | Speedup | Efficiency | +| --- | --- | --- | --- | --- | +| seq | 1 | 0.159970 | 1.00 | N/A | +| tbb | 2 | 0.123646 | 1.29 | 64.69% | +| tbb | 4 | 0.074043 | 2.16 | 54.01% | +| tbb | 8 | 0.063984 | 2.50 | 31.25% | + +Производительность TBB растёт с числом потоков и приближается к результату OMP при восьми потоках. +Накладные расходы на планирование задач TBB заметны на малом числе потоков, поэтому на двух потоках +ускорение невелико. + +## 8. Conclusions + +TBB-версия показывает рост производительности с увеличением числа потоков, наилучший результат получен на +восьми потоках. Эффективность снижается с ростом числа потоков из-за накладных расходов на планирование +задач и большого числа синхронизированных проходов в сети Бэтчера. + +## 9. References + +1. OpenMP Architecture Review Board. OpenMP Application Programming Interface. +2. oneAPI Threading Building Blocks Documentation. +3. Microsoft MPI Documentation. +4. ISO C++ Standard Library Documentation: `std::thread`. + +## Appendix (Optional) + +```cpp +void TitaevSortirovkaBetcheraTBB::BatcherSort() { + auto &result = GetOutput(); + const size_t n = result.size(); + if (n < 2) { + return; + } + + for (size_t k = 2; k <= n; k <<= 1) { + for (size_t j = k >> 1; j > 0; j >>= 1) { + oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, n), + [&](const oneapi::tbb::blocked_range &r) { + for (size_t i = r.begin(); i < r.end(); i++) { + const size_t l = i ^ j; + if (l > i && l < n) { + const bool ascending = ((i & k) == 0); + const bool need_swap = + ascending ? (result[i] > result[l]) : (result[i] < result[l]); + if (need_swap) { + std::swap(result[i], result[l]); + } + } + } + }); + } + } +} +``` diff --git a/tasks/titaev_m_sortirovka_betchera/tbb/src/ops_tbb.cpp b/tasks/titaev_m_sortirovka_betchera/tbb/src/ops_tbb.cpp new file mode 100644 index 0000000000..e5e6b7dc5b --- /dev/null +++ b/tasks/titaev_m_sortirovka_betchera/tbb/src/ops_tbb.cpp @@ -0,0 +1,151 @@ +#include "titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp" + +#include +#include + +#include +#include +#include +#include + +#include "titaev_m_sortirovka_betchera/common/include/common.hpp" + +namespace titaev_m_sortirovka_betchera { + +namespace { + +uint64_t DoubleToOrderedUint(double value) { + uint64_t x = 0; + std::memcpy(&x, &value, sizeof(double)); + constexpr uint64_t kSignMask = (1ULL << 63); + if ((x & kSignMask) != 0ULL) { + x = ~x; + } else { + x ^= kSignMask; + } + return x; +} + +double OrderedUintToDouble(uint64_t x) { + constexpr uint64_t kSignMask = (1ULL << 63); + if ((x & kSignMask) != 0ULL) { + x ^= kSignMask; + } else { + x = ~x; + } + double result = 0.0; + std::memcpy(&result, &x, sizeof(double)); + return result; +} +} // namespace + +TitaevSortirovkaBetcheraTBB::TitaevSortirovkaBetcheraTBB(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput().clear(); +} + +bool TitaevSortirovkaBetcheraTBB::ValidationImpl() { + return !GetInput().empty(); +} + +bool TitaevSortirovkaBetcheraTBB::PreProcessingImpl() { + GetOutput() = GetInput(); + return true; +} + +void TitaevSortirovkaBetcheraTBB::ConvertToKeys(const InType &input, std::vector &keys) { + const size_t n = input.size(); + oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, n), [&](const oneapi::tbb::blocked_range &r) { + for (size_t i = r.begin(); i < r.end(); i++) { + keys[i] = DoubleToOrderedUint(input[i]); + } + }); +} + +void TitaevSortirovkaBetcheraTBB::RadixSort(std::vector &keys) { + const size_t n = keys.size(); + if (n <= 1) { + return; + } + + constexpr int kBits = 8; + constexpr int kBuckets = 1 << kBits; + constexpr int kPasses = 64 / kBits; + + std::vector tmp(n); + + for (int pass = 0; pass < kPasses; pass++) { + std::vector count(kBuckets, 0); + for (size_t i = 0; i < n; i++) { + size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + count[bucket]++; + } + for (int i = 1; i < kBuckets; i++) { + count[i] += count[i - 1]; + } + for (size_t i = n; i-- > 0;) { + size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + tmp[--count[bucket]] = keys[i]; + } + keys.swap(tmp); + } +} + +void TitaevSortirovkaBetcheraTBB::ConvertFromKeys(const std::vector &keys, OutType &output) { + const size_t n = keys.size(); + output.resize(n); + oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, n), [&](const oneapi::tbb::blocked_range &r) { + for (size_t i = r.begin(); i < r.end(); i++) { + output[i] = OrderedUintToDouble(keys[i]); + } + }); +} + +void TitaevSortirovkaBetcheraTBB::BatcherSort() { + auto &result = GetOutput(); + const size_t n = result.size(); + if (n < 2) { + return; + } + + for (size_t k = 2; k <= n; k <<= 1) { + for (size_t j = k >> 1; j > 0; j >>= 1) { + oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, n), + [&](const oneapi::tbb::blocked_range &r) { + for (size_t i = r.begin(); i < r.end(); i++) { + const size_t l = i ^ j; + if (l > i && l < n) { + const bool ascending = ((i & k) == 0); + const bool need_swap = ascending ? (result[i] > result[l]) : (result[i] < result[l]); + if (need_swap) { + std::swap(result[i], result[l]); + } + } + } + }); + } + } +} + +bool TitaevSortirovkaBetcheraTBB::RunImpl() { + auto &input = GetInput(); + const size_t n = input.size(); + if (n <= 1) { + return true; + } + std::vector keys(n); + ConvertToKeys(input, keys); + RadixSort(keys); + ConvertFromKeys(keys, GetOutput()); + if ((n & (n - 1)) == 0) { + BatcherSort(); + } + return true; +} + +bool TitaevSortirovkaBetcheraTBB::PostProcessingImpl() { + return true; +} + +} // namespace titaev_m_sortirovka_betchera diff --git a/tasks/titaev_m_sortirovka_betchera/tests/functional/main.cpp b/tasks/titaev_m_sortirovka_betchera/tests/functional/main.cpp index 50e7e2be38..b384465b2b 100644 --- a/tasks/titaev_m_sortirovka_betchera/tests/functional/main.cpp +++ b/tasks/titaev_m_sortirovka_betchera/tests/functional/main.cpp @@ -1,91 +1,74 @@ #include +#include +#include #include -#include -#include -#include #include #include +#include #include -#include "task/include/task.hpp" +#include "titaev_m_sortirovka_betchera/all/include/ops_all.hpp" #include "titaev_m_sortirovka_betchera/common/include/common.hpp" #include "titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp" #include "titaev_m_sortirovka_betchera/seq/include/ops_seq.hpp" +#include "titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp" +#include "titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp" #include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" namespace titaev_m_sortirovka_betchera { -using TaskFactory = std::function>(InType)>; -using ParamType = std::tuple; - -class TitaevBatcherRadixFuncTests : public ppc::util::BaseRunFuncTests { +class TitaevSortirovkaBetcheraFuncTests : public ppc::util::BaseRunFuncTests { public: - static std::string PrintTestParam( - const testing::TestParamInfo &info) { - return std::get<1>(info.param); + static std::string PrintTestParam(const TestType &test_param) { + return std::to_string(std::get<0>(test_param)) + "_" + std::get<1>(test_param); } protected: - InType input; - void SetUp() override { - auto full_param = GetParam(); - TestType param = std::get<2>(full_param); - const int size = std::get<0>(param); - - std::random_device rd; - std::mt19937_64 gen(rd()); - std::uniform_real_distribution dist(-1000.0, 1000.0); - - input.resize(static_cast(size)); - for (auto &val : input) { - val = dist(gen); + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + size_ = std::get<0>(params); + input_data_.resize(static_cast(size_)); + for (int i = 0; i < size_; i++) { + input_data_[i] = static_cast(size_ - i) - 0.5; } } - bool CheckTestOutputData(OutType &output) final { - if (output.size() != input.size()) { + bool CheckTestOutputData(OutType &output_data) final { + if (output_data.size() != static_cast(size_)) { return false; } - for (size_t i = 1; i < output.size(); i++) { - if (output[i] < output[i - 1]) { - return false; - } - } - return true; + return std::is_sorted(output_data.begin(), output_data.end()); } InType GetTestInputData() final { - return input; + return input_data_; } + + private: + int size_ = 0; + InType input_data_; }; namespace { - -std::shared_ptr> CreateSeqTask(const InType &in) { - return std::make_shared(in); -} - -std::shared_ptr> CreateOmpTask(const InType &in) { - return std::make_shared(in); +TEST_P(TitaevSortirovkaBetcheraFuncTests, SortCorrectness) { + ExecuteTest(GetParam()); } -using ActualParamType = TitaevBatcherRadixFuncTests::ParamType; - -const std::vector kSeqParams = {{CreateSeqTask, "seq_size_128", TestType{128, "size_128"}}, - {CreateSeqTask, "seq_size_512", TestType{512, "size_512"}}, - {CreateSeqTask, "seq_size_1024", TestType{1024, "size_1024"}}}; - -const std::vector kOmpParams = {{CreateOmpTask, "omp_size_128", TestType{128, "size_128"}}, - {CreateOmpTask, "omp_size_512", TestType{512, "size_512"}}, - {CreateOmpTask, "omp_size_1024", TestType{1024, "size_1024"}}}; - -INSTANTIATE_TEST_SUITE_P(SequentialTests, TitaevBatcherRadixFuncTests, ::testing::ValuesIn(kSeqParams), - TitaevBatcherRadixFuncTests::PrintTestParam); +const std::array kTestParam = { + std::make_tuple(1, "N1"), std::make_tuple(2, "N2"), std::make_tuple(4, "N4"), std::make_tuple(8, "N8"), + std::make_tuple(16, "N16"), std::make_tuple(32, "N32"), std::make_tuple(7, "N7"), std::make_tuple(100, "N100")}; -INSTANTIATE_TEST_SUITE_P(OpenMPTests, TitaevBatcherRadixFuncTests, ::testing::ValuesIn(kOmpParams), - TitaevBatcherRadixFuncTests::PrintTestParam); +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_titaev_m_sortirovka_betchera), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_titaev_m_sortirovka_betchera), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_titaev_m_sortirovka_betchera), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_titaev_m_sortirovka_betchera), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_titaev_m_sortirovka_betchera)); +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); +const auto kFuncTestName = TitaevSortirovkaBetcheraFuncTests::PrintFuncTestName; +INSTANTIATE_TEST_SUITE_P(SortFuncTests, TitaevSortirovkaBetcheraFuncTests, kGtestValues, kFuncTestName); } // namespace } // namespace titaev_m_sortirovka_betchera diff --git a/tasks/titaev_m_sortirovka_betchera/tests/performance/main.cpp b/tasks/titaev_m_sortirovka_betchera/tests/performance/main.cpp index ccca55be9a..7394439127 100644 --- a/tasks/titaev_m_sortirovka_betchera/tests/performance/main.cpp +++ b/tasks/titaev_m_sortirovka_betchera/tests/performance/main.cpp @@ -1,60 +1,61 @@ #include +#include #include -#include +#include +#include +#include "titaev_m_sortirovka_betchera/all/include/ops_all.hpp" #include "titaev_m_sortirovka_betchera/common/include/common.hpp" #include "titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp" +#include "titaev_m_sortirovka_betchera/seq/include/ops_seq.hpp" +#include "titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp" +#include "titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp" #include "util/include/perf_test_util.hpp" namespace titaev_m_sortirovka_betchera { -class TitaevBatcherRadixPerfTests : public ppc::util::BaseRunPerfTests { +class TitaevSortirovkaBetcheraPerfTests : public ppc::util::BaseRunPerfTests { protected: - static constexpr size_t kSize = 1000000; - InType input; - void SetUp() override { - std::random_device rd; - std::mt19937_64 gen(rd()); - std::uniform_real_distribution dist(-100000.0, 100000.0); - - input.resize(kSize); - for (auto &val : input) { - val = dist(gen); + constexpr int kSize = 1 << 20; + input_data_.resize(kSize); + for (int i = 0; i < kSize; i++) { + input_data_[i] = static_cast(kSize - i) - 0.5; } } - bool CheckTestOutputData(OutType &output) final { - if (output.size() != input.size()) { - return false; - } - for (size_t i = 1; i < output.size(); i++) { - if (output[i] < output[i - 1]) { - return false; - } - } - return true; + bool CheckTestOutputData(OutType &output_data) final { + return std::is_sorted(output_data.begin(), output_data.end()); } InType GetTestInputData() final { - return input; + return input_data_; } + + private: + InType input_data_; }; -TEST_P(TitaevBatcherRadixPerfTests, RunPerformanceOMP) { +TEST_P(TitaevSortirovkaBetcheraPerfTests, RunPerfModes) { ExecuteTest(GetParam()); } namespace { - -const auto kPerfTasks = +const auto kSeqPerfTasks = + ppc::util::MakeAllPerfTasks(PPC_SETTINGS_titaev_m_sortirovka_betchera); +const auto kOmpPerfTasks = ppc::util::MakeAllPerfTasks(PPC_SETTINGS_titaev_m_sortirovka_betchera); - -const auto kValues = ppc::util::TupleToGTestValues(kPerfTasks); -const auto kNameGen = TitaevBatcherRadixPerfTests::CustomPerfTestName; - -INSTANTIATE_TEST_SUITE_P(PerformanceSortingTests, TitaevBatcherRadixPerfTests, kValues, kNameGen); - +const auto kTbbPerfTasks = + ppc::util::MakeAllPerfTasks(PPC_SETTINGS_titaev_m_sortirovka_betchera); +const auto kStlPerfTasks = + ppc::util::MakeAllPerfTasks(PPC_SETTINGS_titaev_m_sortirovka_betchera); +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks(PPC_SETTINGS_titaev_m_sortirovka_betchera); + +const auto kPerfTasks = std::tuple_cat(kSeqPerfTasks, kOmpPerfTasks, kTbbPerfTasks, kStlPerfTasks, kAllPerfTasks); +const auto kGtestValues = ppc::util::TupleToGTestValues(kPerfTasks); +const auto kPerfTestName = TitaevSortirovkaBetcheraPerfTests::CustomPerfTestName; +INSTANTIATE_TEST_SUITE_P(RunModeTests, TitaevSortirovkaBetcheraPerfTests, kGtestValues, kPerfTestName); } // namespace } // namespace titaev_m_sortirovka_betchera From 13da53985b921ffa32431ea8a015d6ce49cd8cf6 Mon Sep 17 00:00:00 2001 From: Makasimchik Date: Wed, 3 Jun 2026 21:38:37 +0300 Subject: [PATCH 2/5] Titaev_m_RP --- .../all/include/ops_all.hpp | 2 +- .../all/src/ops_all.cpp | 103 ++++---- .../omp/include/ops_omp.hpp | 2 +- .../omp/src/ops_omp.cpp | 101 ++++---- .../seq/include/ops_seq.hpp | 2 +- .../seq/src/ops_seq.cpp | 83 +++---- .../stl/include/ops_stl.hpp | 3 +- .../stl/src/ops_stl.cpp | 221 ++++++++---------- .../tbb/include/ops_tbb.hpp | 2 +- .../tbb/src/ops_tbb.cpp | 90 +++---- .../tests/functional/main.cpp | 8 +- .../tests/performance/main.cpp | 3 +- 12 files changed, 312 insertions(+), 308 deletions(-) diff --git a/tasks/titaev_m_sortirovka_betchera/all/include/ops_all.hpp b/tasks/titaev_m_sortirovka_betchera/all/include/ops_all.hpp index 76f097bb3b..6d5872ba01 100644 --- a/tasks/titaev_m_sortirovka_betchera/all/include/ops_all.hpp +++ b/tasks/titaev_m_sortirovka_betchera/all/include/ops_all.hpp @@ -1,5 +1,4 @@ #pragma once -#include #include #include @@ -25,6 +24,7 @@ class TitaevSortirovkaBetcheraALL : public BaseTask { static void RadixSort(std::vector &keys); static void ConvertFromKeys(const std::vector &keys, OutType &output); void BatcherSort(); + static void BatcherStage(OutType &result, std::size_t array_size, std::size_t block, std::size_t step); }; } // namespace titaev_m_sortirovka_betchera diff --git a/tasks/titaev_m_sortirovka_betchera/all/src/ops_all.cpp b/tasks/titaev_m_sortirovka_betchera/all/src/ops_all.cpp index 3a3bd2be82..2a0b266cf6 100644 --- a/tasks/titaev_m_sortirovka_betchera/all/src/ops_all.cpp +++ b/tasks/titaev_m_sortirovka_betchera/all/src/ops_all.cpp @@ -14,28 +14,29 @@ namespace titaev_m_sortirovka_betchera { namespace { uint64_t DoubleToOrderedUint(double value) { - uint64_t x = 0; - std::memcpy(&x, &value, sizeof(double)); + uint64_t bits = 0; + std::memcpy(&bits, &value, sizeof(double)); constexpr uint64_t kSignMask = (1ULL << 63); - if ((x & kSignMask) != 0ULL) { - x = ~x; + if ((bits & kSignMask) != 0ULL) { + bits = ~bits; } else { - x ^= kSignMask; + bits ^= kSignMask; } - return x; + return bits; } -double OrderedUintToDouble(uint64_t x) { +double OrderedUintToDouble(uint64_t bits) { constexpr uint64_t kSignMask = (1ULL << 63); - if ((x & kSignMask) != 0ULL) { - x ^= kSignMask; + if ((bits & kSignMask) != 0ULL) { + bits ^= kSignMask; } else { - x = ~x; + bits = ~bits; } double result = 0.0; - std::memcpy(&result, &x, sizeof(double)); + std::memcpy(&result, &bits, sizeof(double)); return result; } + } // namespace TitaevSortirovkaBetcheraALL::TitaevSortirovkaBetcheraALL(const InType &in) { @@ -54,15 +55,15 @@ bool TitaevSortirovkaBetcheraALL::PreProcessingImpl() { } void TitaevSortirovkaBetcheraALL::ConvertToKeys(const InType &input, std::vector &keys) { - const size_t n = input.size(); -#pragma omp parallel for - for (long long i = 0; i < static_cast(n); i++) { + const auto n = static_cast(input.size()); +#pragma omp parallel for default(none) shared(input, keys, n) + for (int64_t i = 0; i < n; i++) { keys[i] = DoubleToOrderedUint(input[i]); } } void TitaevSortirovkaBetcheraALL::RadixSort(std::vector &keys) { - const size_t n = keys.size(); + const std::size_t n = keys.size(); if (n <= 1) { return; } @@ -72,76 +73,86 @@ void TitaevSortirovkaBetcheraALL::RadixSort(std::vector &keys) { constexpr int kPasses = 64 / kBits; std::vector tmp(n); + const auto signed_n = static_cast(n); for (int pass = 0; pass < kPasses; pass++) { - std::vector count(kBuckets, 0); + std::vector count(kBuckets, 0); const int num_threads = omp_get_max_threads(); - std::vector> local_count(num_threads, std::vector(kBuckets, 0)); + std::vector> local_count(num_threads, std::vector(kBuckets, 0)); -#pragma omp parallel +#pragma omp parallel default(none) shared(keys, local_count, signed_n, pass) { const int tid = omp_get_thread_num(); auto &lc = local_count[tid]; #pragma omp for - for (long long i = 0; i < static_cast(n); i++) { - size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + for (int64_t i = 0; i < signed_n; i++) { + const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); lc[bucket]++; } } - for (int b = 0; b < kBuckets; b++) { - for (int t = 0; t < num_threads; t++) { - count[b] += local_count[t][b]; + for (int bucket_idx = 0; bucket_idx < kBuckets; bucket_idx++) { + for (int thr = 0; thr < num_threads; thr++) { + count[bucket_idx] += local_count[thr][bucket_idx]; } } + for (int i = 1; i < kBuckets; i++) { count[i] += count[i - 1]; } - for (size_t i = n; i-- > 0;) { - size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + + for (std::size_t i = n; i-- > 0;) { + const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); tmp[--count[bucket]] = keys[i]; } + keys.swap(tmp); } } void TitaevSortirovkaBetcheraALL::ConvertFromKeys(const std::vector &keys, OutType &output) { - const size_t n = keys.size(); - output.resize(n); -#pragma omp parallel for - for (long long i = 0; i < static_cast(n); i++) { + const auto n = static_cast(keys.size()); + output.resize(keys.size()); +#pragma omp parallel for default(none) shared(keys, output, n) + for (int64_t i = 0; i < n; i++) { output[i] = OrderedUintToDouble(keys[i]); } } +void TitaevSortirovkaBetcheraALL::BatcherStage(OutType &result, std::size_t array_size, std::size_t block, + std::size_t step) { + const auto signed_size = static_cast(array_size); +#pragma omp parallel for default(none) shared(result, signed_size, block, step) + for (int64_t i = 0; i < signed_size; i++) { + const auto idx = static_cast(i); + const std::size_t partner = idx ^ step; + if (partner <= idx) { + continue; + } + const bool ascending = ((idx & block) == 0); + const bool need_swap = ascending ? (result[idx] > result[partner]) : (result[idx] < result[partner]); + if (need_swap) { + std::swap(result[idx], result[partner]); + } + } +} + void TitaevSortirovkaBetcheraALL::BatcherSort() { auto &result = GetOutput(); - const size_t n = result.size(); + const std::size_t n = result.size(); if (n < 2) { return; } - - for (size_t k = 2; k <= n; k <<= 1) { - for (size_t j = k >> 1; j > 0; j >>= 1) { -#pragma omp parallel for - for (long long ii = 0; ii < static_cast(n); ii++) { - const size_t i = static_cast(ii); - const size_t l = i ^ j; - if (l > i) { - const bool ascending = ((i & k) == 0); - const bool need_swap = ascending ? (result[i] > result[l]) : (result[i] < result[l]); - if (need_swap) { - std::swap(result[i], result[l]); - } - } - } + for (std::size_t block = 2; block <= n; block <<= 1) { + for (std::size_t step = block >> 1; step > 0; step >>= 1) { + BatcherStage(result, n, block, step); } } } bool TitaevSortirovkaBetcheraALL::RunImpl() { auto &input = GetInput(); - const size_t n = input.size(); + const std::size_t n = input.size(); if (n <= 1) { return true; } diff --git a/tasks/titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp b/tasks/titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp index e26cf3fa28..d34c6184bb 100644 --- a/tasks/titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp +++ b/tasks/titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp @@ -1,5 +1,4 @@ #pragma once -#include #include #include @@ -25,6 +24,7 @@ class TitaevSortirovkaBetcheraOMP : public BaseTask { static void RadixSort(std::vector &keys); static void ConvertFromKeys(const std::vector &keys, OutType &output); void BatcherSort(); + static void BatcherStage(OutType &result, std::size_t array_size, std::size_t block, std::size_t step); }; } // namespace titaev_m_sortirovka_betchera diff --git a/tasks/titaev_m_sortirovka_betchera/omp/src/ops_omp.cpp b/tasks/titaev_m_sortirovka_betchera/omp/src/ops_omp.cpp index bcae682b40..17aba62074 100644 --- a/tasks/titaev_m_sortirovka_betchera/omp/src/ops_omp.cpp +++ b/tasks/titaev_m_sortirovka_betchera/omp/src/ops_omp.cpp @@ -14,28 +14,29 @@ namespace titaev_m_sortirovka_betchera { namespace { uint64_t DoubleToOrderedUint(double value) { - uint64_t x = 0; - std::memcpy(&x, &value, sizeof(double)); + uint64_t bits = 0; + std::memcpy(&bits, &value, sizeof(double)); constexpr uint64_t kSignMask = (1ULL << 63); - if ((x & kSignMask) != 0ULL) { - x = ~x; + if ((bits & kSignMask) != 0ULL) { + bits = ~bits; } else { - x ^= kSignMask; + bits ^= kSignMask; } - return x; + return bits; } -double OrderedUintToDouble(uint64_t x) { +double OrderedUintToDouble(uint64_t bits) { constexpr uint64_t kSignMask = (1ULL << 63); - if ((x & kSignMask) != 0ULL) { - x ^= kSignMask; + if ((bits & kSignMask) != 0ULL) { + bits ^= kSignMask; } else { - x = ~x; + bits = ~bits; } double result = 0.0; - std::memcpy(&result, &x, sizeof(double)); + std::memcpy(&result, &bits, sizeof(double)); return result; } + } // namespace TitaevSortirovkaBetcheraOMP::TitaevSortirovkaBetcheraOMP(const InType &in) { @@ -54,15 +55,15 @@ bool TitaevSortirovkaBetcheraOMP::PreProcessingImpl() { } void TitaevSortirovkaBetcheraOMP::ConvertToKeys(const InType &input, std::vector &keys) { - const size_t n = input.size(); -#pragma omp parallel for - for (long long i = 0; i < static_cast(n); i++) { + const auto n = static_cast(input.size()); +#pragma omp parallel for default(none) shared(input, keys, n) + for (int64_t i = 0; i < n; i++) { keys[i] = DoubleToOrderedUint(input[i]); } } void TitaevSortirovkaBetcheraOMP::RadixSort(std::vector &keys) { - const size_t n = keys.size(); + const std::size_t n = keys.size(); if (n <= 1) { return; } @@ -72,27 +73,27 @@ void TitaevSortirovkaBetcheraOMP::RadixSort(std::vector &keys) { constexpr int kPasses = 64 / kBits; std::vector tmp(n); + const auto signed_n = static_cast(n); for (int pass = 0; pass < kPasses; pass++) { - std::vector count(kBuckets, 0); - + std::vector count(kBuckets, 0); const int num_threads = omp_get_max_threads(); - std::vector> local_count(num_threads, std::vector(kBuckets, 0)); + std::vector> local_count(num_threads, std::vector(kBuckets, 0)); -#pragma omp parallel +#pragma omp parallel default(none) shared(keys, local_count, signed_n, pass) { const int tid = omp_get_thread_num(); auto &lc = local_count[tid]; #pragma omp for - for (long long i = 0; i < static_cast(n); i++) { - size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + for (int64_t i = 0; i < signed_n; i++) { + const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); lc[bucket]++; } } - for (int b = 0; b < kBuckets; b++) { - for (int t = 0; t < num_threads; t++) { - count[b] += local_count[t][b]; + for (int bucket_idx = 0; bucket_idx < kBuckets; bucket_idx++) { + for (int thr = 0; thr < num_threads; thr++) { + count[bucket_idx] += local_count[thr][bucket_idx]; } } @@ -100,8 +101,8 @@ void TitaevSortirovkaBetcheraOMP::RadixSort(std::vector &keys) { count[i] += count[i - 1]; } - for (size_t i = n; i-- > 0;) { - size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + for (std::size_t i = n; i-- > 0;) { + const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); tmp[--count[bucket]] = keys[i]; } @@ -110,42 +111,48 @@ void TitaevSortirovkaBetcheraOMP::RadixSort(std::vector &keys) { } void TitaevSortirovkaBetcheraOMP::ConvertFromKeys(const std::vector &keys, OutType &output) { - const size_t n = keys.size(); - output.resize(n); -#pragma omp parallel for - for (long long i = 0; i < static_cast(n); i++) { + const auto n = static_cast(keys.size()); + output.resize(keys.size()); +#pragma omp parallel for default(none) shared(keys, output, n) + for (int64_t i = 0; i < n; i++) { output[i] = OrderedUintToDouble(keys[i]); } } +void TitaevSortirovkaBetcheraOMP::BatcherStage(OutType &result, std::size_t array_size, std::size_t block, + std::size_t step) { + const auto signed_size = static_cast(array_size); +#pragma omp parallel for default(none) shared(result, signed_size, block, step) + for (int64_t i = 0; i < signed_size; i++) { + const auto idx = static_cast(i); + const std::size_t partner = idx ^ step; + if (partner <= idx) { + continue; + } + const bool ascending = ((idx & block) == 0); + const bool need_swap = ascending ? (result[idx] > result[partner]) : (result[idx] < result[partner]); + if (need_swap) { + std::swap(result[idx], result[partner]); + } + } +} + void TitaevSortirovkaBetcheraOMP::BatcherSort() { auto &result = GetOutput(); - const size_t n = result.size(); + const std::size_t n = result.size(); if (n < 2) { return; } - - for (size_t k = 2; k <= n; k <<= 1) { - for (size_t j = k >> 1; j > 0; j >>= 1) { -#pragma omp parallel for - for (long long ii = 0; ii < static_cast(n); ii++) { - const size_t i = static_cast(ii); - const size_t l = i ^ j; - if (l > i) { - const bool ascending = ((i & k) == 0); - const bool need_swap = ascending ? (result[i] > result[l]) : (result[i] < result[l]); - if (need_swap) { - std::swap(result[i], result[l]); - } - } - } + for (std::size_t block = 2; block <= n; block <<= 1) { + for (std::size_t step = block >> 1; step > 0; step >>= 1) { + BatcherStage(result, n, block, step); } } } bool TitaevSortirovkaBetcheraOMP::RunImpl() { auto &input = GetInput(); - const size_t n = input.size(); + const std::size_t n = input.size(); if (n <= 1) { return true; } diff --git a/tasks/titaev_m_sortirovka_betchera/seq/include/ops_seq.hpp b/tasks/titaev_m_sortirovka_betchera/seq/include/ops_seq.hpp index b91f5233b3..16fb1140fb 100644 --- a/tasks/titaev_m_sortirovka_betchera/seq/include/ops_seq.hpp +++ b/tasks/titaev_m_sortirovka_betchera/seq/include/ops_seq.hpp @@ -1,5 +1,4 @@ #pragma once -#include #include #include @@ -25,6 +24,7 @@ class TitaevSortirovkaBetcheraSEQ : public BaseTask { static void RadixSort(std::vector &keys); static void ConvertFromKeys(const std::vector &keys, OutType &output); void BatcherSort(); + static void BatcherStage(OutType &result, std::size_t array_size, std::size_t block, std::size_t step); }; } // namespace titaev_m_sortirovka_betchera diff --git a/tasks/titaev_m_sortirovka_betchera/seq/src/ops_seq.cpp b/tasks/titaev_m_sortirovka_betchera/seq/src/ops_seq.cpp index ce1cde53bb..0525be97c6 100644 --- a/tasks/titaev_m_sortirovka_betchera/seq/src/ops_seq.cpp +++ b/tasks/titaev_m_sortirovka_betchera/seq/src/ops_seq.cpp @@ -12,33 +12,29 @@ namespace titaev_m_sortirovka_betchera { namespace { uint64_t DoubleToOrderedUint(double value) { - uint64_t x = 0; - std::memcpy(&x, &value, sizeof(double)); - + uint64_t bits = 0; + std::memcpy(&bits, &value, sizeof(double)); constexpr uint64_t kSignMask = (1ULL << 63); - - if ((x & kSignMask) != 0ULL) { - x = ~x; + if ((bits & kSignMask) != 0ULL) { + bits = ~bits; } else { - x ^= kSignMask; + bits ^= kSignMask; } - - return x; + return bits; } -double OrderedUintToDouble(uint64_t x) { +double OrderedUintToDouble(uint64_t bits) { constexpr uint64_t kSignMask = (1ULL << 63); - - if ((x & kSignMask) != 0ULL) { - x ^= kSignMask; + if ((bits & kSignMask) != 0ULL) { + bits ^= kSignMask; } else { - x = ~x; + bits = ~bits; } - double result = 0.0; - std::memcpy(&result, &x, sizeof(double)); + std::memcpy(&result, &bits, sizeof(double)); return result; } + } // namespace TitaevSortirovkaBetcheraSEQ::TitaevSortirovkaBetcheraSEQ(const InType &in) { @@ -57,14 +53,14 @@ bool TitaevSortirovkaBetcheraSEQ::PreProcessingImpl() { } void TitaevSortirovkaBetcheraSEQ::ConvertToKeys(const InType &input, std::vector &keys) { - const size_t n = input.size(); - for (size_t i = 0; i < n; i++) { + const std::size_t n = input.size(); + for (std::size_t i = 0; i < n; i++) { keys[i] = DoubleToOrderedUint(input[i]); } } void TitaevSortirovkaBetcheraSEQ::RadixSort(std::vector &keys) { - const size_t n = keys.size(); + const std::size_t n = keys.size(); if (n <= 1) { return; } @@ -76,10 +72,10 @@ void TitaevSortirovkaBetcheraSEQ::RadixSort(std::vector &keys) { std::vector tmp(n); for (int pass = 0; pass < kPasses; pass++) { - std::vector count(kBuckets, 0); + std::vector count(kBuckets, 0); - for (size_t i = 0; i < n; i++) { - size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + for (std::size_t i = 0; i < n; i++) { + const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); count[bucket]++; } @@ -87,8 +83,8 @@ void TitaevSortirovkaBetcheraSEQ::RadixSort(std::vector &keys) { count[i] += count[i - 1]; } - for (size_t i = n; i-- > 0;) { - size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + for (std::size_t i = n; i-- > 0;) { + const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); tmp[--count[bucket]] = keys[i]; } @@ -97,39 +93,44 @@ void TitaevSortirovkaBetcheraSEQ::RadixSort(std::vector &keys) { } void TitaevSortirovkaBetcheraSEQ::ConvertFromKeys(const std::vector &keys, OutType &output) { - const size_t n = keys.size(); + const std::size_t n = keys.size(); output.resize(n); - for (size_t i = 0; i < n; i++) { + for (std::size_t i = 0; i < n; i++) { output[i] = OrderedUintToDouble(keys[i]); } } +void TitaevSortirovkaBetcheraSEQ::BatcherStage(OutType &result, std::size_t array_size, std::size_t block, + std::size_t step) { + for (std::size_t i = 0; i < array_size; i++) { + const std::size_t partner = i ^ step; + if (partner <= i) { + continue; + } + const bool ascending = ((i & block) == 0); + const bool need_swap = ascending ? (result[i] > result[partner]) : (result[i] < result[partner]); + if (need_swap) { + std::swap(result[i], result[partner]); + } + } +} + void TitaevSortirovkaBetcheraSEQ::BatcherSort() { auto &result = GetOutput(); - const size_t n = result.size(); + const std::size_t n = result.size(); if (n < 2) { return; } - - for (size_t k = 2; k <= n; k <<= 1) { - for (size_t j = k >> 1; j > 0; j >>= 1) { - for (size_t i = 0; i < n; i++) { - const size_t l = i ^ j; - if (l > i) { - const bool ascending = ((i & k) == 0); - const bool need_swap = ascending ? (result[i] > result[l]) : (result[i] < result[l]); - if (need_swap) { - std::swap(result[i], result[l]); - } - } - } + for (std::size_t block = 2; block <= n; block <<= 1) { + for (std::size_t step = block >> 1; step > 0; step >>= 1) { + BatcherStage(result, n, block, step); } } } bool TitaevSortirovkaBetcheraSEQ::RunImpl() { auto &input = GetInput(); - const size_t n = input.size(); + const std::size_t n = input.size(); if (n <= 1) { return true; } diff --git a/tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp b/tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp index c1eeff4bf4..e6f912a544 100644 --- a/tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp +++ b/tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp @@ -1,5 +1,4 @@ #pragma once -#include #include #include @@ -23,8 +22,10 @@ class TitaevSortirovkaBetcheraSTL : public BaseTask { static void ConvertToKeys(const InType &input, std::vector &keys); static void RadixSort(std::vector &keys); + static void RadixCountPass(std::vector &keys, std::vector &tmp, int pass); static void ConvertFromKeys(const std::vector &keys, OutType &output); void BatcherSort(); + static void BatcherStage(OutType &result, std::size_t array_size, std::size_t block, std::size_t step); }; } // namespace titaev_m_sortirovka_betchera diff --git a/tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp b/tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp index d12a7f76c0..aedb83da5c 100644 --- a/tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp +++ b/tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -12,34 +13,58 @@ namespace titaev_m_sortirovka_betchera { namespace { +constexpr int kBits = 8; +constexpr int kBuckets = 1 << kBits; +constexpr int kPasses = 64 / kBits; + uint64_t DoubleToOrderedUint(double value) { - uint64_t x = 0; - std::memcpy(&x, &value, sizeof(double)); + uint64_t bits = 0; + std::memcpy(&bits, &value, sizeof(double)); constexpr uint64_t kSignMask = (1ULL << 63); - if ((x & kSignMask) != 0ULL) { - x = ~x; + if ((bits & kSignMask) != 0ULL) { + bits = ~bits; } else { - x ^= kSignMask; + bits ^= kSignMask; } - return x; + return bits; } -double OrderedUintToDouble(uint64_t x) { +double OrderedUintToDouble(uint64_t bits) { constexpr uint64_t kSignMask = (1ULL << 63); - if ((x & kSignMask) != 0ULL) { - x ^= kSignMask; + if ((bits & kSignMask) != 0ULL) { + bits ^= kSignMask; } else { - x = ~x; + bits = ~bits; } double result = 0.0; - std::memcpy(&result, &x, sizeof(double)); + std::memcpy(&result, &bits, sizeof(double)); return result; } unsigned int GetThreadCount() { - unsigned int hw = std::thread::hardware_concurrency(); + const unsigned int hw = std::thread::hardware_concurrency(); return hw == 0 ? 1U : hw; } + +void RunInParallel(std::size_t total, const std::function &body) { + const unsigned int num_threads = GetThreadCount(); + std::vector threads; + threads.reserve(num_threads); + const std::size_t chunk = (total + num_threads - 1) / num_threads; + + for (unsigned int thr = 0; thr < num_threads; thr++) { + const std::size_t begin = thr * chunk; + const std::size_t end = std::min(begin + chunk, total); + if (begin >= end) { + break; + } + threads.emplace_back([&body, begin, end]() { body(begin, end); }); + } + for (auto &th : threads) { + th.join(); + } +} + } // namespace TitaevSortirovkaBetcheraSTL::TitaevSortirovkaBetcheraSTL(const InType &in) { @@ -58,154 +83,108 @@ bool TitaevSortirovkaBetcheraSTL::PreProcessingImpl() { } void TitaevSortirovkaBetcheraSTL::ConvertToKeys(const InType &input, std::vector &keys) { - const size_t n = input.size(); + RunInParallel(input.size(), [&input, &keys](std::size_t begin, std::size_t end) { + for (std::size_t i = begin; i < end; i++) { + keys[i] = DoubleToOrderedUint(input[i]); + } + }); +} + +void TitaevSortirovkaBetcheraSTL::RadixCountPass(std::vector &keys, std::vector &tmp, int pass) { + const std::size_t n = keys.size(); const unsigned int num_threads = GetThreadCount(); + std::vector> local_count(num_threads, std::vector(kBuckets, 0)); + + const std::size_t chunk = (n + num_threads - 1) / num_threads; std::vector threads; threads.reserve(num_threads); - const size_t chunk = (n + num_threads - 1) / num_threads; - - for (unsigned int t = 0; t < num_threads; t++) { - const size_t begin = t * chunk; - const size_t end = std::min(begin + chunk, n); + for (unsigned int thr = 0; thr < num_threads; thr++) { + const std::size_t begin = thr * chunk; + const std::size_t end = std::min(begin + chunk, n); if (begin >= end) { break; } - threads.emplace_back([&input, &keys, begin, end]() { - for (size_t i = begin; i < end; i++) { - keys[i] = DoubleToOrderedUint(input[i]); + threads.emplace_back([&keys, &local_count, thr, begin, end, pass]() { + auto &lc = local_count[thr]; + for (std::size_t i = begin; i < end; i++) { + const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + lc[bucket]++; } }); } for (auto &th : threads) { th.join(); } + + std::vector count(kBuckets, 0); + for (int bucket_idx = 0; bucket_idx < kBuckets; bucket_idx++) { + for (unsigned int thr = 0; thr < num_threads; thr++) { + count[bucket_idx] += local_count[thr][bucket_idx]; + } + } + for (int i = 1; i < kBuckets; i++) { + count[i] += count[i - 1]; + } + for (std::size_t i = n; i-- > 0;) { + const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + tmp[--count[bucket]] = keys[i]; + } + keys.swap(tmp); } void TitaevSortirovkaBetcheraSTL::RadixSort(std::vector &keys) { - const size_t n = keys.size(); + const std::size_t n = keys.size(); if (n <= 1) { return; } - - constexpr int kBits = 8; - constexpr int kBuckets = 1 << kBits; - constexpr int kPasses = 64 / kBits; - - const unsigned int num_threads = GetThreadCount(); std::vector tmp(n); - for (int pass = 0; pass < kPasses; pass++) { - std::vector> local_count(num_threads, std::vector(kBuckets, 0)); - std::vector threads; - threads.reserve(num_threads); - const size_t chunk = (n + num_threads - 1) / num_threads; - - for (unsigned int t = 0; t < num_threads; t++) { - const size_t begin = t * chunk; - const size_t end = std::min(begin + chunk, n); - if (begin >= end) { - break; - } - threads.emplace_back([&keys, &local_count, t, begin, end, pass]() { - auto &lc = local_count[t]; - for (size_t i = begin; i < end; i++) { - size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); - lc[bucket]++; - } - }); - } - for (auto &th : threads) { - th.join(); - } - - std::vector count(kBuckets, 0); - for (int b = 0; b < kBuckets; b++) { - for (unsigned int t = 0; t < num_threads; t++) { - count[b] += local_count[t][b]; - } - } - for (int i = 1; i < kBuckets; i++) { - count[i] += count[i - 1]; - } - - for (size_t i = n; i-- > 0;) { - size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); - tmp[--count[bucket]] = keys[i]; - } - - keys.swap(tmp); + RadixCountPass(keys, tmp, pass); } } void TitaevSortirovkaBetcheraSTL::ConvertFromKeys(const std::vector &keys, OutType &output) { - const size_t n = keys.size(); - output.resize(n); - const unsigned int num_threads = GetThreadCount(); - std::vector threads; - threads.reserve(num_threads); - const size_t chunk = (n + num_threads - 1) / num_threads; - - for (unsigned int t = 0; t < num_threads; t++) { - const size_t begin = t * chunk; - const size_t end = std::min(begin + chunk, n); - if (begin >= end) { - break; + output.resize(keys.size()); + RunInParallel(keys.size(), [&keys, &output](std::size_t begin, std::size_t end) { + for (std::size_t i = begin; i < end; i++) { + output[i] = OrderedUintToDouble(keys[i]); } - threads.emplace_back([&keys, &output, begin, end]() { - for (size_t i = begin; i < end; i++) { - output[i] = OrderedUintToDouble(keys[i]); + }); +} + +void TitaevSortirovkaBetcheraSTL::BatcherStage(OutType &result, std::size_t array_size, std::size_t block, + std::size_t step) { + RunInParallel(array_size, [&result, block, step](std::size_t begin, std::size_t end) { + for (std::size_t i = begin; i < end; i++) { + const std::size_t partner = i ^ step; + if (partner <= i) { + continue; } - }); - } - for (auto &th : threads) { - th.join(); - } + const bool ascending = ((i & block) == 0); + const bool need_swap = ascending ? (result[i] > result[partner]) : (result[i] < result[partner]); + if (need_swap) { + std::swap(result[i], result[partner]); + } + } + }); } void TitaevSortirovkaBetcheraSTL::BatcherSort() { auto &result = GetOutput(); - const size_t n = result.size(); + const std::size_t n = result.size(); if (n < 2) { return; } - - const unsigned int num_threads = GetThreadCount(); - - for (size_t k = 2; k <= n; k <<= 1) { - for (size_t j = k >> 1; j > 0; j >>= 1) { - std::vector threads; - threads.reserve(num_threads); - const size_t chunk = (n + num_threads - 1) / num_threads; - - for (unsigned int t = 0; t < num_threads; t++) { - const size_t begin = t * chunk; - const size_t end = std::min(begin + chunk, n); - if (begin >= end) { - break; - } - threads.emplace_back([&result, n, k, j, begin, end]() { - for (size_t i = begin; i < end; i++) { - const size_t l = i ^ j; - if (l > i && l < n) { - const bool ascending = ((i & k) == 0); - const bool need_swap = ascending ? (result[i] > result[l]) : (result[i] < result[l]); - if (need_swap) { - std::swap(result[i], result[l]); - } - } - } - }); - } - for (auto &th : threads) { - th.join(); - } + for (std::size_t block = 2; block <= n; block <<= 1) { + for (std::size_t step = block >> 1; step > 0; step >>= 1) { + BatcherStage(result, n, block, step); } } } bool TitaevSortirovkaBetcheraSTL::RunImpl() { auto &input = GetInput(); - const size_t n = input.size(); + const std::size_t n = input.size(); if (n <= 1) { return true; } diff --git a/tasks/titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp b/tasks/titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp index 9b4cb07704..b9c122862e 100644 --- a/tasks/titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp +++ b/tasks/titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp @@ -1,5 +1,4 @@ #pragma once -#include #include #include @@ -25,6 +24,7 @@ class TitaevSortirovkaBetcheraTBB : public BaseTask { static void RadixSort(std::vector &keys); static void ConvertFromKeys(const std::vector &keys, OutType &output); void BatcherSort(); + static void BatcherStage(OutType &result, std::size_t array_size, std::size_t block, std::size_t step); }; } // namespace titaev_m_sortirovka_betchera diff --git a/tasks/titaev_m_sortirovka_betchera/tbb/src/ops_tbb.cpp b/tasks/titaev_m_sortirovka_betchera/tbb/src/ops_tbb.cpp index e5e6b7dc5b..5770ab9a42 100644 --- a/tasks/titaev_m_sortirovka_betchera/tbb/src/ops_tbb.cpp +++ b/tasks/titaev_m_sortirovka_betchera/tbb/src/ops_tbb.cpp @@ -15,28 +15,29 @@ namespace titaev_m_sortirovka_betchera { namespace { uint64_t DoubleToOrderedUint(double value) { - uint64_t x = 0; - std::memcpy(&x, &value, sizeof(double)); + uint64_t bits = 0; + std::memcpy(&bits, &value, sizeof(double)); constexpr uint64_t kSignMask = (1ULL << 63); - if ((x & kSignMask) != 0ULL) { - x = ~x; + if ((bits & kSignMask) != 0ULL) { + bits = ~bits; } else { - x ^= kSignMask; + bits ^= kSignMask; } - return x; + return bits; } -double OrderedUintToDouble(uint64_t x) { +double OrderedUintToDouble(uint64_t bits) { constexpr uint64_t kSignMask = (1ULL << 63); - if ((x & kSignMask) != 0ULL) { - x ^= kSignMask; + if ((bits & kSignMask) != 0ULL) { + bits ^= kSignMask; } else { - x = ~x; + bits = ~bits; } double result = 0.0; - std::memcpy(&result, &x, sizeof(double)); + std::memcpy(&result, &bits, sizeof(double)); return result; } + } // namespace TitaevSortirovkaBetcheraTBB::TitaevSortirovkaBetcheraTBB(const InType &in) { @@ -55,16 +56,17 @@ bool TitaevSortirovkaBetcheraTBB::PreProcessingImpl() { } void TitaevSortirovkaBetcheraTBB::ConvertToKeys(const InType &input, std::vector &keys) { - const size_t n = input.size(); - oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, n), [&](const oneapi::tbb::blocked_range &r) { - for (size_t i = r.begin(); i < r.end(); i++) { + const std::size_t n = input.size(); + oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, n), + [&input, &keys](const oneapi::tbb::blocked_range &range) { + for (std::size_t i = range.begin(); i < range.end(); i++) { keys[i] = DoubleToOrderedUint(input[i]); } }); } void TitaevSortirovkaBetcheraTBB::RadixSort(std::vector &keys) { - const size_t n = keys.size(); + const std::size_t n = keys.size(); if (n <= 1) { return; } @@ -76,16 +78,16 @@ void TitaevSortirovkaBetcheraTBB::RadixSort(std::vector &keys) { std::vector tmp(n); for (int pass = 0; pass < kPasses; pass++) { - std::vector count(kBuckets, 0); - for (size_t i = 0; i < n; i++) { - size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + std::vector count(kBuckets, 0); + for (std::size_t i = 0; i < n; i++) { + const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); count[bucket]++; } for (int i = 1; i < kBuckets; i++) { count[i] += count[i - 1]; } - for (size_t i = n; i-- > 0;) { - size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + for (std::size_t i = n; i-- > 0;) { + const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); tmp[--count[bucket]] = keys[i]; } keys.swap(tmp); @@ -93,44 +95,50 @@ void TitaevSortirovkaBetcheraTBB::RadixSort(std::vector &keys) { } void TitaevSortirovkaBetcheraTBB::ConvertFromKeys(const std::vector &keys, OutType &output) { - const size_t n = keys.size(); + const std::size_t n = keys.size(); output.resize(n); - oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, n), [&](const oneapi::tbb::blocked_range &r) { - for (size_t i = r.begin(); i < r.end(); i++) { + oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, n), + [&keys, &output](const oneapi::tbb::blocked_range &range) { + for (std::size_t i = range.begin(); i < range.end(); i++) { output[i] = OrderedUintToDouble(keys[i]); } }); } +void TitaevSortirovkaBetcheraTBB::BatcherStage(OutType &result, std::size_t array_size, std::size_t block, + std::size_t step) { + oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, array_size), + [&result, block, step](const oneapi::tbb::blocked_range &range) { + for (std::size_t i = range.begin(); i < range.end(); i++) { + const std::size_t partner = i ^ step; + if (partner <= i) { + continue; + } + const bool ascending = ((i & block) == 0); + const bool need_swap = ascending ? (result[i] > result[partner]) : (result[i] < result[partner]); + if (need_swap) { + std::swap(result[i], result[partner]); + } + } + }); +} + void TitaevSortirovkaBetcheraTBB::BatcherSort() { auto &result = GetOutput(); - const size_t n = result.size(); + const std::size_t n = result.size(); if (n < 2) { return; } - - for (size_t k = 2; k <= n; k <<= 1) { - for (size_t j = k >> 1; j > 0; j >>= 1) { - oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, n), - [&](const oneapi::tbb::blocked_range &r) { - for (size_t i = r.begin(); i < r.end(); i++) { - const size_t l = i ^ j; - if (l > i && l < n) { - const bool ascending = ((i & k) == 0); - const bool need_swap = ascending ? (result[i] > result[l]) : (result[i] < result[l]); - if (need_swap) { - std::swap(result[i], result[l]); - } - } - } - }); + for (std::size_t block = 2; block <= n; block <<= 1) { + for (std::size_t step = block >> 1; step > 0; step >>= 1) { + BatcherStage(result, n, block, step); } } } bool TitaevSortirovkaBetcheraTBB::RunImpl() { auto &input = GetInput(); - const size_t n = input.size(); + const std::size_t n = input.size(); if (n <= 1) { return true; } diff --git a/tasks/titaev_m_sortirovka_betchera/tests/functional/main.cpp b/tasks/titaev_m_sortirovka_betchera/tests/functional/main.cpp index b384465b2b..0e57acbf56 100644 --- a/tasks/titaev_m_sortirovka_betchera/tests/functional/main.cpp +++ b/tasks/titaev_m_sortirovka_betchera/tests/functional/main.cpp @@ -5,8 +5,6 @@ #include #include #include -#include -#include #include "titaev_m_sortirovka_betchera/all/include/ops_all.hpp" #include "titaev_m_sortirovka_betchera/common/include/common.hpp" @@ -29,17 +27,17 @@ class TitaevSortirovkaBetcheraFuncTests : public ppc::util::BaseRunFuncTests(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); size_ = std::get<0>(params); - input_data_.resize(static_cast(size_)); + input_data_.resize(static_cast(size_)); for (int i = 0; i < size_; i++) { input_data_[i] = static_cast(size_ - i) - 0.5; } } bool CheckTestOutputData(OutType &output_data) final { - if (output_data.size() != static_cast(size_)) { + if (output_data.size() != static_cast(size_)) { return false; } - return std::is_sorted(output_data.begin(), output_data.end()); + return std::ranges::is_sorted(output_data); } InType GetTestInputData() final { diff --git a/tasks/titaev_m_sortirovka_betchera/tests/performance/main.cpp b/tasks/titaev_m_sortirovka_betchera/tests/performance/main.cpp index 7394439127..a91a105549 100644 --- a/tasks/titaev_m_sortirovka_betchera/tests/performance/main.cpp +++ b/tasks/titaev_m_sortirovka_betchera/tests/performance/main.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include "titaev_m_sortirovka_betchera/all/include/ops_all.hpp" #include "titaev_m_sortirovka_betchera/common/include/common.hpp" @@ -26,7 +25,7 @@ class TitaevSortirovkaBetcheraPerfTests : public ppc::util::BaseRunPerfTests Date: Wed, 3 Jun 2026 21:50:53 +0300 Subject: [PATCH 3/5] Titaev_m_RP --- tasks/titaev_m_sortirovka_betchera/all/include/ops_all.hpp | 1 + tasks/titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp | 1 + tasks/titaev_m_sortirovka_betchera/seq/include/ops_seq.hpp | 1 + tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp | 1 + tasks/titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp | 1 + tasks/titaev_m_sortirovka_betchera/tests/performance/main.cpp | 1 - 6 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tasks/titaev_m_sortirovka_betchera/all/include/ops_all.hpp b/tasks/titaev_m_sortirovka_betchera/all/include/ops_all.hpp index 6d5872ba01..af804f58b8 100644 --- a/tasks/titaev_m_sortirovka_betchera/all/include/ops_all.hpp +++ b/tasks/titaev_m_sortirovka_betchera/all/include/ops_all.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include diff --git a/tasks/titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp b/tasks/titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp index d34c6184bb..033950a720 100644 --- a/tasks/titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp +++ b/tasks/titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include diff --git a/tasks/titaev_m_sortirovka_betchera/seq/include/ops_seq.hpp b/tasks/titaev_m_sortirovka_betchera/seq/include/ops_seq.hpp index 16fb1140fb..141bea5de4 100644 --- a/tasks/titaev_m_sortirovka_betchera/seq/include/ops_seq.hpp +++ b/tasks/titaev_m_sortirovka_betchera/seq/include/ops_seq.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include diff --git a/tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp b/tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp index e6f912a544..e4acc286e7 100644 --- a/tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp +++ b/tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include diff --git a/tasks/titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp b/tasks/titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp index b9c122862e..ad2712e044 100644 --- a/tasks/titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp +++ b/tasks/titaev_m_sortirovka_betchera/tbb/include/ops_tbb.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include diff --git a/tasks/titaev_m_sortirovka_betchera/tests/performance/main.cpp b/tasks/titaev_m_sortirovka_betchera/tests/performance/main.cpp index a91a105549..256082188b 100644 --- a/tasks/titaev_m_sortirovka_betchera/tests/performance/main.cpp +++ b/tasks/titaev_m_sortirovka_betchera/tests/performance/main.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include "titaev_m_sortirovka_betchera/all/include/ops_all.hpp" From cd60aaffff4123b4e92ea1e5cfa59ac64217ce9a Mon Sep 17 00:00:00 2001 From: Makasimchik Date: Wed, 3 Jun 2026 22:07:37 +0300 Subject: [PATCH 4/5] Titaev_m_RP --- .../stl/src/ops_stl.cpp | 68 ++++++++++++------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp b/tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp index aedb83da5c..48d597e933 100644 --- a/tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp +++ b/tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp @@ -1,6 +1,7 @@ #include "titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp" #include +#include #include #include #include @@ -16,6 +17,7 @@ namespace { constexpr int kBits = 8; constexpr int kBuckets = 1 << kBits; constexpr int kPasses = 64 / kBits; +constexpr std::size_t kSequentialThreshold = 1 << 16; uint64_t DoubleToOrderedUint(double value) { uint64_t bits = 0; @@ -47,7 +49,13 @@ unsigned int GetThreadCount() { } void RunInParallel(std::size_t total, const std::function &body) { - const unsigned int num_threads = GetThreadCount(); + const unsigned int num_threads = (total < kSequentialThreshold) ? 1U : GetThreadCount(); + + if (num_threads <= 1) { + body(0, total); + return; + } + std::vector threads; threads.reserve(num_threads); const std::size_t chunk = (total + num_threads - 1) / num_threads; @@ -92,36 +100,44 @@ void TitaevSortirovkaBetcheraSTL::ConvertToKeys(const InType &input, std::vector void TitaevSortirovkaBetcheraSTL::RadixCountPass(std::vector &keys, std::vector &tmp, int pass) { const std::size_t n = keys.size(); - const unsigned int num_threads = GetThreadCount(); - std::vector> local_count(num_threads, std::vector(kBuckets, 0)); - - const std::size_t chunk = (n + num_threads - 1) / num_threads; - std::vector threads; - threads.reserve(num_threads); - for (unsigned int thr = 0; thr < num_threads; thr++) { - const std::size_t begin = thr * chunk; - const std::size_t end = std::min(begin + chunk, n); - if (begin >= end) { - break; - } - threads.emplace_back([&keys, &local_count, thr, begin, end, pass]() { - auto &lc = local_count[thr]; - for (std::size_t i = begin; i < end; i++) { - const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); - lc[bucket]++; - } - }); - } - for (auto &th : threads) { - th.join(); - } + const unsigned int num_threads = (n < kSequentialThreshold) ? 1U : GetThreadCount(); std::vector count(kBuckets, 0); - for (int bucket_idx = 0; bucket_idx < kBuckets; bucket_idx++) { + + if (num_threads <= 1) { + for (std::size_t i = 0; i < n; i++) { + const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + count[bucket]++; + } + } else { + std::vector> local_count(num_threads, std::vector(kBuckets, 0)); + const std::size_t chunk = (n + num_threads - 1) / num_threads; + std::vector threads; + threads.reserve(num_threads); for (unsigned int thr = 0; thr < num_threads; thr++) { - count[bucket_idx] += local_count[thr][bucket_idx]; + const std::size_t begin = thr * chunk; + const std::size_t end = std::min(begin + chunk, n); + if (begin >= end) { + break; + } + threads.emplace_back([&keys, &local_count, thr, begin, end, pass]() { + auto &lc = local_count[thr]; + for (std::size_t i = begin; i < end; i++) { + const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + lc[bucket]++; + } + }); + } + for (auto &th : threads) { + th.join(); + } + for (int bucket_idx = 0; bucket_idx < kBuckets; bucket_idx++) { + for (unsigned int thr = 0; thr < num_threads; thr++) { + count[bucket_idx] += local_count[thr][bucket_idx]; + } } } + for (int i = 1; i < kBuckets; i++) { count[i] += count[i - 1]; } From 179cdd5bf253816facf00479be560fb54967d120 Mon Sep 17 00:00:00 2001 From: Makasimchik Date: Wed, 3 Jun 2026 22:29:03 +0300 Subject: [PATCH 5/5] Titaev_m_RP --- .../stl/include/ops_stl.hpp | 3 + .../stl/src/ops_stl.cpp | 76 +++++++++++-------- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp b/tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp index e4acc286e7..fcf057fc9a 100644 --- a/tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp +++ b/tasks/titaev_m_sortirovka_betchera/stl/include/ops_stl.hpp @@ -23,6 +23,9 @@ class TitaevSortirovkaBetcheraSTL : public BaseTask { static void ConvertToKeys(const InType &input, std::vector &keys); static void RadixSort(std::vector &keys); + static void CountSequential(const std::vector &keys, std::vector &count, int pass); + static void CountParallel(const std::vector &keys, std::vector &count, int pass, + unsigned int num_threads); static void RadixCountPass(std::vector &keys, std::vector &tmp, int pass); static void ConvertFromKeys(const std::vector &keys, OutType &output); void BatcherSort(); diff --git a/tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp b/tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp index 48d597e933..3e2c6eaf63 100644 --- a/tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp +++ b/tasks/titaev_m_sortirovka_betchera/stl/src/ops_stl.cpp @@ -98,44 +98,57 @@ void TitaevSortirovkaBetcheraSTL::ConvertToKeys(const InType &input, std::vector }); } +void TitaevSortirovkaBetcheraSTL::CountSequential(const std::vector &keys, std::vector &count, + int pass) { + const std::size_t n = keys.size(); + for (std::size_t i = 0; i < n; i++) { + const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + count[bucket]++; + } +} + +void TitaevSortirovkaBetcheraSTL::CountParallel(const std::vector &keys, std::vector &count, + int pass, unsigned int num_threads) { + const std::size_t n = keys.size(); + std::vector> local_count(num_threads, std::vector(kBuckets, 0)); + const std::size_t chunk = (n + num_threads - 1) / num_threads; + + std::vector threads; + threads.reserve(num_threads); + for (unsigned int thr = 0; thr < num_threads; thr++) { + const std::size_t begin = thr * chunk; + const std::size_t end = std::min(begin + chunk, n); + if (begin >= end) { + break; + } + threads.emplace_back([&keys, &local_count, thr, begin, end, pass]() { + auto &lc = local_count[thr]; + for (std::size_t i = begin; i < end; i++) { + const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); + lc[bucket]++; + } + }); + } + for (auto &th : threads) { + th.join(); + } + + for (int bucket_idx = 0; bucket_idx < kBuckets; bucket_idx++) { + for (unsigned int thr = 0; thr < num_threads; thr++) { + count[bucket_idx] += local_count[thr][bucket_idx]; + } + } +} + void TitaevSortirovkaBetcheraSTL::RadixCountPass(std::vector &keys, std::vector &tmp, int pass) { const std::size_t n = keys.size(); const unsigned int num_threads = (n < kSequentialThreshold) ? 1U : GetThreadCount(); std::vector count(kBuckets, 0); - if (num_threads <= 1) { - for (std::size_t i = 0; i < n; i++) { - const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); - count[bucket]++; - } + CountSequential(keys, count, pass); } else { - std::vector> local_count(num_threads, std::vector(kBuckets, 0)); - const std::size_t chunk = (n + num_threads - 1) / num_threads; - std::vector threads; - threads.reserve(num_threads); - for (unsigned int thr = 0; thr < num_threads; thr++) { - const std::size_t begin = thr * chunk; - const std::size_t end = std::min(begin + chunk, n); - if (begin >= end) { - break; - } - threads.emplace_back([&keys, &local_count, thr, begin, end, pass]() { - auto &lc = local_count[thr]; - for (std::size_t i = begin; i < end; i++) { - const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1); - lc[bucket]++; - } - }); - } - for (auto &th : threads) { - th.join(); - } - for (int bucket_idx = 0; bucket_idx < kBuckets; bucket_idx++) { - for (unsigned int thr = 0; thr < num_threads; thr++) { - count[bucket_idx] += local_count[thr][bucket_idx]; - } - } + CountParallel(keys, count, pass, num_threads); } for (int i = 1; i < kBuckets; i++) { @@ -147,7 +160,6 @@ void TitaevSortirovkaBetcheraSTL::RadixCountPass(std::vector &keys, st } keys.swap(tmp); } - void TitaevSortirovkaBetcheraSTL::RadixSort(std::vector &keys) { const std::size_t n = keys.size(); if (n <= 1) {