diff --git a/tasks/cheremkhin_a_radix_sort_batcher/common/include/common.hpp b/tasks/cheremkhin_a_radix_sort_batcher/common/include/common.hpp new file mode 100644 index 0000000000..f153353820 --- /dev/null +++ b/tasks/cheremkhin_a_radix_sort_batcher/common/include/common.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" + +namespace cheremkhin_a_radix_sort_batcher { + +using InType = std::vector; +using OutType = std::vector; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace cheremkhin_a_radix_sort_batcher diff --git a/tasks/cheremkhin_a_radix_sort_batcher/info.json b/tasks/cheremkhin_a_radix_sort_batcher/info.json new file mode 100644 index 0000000000..7d409ec626 --- /dev/null +++ b/tasks/cheremkhin_a_radix_sort_batcher/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Андрей", + "last_name": "Черемхин", + "middle_name": "Александрович", + "group_number": "3823Б1ПР3", + "task_number": "3" + } +} diff --git a/tasks/cheremkhin_a_radix_sort_batcher/mpi/include/ops_mpi.hpp b/tasks/cheremkhin_a_radix_sort_batcher/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..988e552c17 --- /dev/null +++ b/tasks/cheremkhin_a_radix_sort_batcher/mpi/include/ops_mpi.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "cheremkhin_a_radix_sort_batcher/common/include/common.hpp" + +namespace cheremkhin_a_radix_sort_batcher { + +class CheremkhinARadixSortBatcherMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit CheremkhinARadixSortBatcherMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace cheremkhin_a_radix_sort_batcher diff --git a/tasks/cheremkhin_a_radix_sort_batcher/mpi/src/ops_mpi.cpp b/tasks/cheremkhin_a_radix_sort_batcher/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..00ad3bbcd8 --- /dev/null +++ b/tasks/cheremkhin_a_radix_sort_batcher/mpi/src/ops_mpi.cpp @@ -0,0 +1,210 @@ +#include "cheremkhin_a_radix_sort_batcher/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include "cheremkhin_a_radix_sort_batcher/common/include/common.hpp" + +namespace cheremkhin_a_radix_sort_batcher { + +namespace { + +constexpr std::uint32_t kSignMask = 0x80000000U; +constexpr std::size_t kRadix = 256; + +inline bool IsPowerOfTwo(int x) { + return x > 0 && (x & (x - 1)) == 0; +} + +inline std::uint8_t GetByteForRadixSort(int v, int byte_idx) { + const std::uint32_t key = static_cast(v) ^ kSignMask; + return static_cast((key >> (static_cast(byte_idx) * 8U)) & 0xFFU); +} + +std::vector RadixSortSigned32(const std::vector &in) { + if (in.size() <= 1) { + return in; + } + + std::vector a; + a.reserve(in.size()); + a.assign(in.begin(), in.end()); + std::vector tmp(in.size()); + + for (int byte_idx = 0; byte_idx < 4; ++byte_idx) { + std::vector cnt(kRadix, 0); + for (int v : a) { + ++cnt[GetByteForRadixSort(v, byte_idx)]; + } + + std::vector pos(kRadix); + std::size_t sum = 0; + for (std::size_t i = 0; i < kRadix; ++i) { + pos[i] = sum; + sum += cnt[i]; + } + + for (int v : a) { + const std::uint8_t b = GetByteForRadixSort(v, byte_idx); + tmp[pos[b]++] = v; + } + + a.swap(tmp); + } + + return a; +} + +std::vector MergeSorted(const std::vector &a, const std::vector &b) { + std::vector out; + out.resize(a.size() + b.size()); + std::ranges::merge(a, b, out.begin()); + return out; +} + +void CompareSplit(int rank, int partner, int keep_cnt, std::vector *local) { + std::vector recv(static_cast(keep_cnt)); + + MPI_Sendrecv(local->data(), keep_cnt, MPI_INT, partner, 0, recv.data(), keep_cnt, MPI_INT, partner, 0, MPI_COMM_WORLD, + MPI_STATUS_IGNORE); + + std::vector merged = MergeSorted(*local, recv); + + std::vector new_local(static_cast(keep_cnt)); + if (rank < partner) { + std::copy_n(merged.begin(), static_cast(keep_cnt), new_local.begin()); + } else { + std::copy_n(merged.end() - static_cast(keep_cnt), static_cast(keep_cnt), + new_local.begin()); + } + local->swap(new_local); +} + +// NOLINTNEXTLINE(misc-no-recursion) +void OddEvenMerge(int lo, int n, int r, std::vector> *comps) { + const int m = r * 2; + if (m < n) { + OddEvenMerge(lo, n, m, comps); + OddEvenMerge(lo + r, n, m, comps); + for (int i = lo + r; i + r < lo + n; i += m) { + comps->emplace_back(i, i + r); + } + } else { + comps->emplace_back(lo, lo + r); + } +} + +// NOLINTNEXTLINE(misc-no-recursion) +void OddEvenMergeSort(int lo, int n, std::vector> *comps) { + if (n > 1) { + const int m = n / 2; + OddEvenMergeSort(lo, m, comps); + OddEvenMergeSort(lo + m, m, comps); + OddEvenMerge(lo, n, 1, comps); + } +} + +} // namespace + +CheremkhinARadixSortBatcherMPI::CheremkhinARadixSortBatcherMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = {}; +} + +bool CheremkhinARadixSortBatcherMPI::ValidationImpl() { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank == 0) { + return !GetInput().empty(); + } + return true; +} + +bool CheremkhinARadixSortBatcherMPI::PreProcessingImpl() { + return true; +} + +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +bool CheremkhinARadixSortBatcherMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + int n = 0; + if (rank == 0) { + n = static_cast(GetInput().size()); + } + MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD); + + const int block = (n + size - 1) / size; + const int padded_n = block * size; + + std::vector padded; + if (rank == 0) { + padded.assign(static_cast(padded_n), std::numeric_limits::max()); + std::copy(GetInput().begin(), GetInput().end(), padded.begin()); + } + + std::vector local(static_cast(block)); + MPI_Scatter(padded.data(), block, MPI_INT, local.data(), block, MPI_INT, 0, MPI_COMM_WORLD); + + local = RadixSortSigned32(local); + + if (size > 1) { + if (IsPowerOfTwo(size)) { + std::vector> comps; + OddEvenMergeSort(0, size, &comps); + + for (const auto &[a, b] : comps) { + if (rank == a || rank == b) { + const int partner = (rank == a) ? b : a; + CompareSplit(rank, partner, block, &local); + } + MPI_Barrier(MPI_COMM_WORLD); + } + } else { + // Fallback for non power-of-two number of ranks. + for (int phase = 0; phase < size; ++phase) { + int partner = -1; + if ((phase % 2) == 0) { + partner = ((rank % 2) == 0) ? rank + 1 : rank - 1; + } else { + partner = ((rank % 2) == 0) ? rank - 1 : rank + 1; + } + if (partner >= 0 && partner < size) { + CompareSplit(rank, partner, block, &local); + } + MPI_Barrier(MPI_COMM_WORLD); + } + } + } + + std::vector gathered; + if (rank == 0) { + gathered.resize(static_cast(padded_n)); + } + MPI_Gather(local.data(), block, MPI_INT, gathered.data(), block, MPI_INT, 0, MPI_COMM_WORLD); + + std::vector out(static_cast(n)); + if (rank == 0) { + std::copy_n(gathered.begin(), static_cast(n), out.begin()); + } + MPI_Bcast(out.data(), n, MPI_INT, 0, MPI_COMM_WORLD); + + GetOutput() = out; + return true; +} + +bool CheremkhinARadixSortBatcherMPI::PostProcessingImpl() { + return !GetOutput().empty(); +} + +} // namespace cheremkhin_a_radix_sort_batcher diff --git a/tasks/cheremkhin_a_radix_sort_batcher/report.md b/tasks/cheremkhin_a_radix_sort_batcher/report.md new file mode 100644 index 0000000000..043d48078d --- /dev/null +++ b/tasks/cheremkhin_a_radix_sort_batcher/report.md @@ -0,0 +1,125 @@ +# Поразрядная сортировка целых чисел с чётно-нечётным слиянием Бэтчера (SEQ | MPI) + +- Студент: Черемхин Андрей Александрович, группа 3823Б1ПР3 +- Технология: SEQ | MPI +- Вариант: 1 + +## Введение + +Цель задачи — реализовать сортировку целых чисел (int) поразрядным алгоритмом (radix sort) и параллельную MPI-версию, +в которой локально отсортированные блоки объединяются с помощью чётно-нечётного слияния Бэтчера (Batcher odd-even merge) +по процессам. + +## Постановка задачи + +На вход подаётся вектор целых чисел. Требуется вернуть этот вектор, отсортированный по неубыванию. + +Тип входных данных: + +```cpp +using InType = std::vector; +``` + +Тип выходных данных: + +```cpp +using OutType = std::vector; +``` + +## Базовый алгоритм (Sequential) + +Используется LSD radix sort по байтам (4 прохода для 32-битного int): + +- на каждом проходе выполняется стабильная сортировка подсчётом по текущему байту +- для корректной обработки отрицательных чисел используется преобразование ключа: `key = (uint32_t)value ^ 0x80000000` + (это переводит порядок signed в порядок unsigned) + +Сложность: + +- O(4 * n) по времени +- O(n) по памяти + +## Схема распараллеливания (MPI): Бэтчер (odd-even merge) по процессам + +1. Rank 0 дополняет вход до кратности числу процессов (padding значением `INT_MAX`) и распределяет блоки одинакового размера по процессам (`MPI_Scatter`). +2. Каждый процесс сортирует свой блок локально тем же radix sort. +3. Далее выполняется сеть компараторов Бэтчера по процессам (для количества процессов = степень двойки): + - на каждом компараторе два процесса обмениваются своими отсортированными блоками (`MPI_Sendrecv`) + - выполняют слияние двух отсортированных массивов (merge) + - процесс с меньшим rank оставляет “меньшую половину” своего размера, процесс с большим rank — “большую половину” +4. Rank 0 собирает итог (`MPI_Gather`), отбрасывает padding и рассылает результат всем (`MPI_Bcast`), чтобы `GetOutput()` был валиден на каждом процессе. + +Примечание: если число процессов не является степенью двойки, используется безопасный fallback на чётно-нечётные соседние обмены +(odd-even transposition), чтобы задача корректно работала в любом запуске. + +## Детали реализации + +Структура задачи: + +``` +tasks/cheremkhin_a_radix_sort_batcher/ +├── common/include/common.hpp +├── seq/include/ops_seq.hpp +├── seq/src/ops_seq.cpp +├── mpi/include/ops_mpi.hpp +├── mpi/src/ops_mpi.cpp +├── tests/functional/main.cpp +├── tests/performance/main.cpp +├── settings.json +├── info.json +└── report.md +``` + +Классы: + +- `CheremkhinARadixSortBatcherSEQ` +- `CheremkhinARadixSortBatcherMPI` + +## Проверка корректности + +Functional-тесты (`tests/functional/main.cpp`) проверяют: + +- сортировку уже отсортированного массива +- сортировку в обратном порядке +- наличие отрицательных чисел и повторов +- тривиальные случаи (1 элемент) + +Ожидаемый результат вычисляется как `std::sort` над копией входа. + +## Производительность + +Ниже приведены результаты одного прогона. Базовая точка для расчёта ускорения — SEQ (1 запуск). + +pipeline: + +| Mode | Count | Time, s | Speedup | Efficiency | +|------|------:|--------:|--------:|-----------:| +| seq | 1 | 0.1738711160 | 1.00 | N/A | +| mpi | 1 | 0.2733182060 | 0.64 | 63.6% | +| mpi | 2 | 0.2901480690 | 0.60 | 30.0% | +| mpi | 3 | 0.3290196940 | 0.53 | 17.6% | +| mpi | 4 | 0.3667764640 | 0.47 | 11.9% | +| mpi | 5 | 0.3904221110 | 0.45 | 8.9% | +| mpi | 12 | 0.7389914400 | 0.24 | 2.0% | + +task_run: + +| Mode | Count | Time, s | Speedup | Efficiency | +|------|------:|--------:|--------:|-----------:| +| seq | 1 | 0.1557295400 | 1.00 | N/A | +| mpi | 1 | 0.2596250240 | 0.60 | 60.0% | +| mpi | 2 | 0.2384910110 | 0.65 | 32.6% | +| mpi | 3 | 0.3234444180 | 0.48 | 16.0% | +| mpi | 4 | 0.3261312520 | 0.48 | 11.9% | +| mpi | 5 | 0.2590720800 | 0.60 | 12.0% | +| mpi | 12 | 0.6011320870 | 0.26 | 2.2% | + + +- В данной реализации основное время при увеличении числа процессов уходит в коммуникации и многократные слияния в сети Бэтчера. +- Для числа процессов, не являющегося степенью двойки (например, 3, 5, 12), используется fallback (odd-even transposition), который добавляет дополнительные фазы обмена и ухудшает масштабируемость. + + +## Источники + +1. [Материалы курса: отчёт (требования и структура)](https://learning-process.github.io/parallel_programming_course/ru/common_information/report.html#overview-and-placement) + diff --git a/tasks/cheremkhin_a_radix_sort_batcher/seq/include/ops_seq.hpp b/tasks/cheremkhin_a_radix_sort_batcher/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..d9e8e653ba --- /dev/null +++ b/tasks/cheremkhin_a_radix_sort_batcher/seq/include/ops_seq.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "cheremkhin_a_radix_sort_batcher/common/include/common.hpp" + +namespace cheremkhin_a_radix_sort_batcher { + +class CheremkhinARadixSortBatcherSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit CheremkhinARadixSortBatcherSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace cheremkhin_a_radix_sort_batcher diff --git a/tasks/cheremkhin_a_radix_sort_batcher/seq/src/ops_seq.cpp b/tasks/cheremkhin_a_radix_sort_batcher/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..63045ca455 --- /dev/null +++ b/tasks/cheremkhin_a_radix_sort_batcher/seq/src/ops_seq.cpp @@ -0,0 +1,80 @@ +#include "cheremkhin_a_radix_sort_batcher/seq/include/ops_seq.hpp" + +#include +#include +#include + +#include "cheremkhin_a_radix_sort_batcher/common/include/common.hpp" + +namespace cheremkhin_a_radix_sort_batcher { + +namespace { + +constexpr std::uint32_t kSignMask = 0x80000000U; +constexpr std::size_t kRadix = 256; + +inline std::uint8_t GetByteForRadixSort(int v, int byte_idx) { + const std::uint32_t key = static_cast(v) ^ kSignMask; + return static_cast((key >> (static_cast(byte_idx) * 8U)) & 0xFFU); +} + +std::vector RadixSortSigned32(const std::vector &in) { + if (in.size() <= 1) { + return in; + } + + std::vector a; + a.reserve(in.size()); + a.assign(in.begin(), in.end()); + std::vector tmp(in.size()); + + for (int byte_idx = 0; byte_idx < 4; ++byte_idx) { + std::vector cnt(kRadix, 0); + for (int v : a) { + ++cnt[GetByteForRadixSort(v, byte_idx)]; + } + + std::vector pos(kRadix); + std::size_t sum = 0; + for (std::size_t i = 0; i < kRadix; ++i) { + pos[i] = sum; + sum += cnt[i]; + } + + for (int v : a) { + const std::uint8_t b = GetByteForRadixSort(v, byte_idx); + tmp[pos[b]++] = v; + } + + a.swap(tmp); + } + + return a; +} + +} // namespace + +CheremkhinARadixSortBatcherSEQ::CheremkhinARadixSortBatcherSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = {}; +} + +bool CheremkhinARadixSortBatcherSEQ::ValidationImpl() { + return !GetInput().empty(); +} + +bool CheremkhinARadixSortBatcherSEQ::PreProcessingImpl() { + return true; +} + +bool CheremkhinARadixSortBatcherSEQ::RunImpl() { + GetOutput() = RadixSortSigned32(GetInput()); + return true; +} + +bool CheremkhinARadixSortBatcherSEQ::PostProcessingImpl() { + return !GetOutput().empty(); +} + +} // namespace cheremkhin_a_radix_sort_batcher diff --git a/tasks/cheremkhin_a_radix_sort_batcher/settings.json b/tasks/cheremkhin_a_radix_sort_batcher/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/cheremkhin_a_radix_sort_batcher/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/cheremkhin_a_radix_sort_batcher/tests/.clang-tidy b/tasks/cheremkhin_a_radix_sort_batcher/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/cheremkhin_a_radix_sort_batcher/tests/.clang-tidy @@ -0,0 +1,13 @@ +InheritParentConfig: true + +Checks: > + -modernize-loop-convert, + -cppcoreguidelines-avoid-goto, + -cppcoreguidelines-avoid-non-const-global-variables, + -misc-use-anonymous-namespace, + -modernize-use-std-print, + -modernize-type-traits + +CheckOptions: + - key: readability-function-cognitive-complexity.Threshold + value: 50 # Relaxed for tests diff --git a/tasks/cheremkhin_a_radix_sort_batcher/tests/functional/main.cpp b/tasks/cheremkhin_a_radix_sort_batcher/tests/functional/main.cpp new file mode 100644 index 0000000000..b3ea1dcd06 --- /dev/null +++ b/tasks/cheremkhin_a_radix_sort_batcher/tests/functional/main.cpp @@ -0,0 +1,87 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "cheremkhin_a_radix_sort_batcher/common/include/common.hpp" +#include "cheremkhin_a_radix_sort_batcher/mpi/include/ops_mpi.hpp" +#include "cheremkhin_a_radix_sort_batcher/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" + +namespace cheremkhin_a_radix_sort_batcher { + +namespace { + +std::vector SortedCopy(std::vector v) { + std::ranges::sort(v); + return v; +} + +} // namespace + +class CheremkhinARadixSortBatcherRunFuncTestsProcesses : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + const auto &input = std::get<0>(test_param); + const int first = input.empty() ? 0 : input[0]; + std::string name = std::to_string(input.size()) + "_" + std::to_string(first); + std::ranges::replace_if(name, [](char c) { + const auto uc = static_cast(c); + return !std::isalnum(uc) && c != '_'; + }, '_'); + return name; + } + + protected: + void SetUp() override { + const TestType params = std::get<2>(GetParam()); + input_data_ = std::get<0>(params); + correct_answer_ = std::get<1>(params); + } + + bool CheckTestOutputData(OutType &output_data) final { + return output_data == correct_answer_; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + OutType correct_answer_; + InType input_data_; +}; + +namespace { + +TEST_P(CheremkhinARadixSortBatcherRunFuncTestsProcesses, SortIntVector) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + std::make_tuple(InType{5, 4, 3, 2, 1}, SortedCopy(InType{5, 4, 3, 2, 1})), + std::make_tuple(InType{1, 2, 3, 4, 5}, SortedCopy(InType{1, 2, 3, 4, 5})), + std::make_tuple(InType{0, -1, -5, 3, 3, 2, -1}, SortedCopy(InType{0, -1, -5, 3, 3, 2, -1})), + std::make_tuple(InType{42}, SortedCopy(InType{42})), + std::make_tuple(InType{1000000, -1000000, 7, 0, -7, 7}, SortedCopy(InType{1000000, -1000000, 7, 0, -7, 7}))}; + +const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_cheremkhin_a_radix_sort_batcher), + ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_cheremkhin_a_radix_sort_batcher)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = CheremkhinARadixSortBatcherRunFuncTestsProcesses::PrintFuncTestName< + CheremkhinARadixSortBatcherRunFuncTestsProcesses>; + +INSTANTIATE_TEST_SUITE_P(RadixSortBatcherTest, CheremkhinARadixSortBatcherRunFuncTestsProcesses, kGtestValues, + kPerfTestName); + +} // namespace + +} // namespace cheremkhin_a_radix_sort_batcher diff --git a/tasks/cheremkhin_a_radix_sort_batcher/tests/performance/main.cpp b/tasks/cheremkhin_a_radix_sort_batcher/tests/performance/main.cpp new file mode 100644 index 0000000000..8abd15f672 --- /dev/null +++ b/tasks/cheremkhin_a_radix_sort_batcher/tests/performance/main.cpp @@ -0,0 +1,100 @@ +#include +#include + +#include +#include +#include +#include + +#include "cheremkhin_a_radix_sort_batcher/common/include/common.hpp" +#include "cheremkhin_a_radix_sort_batcher/mpi/include/ops_mpi.hpp" +#include "cheremkhin_a_radix_sort_batcher/seq/include/ops_seq.hpp" +#include "performance/include/performance.hpp" +#include "util/include/perf_test_util.hpp" + +namespace cheremkhin_a_radix_sort_batcher { + +namespace { + +InType MakeInput(std::size_t n) { + InType in; + in.resize(n); + + std::uint32_t x = 123456789U; + for (std::size_t i = 0; i < n; ++i) { + x = (1103515245U * x) + 12345U; + const std::int32_t v = static_cast(x ^ (x >> 16)) - static_cast(1U << 30); + in[i] = static_cast(v); + } + + return in; +} + +} // namespace + +class RadixSortBatcherRunPerfTestProcesses : public ppc::util::BaseRunPerfTests { + // Keep the test heavy enough but still runnable for multiple MPI counts. + static constexpr std::size_t kN = 6000000U; + + InType input_data_; + OutType correct_answer_; + int rank_ = 0; + bool mpi_inited_ = false; + + void SetUp() override { + int inited = 0; + MPI_Initialized(&inited); + mpi_inited_ = (inited != 0); + if (mpi_inited_) { + MPI_Comm_rank(MPI_COMM_WORLD, &rank_); + } else { + rank_ = 0; + } + + if (!mpi_inited_ || rank_ == 0) { + input_data_ = MakeInput(kN); + correct_answer_ = input_data_; + std::ranges::sort(correct_answer_); + } else { + input_data_.clear(); + correct_answer_.clear(); + } + } + + bool CheckTestOutputData(OutType &output_data) final { + if (mpi_inited_ && rank_ != 0) { + return true; + } + return output_data == correct_answer_; + } + + InType GetTestInputData() final { + return input_data_; + } + + void SetPerfAttributes(ppc::performance::PerfAttr &perf_attrs) override { + const auto t0 = std::chrono::high_resolution_clock::now(); + perf_attrs.current_timer = [t0] { + auto now = std::chrono::high_resolution_clock::now(); + auto ns = std::chrono::duration_cast(now - t0).count(); + return static_cast(ns) * 1e-9; + }; + perf_attrs.num_running = 1; + } +}; + +TEST_P(RadixSortBatcherRunPerfTestProcesses, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_cheremkhin_a_radix_sort_batcher); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = RadixSortBatcherRunPerfTestProcesses::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, RadixSortBatcherRunPerfTestProcesses, kGtestValues, kPerfTestName); + +} // namespace cheremkhin_a_radix_sort_batcher