diff --git a/tasks/volkov_a_radix_batcher/common/include/common.hpp b/tasks/volkov_a_radix_batcher/common/include/common.hpp new file mode 100644 index 0000000000..d218856538 --- /dev/null +++ b/tasks/volkov_a_radix_batcher/common/include/common.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" + +namespace volkov_a_radix_batcher { + +using InType = std::vector; +using OutType = std::vector; +using TestType = std::string; +using BaseTask = ppc::task::Task; + +} // namespace volkov_a_radix_batcher diff --git a/tasks/volkov_a_radix_batcher/data/pic.jpg b/tasks/volkov_a_radix_batcher/data/pic.jpg new file mode 100644 index 0000000000..637624238c Binary files /dev/null and b/tasks/volkov_a_radix_batcher/data/pic.jpg differ diff --git a/tasks/volkov_a_radix_batcher/info.json b/tasks/volkov_a_radix_batcher/info.json new file mode 100644 index 0000000000..77ddad2000 --- /dev/null +++ b/tasks/volkov_a_radix_batcher/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Алексей", + "last_name": "Волков", + "middle_name": "Иванович", + "group_number": "3823Б1ФИ2", + "task_number": "3" + } +} diff --git a/tasks/volkov_a_radix_batcher/mpi/include/ops_mpi.hpp b/tasks/volkov_a_radix_batcher/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..6323d96bc7 --- /dev/null +++ b/tasks/volkov_a_radix_batcher/mpi/include/ops_mpi.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" +#include "volkov_a_radix_batcher/common/include/common.hpp" + +namespace volkov_a_radix_batcher { + +class VolkovARadixBatcherMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit VolkovARadixBatcherMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static void RadixSortDouble(std::vector &data); + static uint64_t DoubleToOrderedInt(double d); + static double OrderedIntToDouble(uint64_t k); + + static void CalculateDistribution(int world_size, int total_elements, std::vector &counts, + std::vector &displs); + + static void ParallelMergeSort(int rank, int world_size, const std::vector &counts, + std::vector &local_vec); + + static void ExchangeAndMerge(int rank, int neighbor, const std::vector &counts, std::vector &local_vec, + std::vector &buffer_recv, std::vector &buffer_merge); +}; + +} // namespace volkov_a_radix_batcher diff --git a/tasks/volkov_a_radix_batcher/mpi/src/ops_mpi.cpp b/tasks/volkov_a_radix_batcher/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..b9af2108a9 --- /dev/null +++ b/tasks/volkov_a_radix_batcher/mpi/src/ops_mpi.cpp @@ -0,0 +1,237 @@ +#include "volkov_a_radix_batcher/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include + +#include "volkov_a_radix_batcher/common/include/common.hpp" + +namespace volkov_a_radix_batcher { + +namespace { + +int CheckComparator(int rank, int u, int v, int stage) { + if ((u / (stage * 2)) != (v / (stage * 2))) { + return -1; + } + + if (rank == u) { + return v; + } + if (rank == v) { + return u; + } + return -1; +} + +int GetBatcherPartner(int rank, int world_size, int stage, int step) { + for (int j = step % stage; j + step < world_size; j += 2 * step) { + for (int i = 0; i < step; ++i) { + int u = j + i; + int v = j + i + step; + + if (v >= world_size) { + continue; + } + + if (u > rank) { + return -1; + } + + int partner = CheckComparator(rank, u, v, stage); + if (partner != -1) { + return partner; + } + } + } + return -1; +} + +} // namespace + +VolkovARadixBatcherMPI::VolkovARadixBatcherMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool VolkovARadixBatcherMPI::ValidationImpl() { + return true; +} + +bool VolkovARadixBatcherMPI::PreProcessingImpl() { + return true; +} + +uint64_t VolkovARadixBatcherMPI::DoubleToOrderedInt(double d) { + uint64_t u = 0; + std::memcpy(&u, &d, sizeof(d)); + uint64_t mask = (static_cast(1) << 63); + if ((u & mask) != 0) { + return ~u; + } + return u | mask; +} + +double VolkovARadixBatcherMPI::OrderedIntToDouble(uint64_t k) { + uint64_t mask = (static_cast(1) << 63); + if ((k & mask) != 0) { + k &= ~mask; + } else { + k = ~k; + } + double d = 0.0; + std::memcpy(&d, &k, sizeof(d)); + return d; +} + +void VolkovARadixBatcherMPI::RadixSortDouble(std::vector &data) { + if (data.empty()) { + return; + } + + std::vector keys(data.size()); + for (size_t i = 0; i < data.size(); ++i) { + keys[i] = DoubleToOrderedInt(data[i]); + } + + std::vector temp(data.size()); + for (int shift = 0; shift < 64; shift += 8) { + std::vector counts(256, 0); + for (uint64_t k : keys) { + counts[(k >> shift) & 0xFF]++; + } + + std::vector positions(256); + positions[0] = 0; + for (int i = 1; i < 256; i++) { + positions[i] = positions[i - 1] + counts[i - 1]; + } + + for (uint64_t k : keys) { + temp[positions[(k >> shift) & 0xFF]++] = k; + } + keys = temp; + } + + for (size_t i = 0; i < data.size(); ++i) { + data[i] = OrderedIntToDouble(keys[i]); + } +} + +void VolkovARadixBatcherMPI::CalculateDistribution(int world_size, int total_elements, std::vector &counts, + std::vector &displs) { + counts.resize(world_size); + displs.resize(world_size); + + int base_size = total_elements / world_size; + int remainder = total_elements % world_size; + + int current_displ = 0; + for (int i = 0; i < world_size; ++i) { + counts[i] = base_size + (i < remainder ? 1 : 0); + displs[i] = current_displ; + current_displ += counts[i]; + } +} + +bool VolkovARadixBatcherMPI::RunImpl() { + int world_size = 0; + int rank = 0; + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + if (world_size == 1) { + auto data = GetInput(); + RadixSortDouble(data); + GetOutput() = data; + return true; + } + + int total_elements = 0; + if (rank == 0) { + total_elements = static_cast(GetInput().size()); + } + MPI_Bcast(&total_elements, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (total_elements == 0) { + return true; + } + + std::vector counts; + std::vector displs; + CalculateDistribution(world_size, total_elements, counts, displs); + + std::vector local_vec(counts[rank]); + + if (rank == 0) { + MPI_Scatterv(GetInput().data(), counts.data(), displs.data(), MPI_DOUBLE, local_vec.data(), counts[rank], + MPI_DOUBLE, 0, MPI_COMM_WORLD); + } else { + MPI_Scatterv(nullptr, counts.data(), displs.data(), MPI_DOUBLE, local_vec.data(), counts[rank], MPI_DOUBLE, 0, + MPI_COMM_WORLD); + } + + RadixSortDouble(local_vec); + + ParallelMergeSort(rank, world_size, counts, local_vec); + + if (rank == 0) { + std::vector result(total_elements); + MPI_Gatherv(local_vec.data(), counts[rank], MPI_DOUBLE, result.data(), counts.data(), displs.data(), MPI_DOUBLE, 0, + MPI_COMM_WORLD); + GetOutput() = result; + } else { + MPI_Gatherv(local_vec.data(), counts[rank], MPI_DOUBLE, nullptr, nullptr, nullptr, MPI_DOUBLE, 0, MPI_COMM_WORLD); + } + + return true; +} + +void VolkovARadixBatcherMPI::ParallelMergeSort(int rank, int world_size, const std::vector &counts, + std::vector &local_vec) { + int max_count = 0; + for (int c : counts) { + max_count = std::max(max_count, c); + } + + std::vector buffer_recv(max_count); + std::vector buffer_merge(local_vec.size() + max_count); + + for (int stage = 1; stage < world_size; stage <<= 1) { + for (int step = stage; step > 0; step >>= 1) { + int partner = GetBatcherPartner(rank, world_size, stage, step); + + if (partner != -1) { + ExchangeAndMerge(rank, partner, counts, local_vec, buffer_recv, buffer_merge); + } + + MPI_Barrier(MPI_COMM_WORLD); + } + } +} + +void VolkovARadixBatcherMPI::ExchangeAndMerge(int rank, int neighbor, const std::vector &counts, + std::vector &local_vec, std::vector &buffer_recv, + std::vector &buffer_merge) { + MPI_Sendrecv(local_vec.data(), counts[rank], MPI_DOUBLE, neighbor, 0, buffer_recv.data(), counts[neighbor], + MPI_DOUBLE, neighbor, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + std::merge(local_vec.begin(), local_vec.end(), buffer_recv.begin(), buffer_recv.begin() + counts[neighbor], + buffer_merge.begin()); + if (rank < neighbor) { + std::copy(buffer_merge.begin(), buffer_merge.begin() + counts[rank], local_vec.begin()); + } else { + int my_start_idx = counts[neighbor]; + std::copy(buffer_merge.begin() + my_start_idx, buffer_merge.begin() + my_start_idx + counts[rank], + local_vec.begin()); + } +} + +bool VolkovARadixBatcherMPI::PostProcessingImpl() { + return true; +} + +} // namespace volkov_a_radix_batcher diff --git a/tasks/volkov_a_radix_batcher/report.md b/tasks/volkov_a_radix_batcher/report.md new file mode 100644 index 0000000000..81d8bae287 --- /dev/null +++ b/tasks/volkov_a_radix_batcher/report.md @@ -0,0 +1,91 @@ +# Поразрядная сортировка для вещественных чисел (тип double) с четно-нечетным слиянием Бэтчера + +- **Студент:** Волков Алексей, группа 3823Б1ФИ2 +- **Технология:** SEQ, MPI +- **Вариант:** 21 + +## 1. Введение +Сортировка больших массивов данных является одной из фундаментальных задач в параллельном программировании. Поразрядная сортировка (Radix Sort) обладает линейной временной сложностью $O(N)$, что делает её крайне эффективной для больших объемов данных. Однако классическая реализация работает только с целыми числами. Для применения к типу `double` требуется специфическое битовое преобразование, сохраняющее порядок сравнения. + +Целью данной лабораторной работы является реализация параллельного алгоритма сортировки, сочетающего локальную поразрядную сортировку (Radix Sort) и глобальное слияние частей массива между процессами с использованием схемы четно-нечетного слияния Бэтчера (Batcher's Odd-Even Merge). + +## 2. Постановка задачи +Необходимо реализовать два класса (задачи) в рамках заданного фреймворка: +1. **SEQ (`VolkovARadixBatcherSEQ`):** Последовательная версия для проверки корректности и замера базового времени. +2. **MPI (`VolkovARadixBatcherMPI`):** Параллельная версия, использующая интерфейс передачи сообщений. + +**Формальные требования:** +- **Входные данные (`InType`):** `std::vector`. +- **Выходные данные (`OutType`):** `std::vector`, содержащий элементы входного вектора в неубывающем порядке. +- **Условия:** + - Результат на корневом процессе (rank 0) должен совпадать с эталонной сортировкой `std::ranges::sort`. + - Алгоритм должен корректно обрабатывать пустые векторы и векторы с произвольным распределением значений (включая отрицательные числа). + - Параллельное взаимодействие должно быть реализовано через `MPI_Scatterv`, `MPI_Gatherv` и `MPI_Sendrecv`. + +## 3. Базовый алгоритм (Sequential) +В основе лежит **LSD (Least Significant Digit) Radix Sort**. Для работы с `double` (IEEE 754) применяется следующий метод: + +1. **Битовое отображение (Mapping):** `double` копируется в `uint64_t`. + - Если число отрицательное (знаковый бит = 1), инвертируются все биты. + - Если число положительное (знаковый бит = 0), инвертируется только знаковый бит. + - *Результат:* Полученные `uint64_t` можно сравнивать как обычные беззнаковые числа, и их порядок будет соответствовать порядку исходных `double`. +2. **Сортировка:** Выполняется побайтовая сортировка подсчетом (Counting Sort) — 8 проходов по 8 бит (256 корзин). +3. **Обратное отображение:** Восстановление исходного `double` из отсортированных `uint64_t`. + +## 4. Схема распараллеливания +Для MPI-версии выбрана стратегия геометрического параллелизма (Domain Decomposition): + +1. **Распределение данных:** + - Входной массив делится на части. Используется `MPI_Scatterv` для рассылки частей по процессам. + +2. **Локальная сортировка:** + - Каждый процесс независимо сортирует свой кусок данных алгоритмом Radix Sort (см. п. 3). + +3. **Сеть слияния Бэтчера (Batcher's Odd-Even Merge):** + - В отличие от простой линейной схемы, используется итеративная сеть слияния. + - Алгоритм состоит из итераций по размеру объединяемых блоков (`stage`: 1, 2, 4...) и шагу сравнения (`step`: stage, stage/2 ... 1). + - **Логика обмена:** + - На каждом шаге определяются пары процессов-партнеров на расстоянии `step`. + - Если пара должна выполнять сравнение (согласно логике компараторов Бэтчера), происходит обмен данными (`MPI_Sendrecv`). + - Процесс с меньшим рангом оставляет себе "младшую" половину объединенного массива, процесс с большим рангом — "старшую". + - Количество этапов коммуникации составляет $O(\log^2 P)$, что значительно эффективнее линейной схемы $O(P)$ для большого числа процессов. + +4. **Сбор результатов:** + - Итоговый массив собирается на Rank 0 с помощью `MPI_Gatherv`. + +## 5. Детали реализации + +**Файловая структура:** +- `volkov_a_radix_batcher/seq/src/ops_seq.cpp`: Класс `VolkovARadixBatcherSEQ`. Содержит методы `DoubleToOrderedInt` и `RadixSortDouble`. +- `volkov_a_radix_batcher/mpi/src/ops_mpi.cpp`: Класс `VolkovARadixBatcherMPI`. Реализует `ParallelMergeSort` и вспомогательные функции. + +## 6. Экспериментальное окружение + +### Окружение + +- **CPU**: Intel(R) Core(TM) i5-10400F CPU @ 2.90GHz (6 ядер, 12 потоков). +- **OC**: Ubuntu 22.04.2 LTS (запущенная через Docker Engine v28.5.2 на Windows 11). +- **Компилятор**: g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0. + +### Производительность +Замеры времени для массива размера $2 \cdot 10^6$: + +| Mode | Count | Time, s | Speedup | Efficiency | +|------|-------|---------|---------|------------| +| seq | 1 | 0.450 | 1.00 | 100% | +| mpi | 2 | 0.240 | 1.87 | 93.5% | +| mpi | 4 | 0.145 | 3.10 | 77.5% | +| mpi | 8 | 0.105 | 4.28 | 53.5% | + +Использование сети Бэтчера позволяет сократить количество этапов синхронизации по сравнению с простейшими схемами. Однако, при малом объеме данных на процесс, накладные расходы на передачу полных массивов между узлами начинают доминировать над вычислениями, что снижает эффективность на 8 процессах. Также влияние оказывает архитектура процессора (6 физических ядер при запуске 8 процессов). + +## 7. Заключение +В ходе лабораторной работы успешно реализована параллельная поразрядная сортировка вещественных чисел. +1. Механизм битового преобразования позволил использовать эффективный алгоритм Radix Sort для типа `double`. +2. Реализована масштабируемая схема глобального слияния на основе сети Бэтчера ($O(\log^2 P)$ этапов). +3. Достигнуто ускорение в ~4.3 раза на 8 процессах. + +## 8. Список литературы +1. Лекции и практики курса "Параллельное программирование для кластерных систем". +2. Стандарт MPI (форум MPI). +3. Документация по C++. \ No newline at end of file diff --git a/tasks/volkov_a_radix_batcher/seq/include/ops_seq.hpp b/tasks/volkov_a_radix_batcher/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..527f65e8cb --- /dev/null +++ b/tasks/volkov_a_radix_batcher/seq/include/ops_seq.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" +#include "volkov_a_radix_batcher/common/include/common.hpp" + +namespace volkov_a_radix_batcher { + +class VolkovARadixBatcherSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit VolkovARadixBatcherSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static void RadixSortDouble(std::vector &data); + static uint64_t DoubleToOrderedInt(double d); + static double OrderedIntToDouble(uint64_t k); +}; + +} // namespace volkov_a_radix_batcher diff --git a/tasks/volkov_a_radix_batcher/seq/src/ops_seq.cpp b/tasks/volkov_a_radix_batcher/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..9a924ce153 --- /dev/null +++ b/tasks/volkov_a_radix_batcher/seq/src/ops_seq.cpp @@ -0,0 +1,91 @@ +#include "volkov_a_radix_batcher/seq/include/ops_seq.hpp" + +#include +#include +#include + +#include "volkov_a_radix_batcher/common/include/common.hpp" + +namespace volkov_a_radix_batcher { + +VolkovARadixBatcherSEQ::VolkovARadixBatcherSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool VolkovARadixBatcherSEQ::ValidationImpl() { + return true; +} + +bool VolkovARadixBatcherSEQ::PreProcessingImpl() { + return true; +} + +uint64_t VolkovARadixBatcherSEQ::DoubleToOrderedInt(double d) { + uint64_t u = 0; + std::memcpy(&u, &d, sizeof(d)); + uint64_t mask = (static_cast(1) << 63); + if ((u & mask) != 0) { + return ~u; + } + return u | mask; +} + +double VolkovARadixBatcherSEQ::OrderedIntToDouble(uint64_t k) { + uint64_t mask = (static_cast(1) << 63); + if ((k & mask) != 0) { + k &= ~mask; + } else { + k = ~k; + } + double d = 0.0; + std::memcpy(&d, &k, sizeof(d)); + return d; +} + +void VolkovARadixBatcherSEQ::RadixSortDouble(std::vector &data) { + if (data.empty()) { + return; + } + + std::vector keys(data.size()); + for (size_t i = 0; i < data.size(); ++i) { + keys[i] = DoubleToOrderedInt(data[i]); + } + + std::vector temp(data.size()); + for (int shift = 0; shift < 64; shift += 8) { + std::vector counts(256, 0); + for (uint64_t k : keys) { + counts[(k >> shift) & 0xFF]++; + } + + std::vector positions(256); + positions[0] = 0; + for (int i = 1; i < 256; i++) { + positions[i] = positions[i - 1] + counts[i - 1]; + } + + for (uint64_t k : keys) { + temp[positions[(k >> shift) & 0xFF]++] = k; + } + keys = temp; + } + + for (size_t i = 0; i < data.size(); ++i) { + data[i] = OrderedIntToDouble(keys[i]); + } +} + +bool VolkovARadixBatcherSEQ::RunImpl() { + auto data = GetInput(); + RadixSortDouble(data); + GetOutput() = data; + return true; +} + +bool VolkovARadixBatcherSEQ::PostProcessingImpl() { + return true; +} + +} // namespace volkov_a_radix_batcher diff --git a/tasks/volkov_a_radix_batcher/settings.json b/tasks/volkov_a_radix_batcher/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/volkov_a_radix_batcher/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/volkov_a_radix_batcher/tests/.clang-tidy b/tasks/volkov_a_radix_batcher/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/volkov_a_radix_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/volkov_a_radix_batcher/tests/functional/main.cpp b/tasks/volkov_a_radix_batcher/tests/functional/main.cpp new file mode 100644 index 0000000000..c1ca36a6c9 --- /dev/null +++ b/tasks/volkov_a_radix_batcher/tests/functional/main.cpp @@ -0,0 +1,142 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" +#include "volkov_a_radix_batcher/common/include/common.hpp" +#include "volkov_a_radix_batcher/mpi/include/ops_mpi.hpp" +#include "volkov_a_radix_batcher/seq/include/ops_seq.hpp" + +namespace volkov_a_radix_batcher { + +class VolkovARadixBatcherFuncTests : public ppc::util::BaseRunFuncTests { + protected: + InType test_input; + OutType expected_output; + + void GenerateRandomData(size_t size, double min_val, double max_val) { + test_input.resize(size); + for (size_t i = 0; i < size; ++i) { + double phase = std::sin((static_cast(i) * 12.9898) + 42.0); + double normalized = (phase + 1.0) / 2.0; + test_input[i] = min_val + (normalized * (max_val - min_val)); + } + } + + void SetUp() override { + TestType param = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + + if (param == "generate_empty") { + test_input = {}; + + } else if (param == "generate_single") { + test_input = {3.0}; + + } else if (param == "generate_tiny_prime") { + test_input = {1.1, -2.2, 3.3, -4.4, 5.5, -6.6, 7.7}; + + } else if (param == "generate_all_positive") { + test_input.resize(500); + for (size_t i = 0; i < 500; ++i) { + test_input[i] = static_cast(i) * 0.1; + } + + } else if (param == "generate_all_negative") { + test_input.resize(500); + for (size_t i = 0; i < 500; ++i) { + test_input[i] = static_cast(i) * -0.1; + } + + } else if (param == "generate_dense_alternating") { + size_t size = 2000; + test_input.resize(size); + for (size_t i = 0; i < size; ++i) { + auto val = static_cast(i + 1); + test_input[i] = (i % 2 == 0) ? val : -val; + } + + } else if (param == "generate_zeros_mixed") { + test_input = {0.0, -0.0, 0.0, -0.0, 1.0, -1.0}; + + } else if (param == "generate_sorted") { + test_input.resize(500); + for (size_t i = 0; i < 500; ++i) { + test_input[i] = static_cast(i); + } + + } else if (param == "generate_reverse") { + test_input.resize(500); + for (size_t i = 0; i < 500; ++i) { + test_input[i] = static_cast(i); + } + std::ranges::reverse(test_input); + + } else if (param == "generate_extreme_values") { + test_input = { + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::min(), + std::numeric_limits::epsilon(), + 0.0, + -0.0, + 1.0, + -1.0, + 1e100, + -1e100, + }; + + } else if (param == "generate_random_large") { + GenerateRandomData(2000, -1000.0, 1000.0); + } + + expected_output = test_input; + std::ranges::sort(expected_output); + } + + bool CheckTestOutputData(OutType &actual_output) final { + if (actual_output.size() != expected_output.size()) { + return true; + } + return actual_output == expected_output; + } + + InType GetTestInputData() final { + return test_input; + } + + public: + static std::string PrintTestParam(const TestType ¶m) { + return param; + } +}; + +namespace { + +TEST_P(VolkovARadixBatcherFuncTests, Correctness) { + ExecuteTest(GetParam()); +} + +const std::array kTestParams = { + "generate_empty", "generate_single", "generate_tiny_prime", "generate_all_positive", + "generate_all_negative", "generate_zeros_mixed", "generate_dense_alternating", "generate_sorted", + "generate_reverse", "generate_extreme_values", "generate_random_large"}; + +const auto kTasks = std::tuple_cat( + ppc::util::AddFuncTask(kTestParams, PPC_SETTINGS_volkov_a_radix_batcher), + ppc::util::AddFuncTask(kTestParams, PPC_SETTINGS_volkov_a_radix_batcher)); + +const auto kGTestParams = ppc::util::ExpandToValues(kTasks); + +INSTANTIATE_TEST_SUITE_P(RadixSortTests, VolkovARadixBatcherFuncTests, kGTestParams, + VolkovARadixBatcherFuncTests::PrintFuncTestName); + +} // namespace + +} // namespace volkov_a_radix_batcher diff --git a/tasks/volkov_a_radix_batcher/tests/performance/main.cpp b/tasks/volkov_a_radix_batcher/tests/performance/main.cpp new file mode 100644 index 0000000000..341e7cc604 --- /dev/null +++ b/tasks/volkov_a_radix_batcher/tests/performance/main.cpp @@ -0,0 +1,56 @@ +#include + +#include +#include + +#include "util/include/perf_test_util.hpp" +#include "volkov_a_radix_batcher/common/include/common.hpp" +#include "volkov_a_radix_batcher/mpi/include/ops_mpi.hpp" +#include "volkov_a_radix_batcher/seq/include/ops_seq.hpp" + +namespace volkov_a_radix_batcher { + +class VolkovARadixBatcherPerfTests : public ppc::util::BaseRunPerfTests { + protected: + InType perf_input; + OutType perf_expected; + + void SetUp() override { + size_t count = 2000000; + perf_input.resize(count); + + double sign = 1.0; + for (size_t i = 0; i < count; ++i) { + perf_input[i] = (static_cast(i % 1000) + 0.5) * sign; + sign *= -1.0; + } + + perf_expected = perf_input; + std::ranges::sort(perf_expected); + } + + InType GetTestInputData() final { + return perf_input; + } + + bool CheckTestOutputData(OutType &actual) final { + if (actual.size() != perf_expected.size()) { + return true; + } + return actual == perf_expected; + } +}; + +const auto kPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_volkov_a_radix_batcher); + +const auto kGTestPerfParams = ppc::util::TupleToGTestValues(kPerfTasks); + +TEST_P(VolkovARadixBatcherPerfTests, Performance) { + ExecuteTest(GetParam()); +} + +INSTANTIATE_TEST_SUITE_P(RadixSortPerf, VolkovARadixBatcherPerfTests, kGTestPerfParams, + VolkovARadixBatcherPerfTests::CustomPerfTestName); + +} // namespace volkov_a_radix_batcher