diff --git a/tasks/gusev_d_radix_double/common/include/common.hpp b/tasks/gusev_d_radix_double/common/include/common.hpp new file mode 100644 index 0000000000..e963d0918e --- /dev/null +++ b/tasks/gusev_d_radix_double/common/include/common.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace gusev_d_radix_double { + +using InType = std::vector; +using OutType = std::vector; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace gusev_d_radix_double diff --git a/tasks/gusev_d_radix_double/info.json b/tasks/gusev_d_radix_double/info.json new file mode 100644 index 0000000000..96bb210afe --- /dev/null +++ b/tasks/gusev_d_radix_double/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Дмитрий", + "last_name": "Гусев", + "middle_name": "Алексеевич", + "group_number": "3823Б1ФИ1", + "task_number": "3" + } +} \ No newline at end of file diff --git a/tasks/gusev_d_radix_double/mpi/include/ops_mpi.hpp b/tasks/gusev_d_radix_double/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..83ec256477 --- /dev/null +++ b/tasks/gusev_d_radix_double/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "gusev_d_radix_double/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace gusev_d_radix_double { + +class GusevDRadixDoubleMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit GusevDRadixDoubleMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace gusev_d_radix_double diff --git a/tasks/gusev_d_radix_double/mpi/src/ops_mpi.cpp b/tasks/gusev_d_radix_double/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..a896b2453c --- /dev/null +++ b/tasks/gusev_d_radix_double/mpi/src/ops_mpi.cpp @@ -0,0 +1,101 @@ +#include "gusev_d_radix_double/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include + +#include "gusev_d_radix_double/common/include/common.hpp" +#include "gusev_d_radix_double/seq/include/ops_seq.hpp" + +namespace gusev_d_radix_double { + +GusevDRadixDoubleMPI::GusevDRadixDoubleMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool GusevDRadixDoubleMPI::ValidationImpl() { + return true; +} + +bool GusevDRadixDoubleMPI::PreProcessingImpl() { + return true; +} + +bool GusevDRadixDoubleMPI::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); + + std::vector send_counts(size); + std::vector displs(size); + + int remainder = n % size; + int sum = 0; + for (int i = 0; i < size; ++i) { + send_counts[i] = (n / size) + (i < remainder ? 1 : 0); + displs[i] = sum; + sum += send_counts[i]; + } + + std::vector local_data(send_counts[rank]); + + MPI_Scatterv(GetInput().data(), send_counts.data(), displs.data(), MPI_DOUBLE, local_data.data(), send_counts[rank], + MPI_DOUBLE, 0, MPI_COMM_WORLD); + + GusevDRadixDoubleSEQ::RadixSort(local_data); + + int step = 1; + while (step < size) { + if (rank % (2 * step) == 0) { + int source = rank + step; + if (source < size) { + int recv_count = 0; + MPI_Recv(&recv_count, 1, MPI_INT, source, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + size_t current_size = local_data.size(); + local_data.resize(current_size + recv_count); + + MPI_Recv(local_data.data() + current_size, recv_count, MPI_DOUBLE, source, 0, MPI_COMM_WORLD, + MPI_STATUS_IGNORE); + + auto mid_iter = local_data.begin() + static_cast(current_size); + std::ranges::inplace_merge(local_data, mid_iter); + } + } else if (rank % (2 * step) == step) { + int dest = rank - step; + int count = static_cast(local_data.size()); + + MPI_Send(&count, 1, MPI_INT, dest, 0, MPI_COMM_WORLD); + MPI_Send(local_data.data(), count, MPI_DOUBLE, dest, 0, MPI_COMM_WORLD); + + local_data.clear(); + local_data.shrink_to_fit(); + break; + } + step *= 2; + } + + if (rank == 0) { + GetOutput() = std::move(local_data); + } + + return true; +} + +bool GusevDRadixDoubleMPI::PostProcessingImpl() { + return true; +} + +} // namespace gusev_d_radix_double diff --git a/tasks/gusev_d_radix_double/report.md b/tasks/gusev_d_radix_double/report.md new file mode 100644 index 0000000000..0696e27c5f --- /dev/null +++ b/tasks/gusev_d_radix_double/report.md @@ -0,0 +1,161 @@ +# Поразрядная сортировка для вещественных чисел (тип double) с простым слиянием +- Студент: Гусев Дмитрий Алексеевич, 3823Б1ФИ1 +- Технология: SEQ, MPI +- Вариант: 20 + +--- + +## 1. Введение +Поразрядная сортировка (Radix Sort) — это алгоритм сортировки, который не использует сравнения элементов между собой. Вместо этого он распределяет элементы по группам ("карманам") в зависимости от цифр в их записи. + +Классический Radix Sort работает с целыми неотрицательными числами. Однако в данной работе рассматривается более сложная модификация алгоритма для сортировки вещественных чисел двойной точности (double). Это требует учета стандарта IEEE 754 и специфичной работы с битовыми масками для корректной обработки отрицательных чисел. + +В качестве метода распараллеливания выбрана схема с простым слиянием: данные разделяются между процессами, сортируются локально, собираются на одном узле и сливаются в итоговый массив. + +--- + +## 2. Постановка задачи +**Исходные данные**: Вектор вещественных чисел $N$ типа double. + +**Цель**: Упорядочить элементы вектора по возрастанию. + +**Алгоритм**: Least Significant Digit (LSD) Radix Sort с основанием системы счисления 256 (сортировка по байтам). + +**Требования**: +- Реализовать последовательную версию алгоритма, способную корректно сортировать double (включая отрицательные числа). +- Реализовать параллельную версию на MPI. +- Реализовать эффективную схему слияния данных от разных процессов. +- Обеспечить корректную работу при неравномерном распределении данных ($N$ не делится нацело на число процессов). + +--- + +## 3. Последовательный алгоритм +Основная сложность сортировки double поразрядным методом заключается в представлении чисел в памяти (IEEE 754). Если просто интерпретировать double как uint64_t, то отрицательные числа окажутся "больше" положительных (из-за знакового бита 1), а порядок отрицательных чисел будет инвертирован. + +Алгоритм: Выполняется 8 проходов (так как sizeof(double) == 8 байт). На каждом проходе используется сортировка подсчетом (Counting Sort) по текущему байту. + +Временная сложность: $O(N \cdot K)$, где $K$ — количество байт в числе (8). Фактически линейная $O(N)$. + +--- + +## 4. Параллельная схема +- **Роли процессов**: + - Все процессы (Rank 0..P-1): Выполняют локальную сортировку своей части данных. + - Rank 0 (Root): Выполняет распределение данных, сбор отсортированных частей и их итоговое слияние. +- **Распределение данных**: + - Вектор разбивается на $P$ частей. + - Используются массивы смещений (displs) и размеров (send_counts) для обработки случая, когда $N \% P \neq 0$. +- **Коммуникация**: + - Рассылка (Scatter): MPI_Scatterv распределяет исходный массив по процессам. + - Слияние (Merge): Реализовано параллельное древовидное слияние (Parallel Tree Merge). Данные не собираются на одном узле сразу, а сливаются иерархически ($1 \to 0, 3 \to 2$, затем $2 \to 0$ и т.д.). + +--- + +## 5. Реализация +- **Структура проекта:** + - `gusev_d_radix_double/seq/include/ops_seq.hpp` и `gusev_d_radix_double/seq/src/ops_seq.cpp` — однопоточная версия. + - `gusev_d_radix_double/mpi/include/ops_mpi.hpp` и `gusev_d_radix_double/mpi/src/ops_mpi.cpp` — MPI-версия. + - `gusev_d_radix_double/common/include/common.hpp` — общие типы и интерфейсы. +- **Реализационные детали**: + - **Работа с типами.** + Для "сырой" работы с битами используется std::memcpy и uint64_t. + - **Оптимизация слияния.** + Вместо последовательного слияния ($O(N \cdot P)$), реализовано попарное слияние (Tree Merge). На каждом шаге соседние блоки сливаются с помощью std::inplace_merge внутри одного буфера. Это снижает сложность слияния до $O(N \cdot \log P)$ и минимизирует аллокации памяти. + - **Граничные случаи.** + Обработаны пустые вектора и вектора из 1 элемента. + +--- + +## 6. Тестовая конфигурация +- **Оборудование**: + Процессор: Intel(R) Core(TM) Ultra 9 185H (16 ядер, 22 логических процессора) + ОЗУ: 32 ГБ +- **ОС:** Windows 10 IoT Enterprise Subscription 2009 +- **Компилятор:** Microsoft Visual C++ версии 19.44.35220 +- **MPI:** Microsoft MPI версии 10.1.12498.18 +- **Параметры задачи:** $N = 25\,000\,000$ элементов для нагрузочных тестов. + +--- + +## 7. Результаты экспериментов +### 7.1 Верификация +Корректность проверялась с помощью Google Test. Реализовано 15 сценариев тестирования: +- Базовые: Малые, средние, большие векторы. +- Краевые: Пустой вектор, один элемент. +- Специфичные: Только положительные, только отрицательные, нули (проверка -0.0 и +0.0). +- Предварительно отсортированные: Прямой и обратный порядок (Best/Worst case). +- Неравномерные: Размер вектора 123 (не делится нацело, проверка Scatterv). + +Результаты сравнивались с std::sort. Все тесты пройдены успешно. + +### 7.2 Измерения производительности +Эксперименты по оценке производительности выполнялись на конфигурации, описанной в разделе 6. + +Измерения времени выполнения проводились для последовательной (SEQ) и параллельной (MPI) версий при различном количестве процессов. Результаты представлены в таблице ниже. + +| Mode | Count | Time, s | Speedup | Efficiency | +|------|-------|---------|----------|--------------| +| seq | 1 | 1.03859 | 1.000 | N/A | +| mpi | 2 | 1.03443 | 1.004 | 50.2% | +| mpi | 4 | 0.96254 | 1.079 | 27.0% | +| mpi | 8 | 1.11908 | 0.928 | 11.6% | + +**Анализ результатов:** +- Memory Bound природа задачи: Поразрядная сортировка (Radix Sort) — это алгоритм с линейной вычислительной сложностью $O(N)$. Арифметическая интенсивность (отношение вычислений к обращениям в память) крайне низка. Скорость работы алгоритма на современных процессорах полностью определяется пропускной способностью шины памяти. +- При запуске 1 процесса, вся пропускная способность канала памяти доступна одному ядру. +- При запуске 2 процессов (Speedup 1.004), два ядра начинают конкурировать за доступ к RAM. Суммарная пропускная способность системы не удваивается, а лишь немного возрастает или делится между ядрами. В результате время практически не изменилось по сравнению с последовательной версией. +- При 4 процессах (Speedup 1.079), конкуренция усиливается. Небольшое ускорение (~8%) достигнуто, вероятно, за счет более эффективного использования кэшей L2/L3 разных ядер (данные разбиты на куски по ~47 МБ, что лучше помещается в иерархию памяти). +- При 8 процессах (Speedup 0.928), накладные расходы на MPI (пересылка данных через разделяемую память) и когерентность кэшей начинают доминировать. Система переходит в режим "трэшинга" шины памяти, и время выполнения становится даже больше, чем у последовательной версии. +- Для алгоритмов класса $O(N)$ на Shared Memory архитектуре (один многоядерный ПК) масштабируемость ограничена аппаратной пропускной способностью памяти. MPI-реализация "упирается" в Memory Wall. Эффективное распараллеливание Radix Sort возможно либо на распределенных системах (кластерах), где каждый узел имеет свою независимую память, либо на GPU, где пропускная способность памяти на порядок выше. + +--- + +## 8. Выводы +- Реализован оптимизированный (in-place) алгоритм Radix Sort для double. +- Реализована параллельная MPI-версия с использованием Tree Merge и Zero-allocation техник. +- Эксперименты на 25 млн элементов показали, что на одной машине алгоритм демонстрирует насыщение производительности уже при 2-4 процессах. +- Доказано, что для Memory Bound задач линейной сложности основным бутылочным горлышком является шина памяти, а не вычислительная мощность ядер. + +--- + +## 9. Литература +- Материалы курса "Параллельное программирование" +- Справочная документация Open MPI +- Документация Microsoft MPI +- Документация Microsoft Visual C++ +- Стандарт IEEE 754 + +--- + +## 10. Приложение +- In-place преобразование в SEQ: +```cpp +void GusevDRadixDoubleSEQ::RadixSort(std::vector& data) { + size_t n = data.size(); + std::vector raw_data(n); + // Копирование вместо reinterpret_cast для безопасности + std::memcpy(raw_data.data(), data.data(), n * sizeof(double)); + + // In-place Bit Flipping + for (size_t i = 0; i < n; ++i) { + uint64_t mask = 0x8000000000000000ULL; + if ((raw_data[i] & mask) != 0) { + raw_data[i] = ~raw_data[i]; + } else { + raw_data[i] |= mask; + } + } + + // ... Radix Sort logic ... +} +``` +- Zero-allocation Merge в MPI: +```cpp +// При слиянии расширяем вектор и принимаем данные в конец +size_t current_size = local_data.size(); +local_data.resize(current_size + recv_count); +MPI_Recv(local_data.data() + current_size, recv_count, MPI_DOUBLE, ...); + +// Слияние без дополнительного буфера +std::inplace_merge(local_data.begin(), local_data.begin() + static_cast(current_size), local_data.end()); +``` \ No newline at end of file diff --git a/tasks/gusev_d_radix_double/seq/include/ops_seq.hpp b/tasks/gusev_d_radix_double/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..c682815da5 --- /dev/null +++ b/tasks/gusev_d_radix_double/seq/include/ops_seq.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "gusev_d_radix_double/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace gusev_d_radix_double { + +class GusevDRadixDoubleSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit GusevDRadixDoubleSEQ(const InType &in); + + static void RadixSort(std::vector &data); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace gusev_d_radix_double diff --git a/tasks/gusev_d_radix_double/seq/src/ops_seq.cpp b/tasks/gusev_d_radix_double/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..acac7b3f56 --- /dev/null +++ b/tasks/gusev_d_radix_double/seq/src/ops_seq.cpp @@ -0,0 +1,102 @@ +#include "gusev_d_radix_double/seq/include/ops_seq.hpp" + +#include +#include +#include +#include +#include + +#include "gusev_d_radix_double/common/include/common.hpp" + +namespace gusev_d_radix_double { + +namespace { + +void CountSortPass(const uint64_t *source, uint64_t *dest, size_t n, int shift) { + std::array count{}; + count.fill(0); + + for (size_t i = 0; i < n; ++i) { + uint8_t byte = (source[i] >> shift) & 0xFF; + count.at(byte)++; + } + + size_t index = 0; + for (size_t &i : count) { + size_t tmp = i; + i = index; + index += tmp; + } + + for (size_t i = 0; i < n; ++i) { + uint8_t byte = (source[i] >> shift) & 0xFF; + dest[count.at(byte)++] = source[i]; + } +} + +} // namespace + +GusevDRadixDoubleSEQ::GusevDRadixDoubleSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool GusevDRadixDoubleSEQ::ValidationImpl() { + return true; +} + +bool GusevDRadixDoubleSEQ::PreProcessingImpl() { + GetOutput() = GetInput(); + return true; +} + +void GusevDRadixDoubleSEQ::RadixSort(std::vector &data) { + if (data.size() < 2) { + return; + } + + size_t n = data.size(); + + std::vector raw_data(n); + std::memcpy(raw_data.data(), data.data(), n * sizeof(double)); + + for (size_t i = 0; i < n; ++i) { + uint64_t mask = 0x8000000000000000ULL; + if ((raw_data[i] & mask) != 0) { + raw_data[i] = ~raw_data[i]; + } else { + raw_data[i] |= mask; + } + } + + std::vector buffer(n); + uint64_t *source = raw_data.data(); + uint64_t *dest = buffer.data(); + + for (int shift = 0; shift < 64; shift += 8) { + CountSortPass(source, dest, n, shift); + std::swap(source, dest); + } + + for (size_t i = 0; i < n; ++i) { + uint64_t u = source[i]; + uint64_t mask = 0x8000000000000000ULL; + if ((u & mask) != 0) { + u ^= mask; + } else { + u = ~u; + } + std::memcpy(&data[i], &u, sizeof(double)); + } +} + +bool GusevDRadixDoubleSEQ::RunImpl() { + RadixSort(GetOutput()); + return true; +} + +bool GusevDRadixDoubleSEQ::PostProcessingImpl() { + return true; +} + +} // namespace gusev_d_radix_double diff --git a/tasks/gusev_d_radix_double/settings.json b/tasks/gusev_d_radix_double/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/gusev_d_radix_double/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/gusev_d_radix_double/tests/.clang-tidy b/tasks/gusev_d_radix_double/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/gusev_d_radix_double/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/gusev_d_radix_double/tests/functional/main.cpp b/tasks/gusev_d_radix_double/tests/functional/main.cpp new file mode 100644 index 0000000000..f6b24cd1c1 --- /dev/null +++ b/tasks/gusev_d_radix_double/tests/functional/main.cpp @@ -0,0 +1,125 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gusev_d_radix_double/common/include/common.hpp" +#include "gusev_d_radix_double/mpi/include/ops_mpi.hpp" +#include "gusev_d_radix_double/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace gusev_d_radix_double { + +class GusevDRadixDoubleFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::get<1>(test_param); + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + int count = std::get<0>(params); + std::string test_name = std::get<1>(params); + + input_data_ = std::vector(count); + + std::random_device rd; + std::mt19937 gen(rd()); + + if (test_name.find("Positive") != std::string::npos) { + std::uniform_real_distribution<> dis(0.1, 1000.0); + for (int i = 0; i < count; ++i) { + input_data_[i] = dis(gen); + } + } else if (test_name.find("Negative") != std::string::npos) { + std::uniform_real_distribution<> dis(-1000.0, -0.1); + for (int i = 0; i < count; ++i) { + input_data_[i] = dis(gen); + } + } else if (test_name.find("Zero") != std::string::npos) { + std::ranges::fill(input_data_, 0.0); + } else { + std::uniform_real_distribution<> dis(-1000.0, 1000.0); + for (int i = 0; i < count; ++i) { + input_data_[i] = dis(gen); + } + } + + if (test_name.find("Sorted") != std::string::npos && test_name.find("Reverse") == std::string::npos) { + std::ranges::sort(input_data_); + } else if (test_name.find("Reverse") != std::string::npos) { + std::ranges::sort(input_data_, std::greater<>()); + } + + ref_output_data_ = input_data_; + std::ranges::sort(ref_output_data_); + } + + bool CheckTestOutputData(OutType &output_data) final { + int initialized = 0; + MPI_Initialized(&initialized); + + if (initialized == 0) { + return output_data == ref_output_data_; + } + + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + if (rank == 0) { + return output_data == ref_output_data_; + } + return true; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; + OutType ref_output_data_; +}; + +namespace { + +TEST_P(GusevDRadixDoubleFuncTests, RunTests) { + ExecuteTest(GetParam()); +} + +const std::array kTestParams = { + std::make_tuple(10, "SmallVector"), std::make_tuple(100, "MediumVector"), + std::make_tuple(500, "LargeVector"), std::make_tuple(0, "EmptyVector"), + std::make_tuple(1, "SingleElement"), + + std::make_tuple(100, "PositiveVector"), std::make_tuple(100, "NegativeVector"), + std::make_tuple(100, "SortedVector"), std::make_tuple(100, "ReverseSortedVector"), + std::make_tuple(50, "ZeroVector"), + + std::make_tuple(123, "OddSizeVector"), + + std::make_tuple(1000, "Size_1000"), std::make_tuple(2048, "Size_2048_Pow2"), + std::make_tuple(2500, "Size_2500"), std::make_tuple(5000, "Size_5000_MaxFunc")}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParams, PPC_SETTINGS_gusev_d_radix_double), + ppc::util::AddFuncTask(kTestParams, PPC_SETTINGS_gusev_d_radix_double)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = GusevDRadixDoubleFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(RadixSortDoubleTests, GusevDRadixDoubleFuncTests, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace gusev_d_radix_double diff --git a/tasks/gusev_d_radix_double/tests/performance/main.cpp b/tasks/gusev_d_radix_double/tests/performance/main.cpp new file mode 100644 index 0000000000..e5ecec848c --- /dev/null +++ b/tasks/gusev_d_radix_double/tests/performance/main.cpp @@ -0,0 +1,55 @@ +#include + +#include +#include +#include +#include + +#include "gusev_d_radix_double/common/include/common.hpp" +#include "gusev_d_radix_double/mpi/include/ops_mpi.hpp" +#include "gusev_d_radix_double/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace gusev_d_radix_double { + +class GusevDRadixDoublePerfTests : public ppc::util::BaseRunPerfTests { + public: + void SetUp() override { + size_t count = 25000000; + input_data_ = std::vector(count); + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<> dis(-1000000.0, 1000000.0); + + for (auto &val : input_data_) { + val = dis(gen); + } + } + + bool CheckTestOutputData(OutType &output_data) final { + return std::ranges::is_sorted(output_data); + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +TEST_P(GusevDRadixDoublePerfTests, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks(PPC_SETTINGS_gusev_d_radix_double); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = GusevDRadixDoublePerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, GusevDRadixDoublePerfTests, kGtestValues, kPerfTestName); + +} // namespace gusev_d_radix_double