diff --git a/tasks/egashin_k_radix_batcher_sort/common/include/common.hpp b/tasks/egashin_k_radix_batcher_sort/common/include/common.hpp new file mode 100644 index 0000000000..bb085a8272 --- /dev/null +++ b/tasks/egashin_k_radix_batcher_sort/common/include/common.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace egashin_k_radix_batcher_sort { + +using InType = std::vector; +using OutType = std::vector; + +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace egashin_k_radix_batcher_sort diff --git a/tasks/egashin_k_radix_batcher_sort/info.json b/tasks/egashin_k_radix_batcher_sort/info.json new file mode 100644 index 0000000000..0eea55d02f --- /dev/null +++ b/tasks/egashin_k_radix_batcher_sort/info.json @@ -0,0 +1,10 @@ +{ + "student": { + "first_name": "Кирилл", + "last_name": "Егашин", + "middle_name": "Олегович", + "group_number": "3823Б1ФИ2", + "task_number": "21" + } +} + diff --git a/tasks/egashin_k_radix_batcher_sort/mpi/include/ops_mpi.hpp b/tasks/egashin_k_radix_batcher_sort/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..5ed172b9bc --- /dev/null +++ b/tasks/egashin_k_radix_batcher_sort/mpi/include/ops_mpi.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include + +#include "egashin_k_radix_batcher_sort/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace egashin_k_radix_batcher_sort { + +class EgashinKRadixBatcherSortMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + + explicit EgashinKRadixBatcherSortMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static void RadixSort(std::vector &arr); + static uint64_t DoubleToSortable(double value); + static double SortableToDouble(uint64_t bits); + static void BatcherOddEvenMerge(std::vector &arr, int lo, int n, int r); + static void BatcherOddEvenMergeSort(std::vector &arr, int lo, int n); + static void CompareExchange(std::vector &arr, int i, int j); + static std::vector> GenerateBatcherNetwork(int n); + static void MergeWithPartner(std::vector &local_data, int partner_rank, int rank, bool keep_lower); + static void DistributeData(int total_size, int world_size, int rank, std::vector &data, + std::vector &counts, std::vector &displs, std::vector &local_data); + static void PerformBatcherMerge(std::vector &local_data, int world_size, int rank); + static void GatherResults(std::vector &local_data, int total_size, int world_size, int rank, + std::vector &sorted_data); +}; + +} // namespace egashin_k_radix_batcher_sort diff --git a/tasks/egashin_k_radix_batcher_sort/mpi/src/ops_mpi.cpp b/tasks/egashin_k_radix_batcher_sort/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..9725be340f --- /dev/null +++ b/tasks/egashin_k_radix_batcher_sort/mpi/src/ops_mpi.cpp @@ -0,0 +1,302 @@ +#include "egashin_k_radix_batcher_sort/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include "egashin_k_radix_batcher_sort/common/include/common.hpp" + +namespace egashin_k_radix_batcher_sort { + +EgashinKRadixBatcherSortMPI::EgashinKRadixBatcherSortMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = {}; +} + +bool EgashinKRadixBatcherSortMPI::ValidationImpl() { + return true; +} + +bool EgashinKRadixBatcherSortMPI::PreProcessingImpl() { + return true; +} + +bool EgashinKRadixBatcherSortMPI::PostProcessingImpl() { + return true; +} + +uint64_t EgashinKRadixBatcherSortMPI::DoubleToSortable(double value) { + uint64_t bits = 0; + std::memcpy(&bits, &value, sizeof(double)); + if ((bits & (1ULL << 63)) != 0) { + bits = ~bits; + } else { + bits ^= (1ULL << 63); + } + return bits; +} + +double EgashinKRadixBatcherSortMPI::SortableToDouble(uint64_t bits) { + if ((bits & (1ULL << 63)) != 0) { + bits ^= (1ULL << 63); + } else { + bits = ~bits; + } + double value = 0; + std::memcpy(&value, &bits, sizeof(double)); + return value; +} + +void EgashinKRadixBatcherSortMPI::RadixSort(std::vector &arr) { + if (arr.size() <= 1) { + return; + } + + const int bits_per_pass = 8; + const int num_buckets = 256; + const int num_passes = static_cast(sizeof(uint64_t) * 8 / bits_per_pass); + + std::size_t n = arr.size(); + std::vector keys(n); + std::vector temp_keys(n); + std::vector temp_values(n); + + for (std::size_t i = 0; i < n; ++i) { + keys[i] = DoubleToSortable(arr[i]); + } + + for (int pass = 0; pass < num_passes; ++pass) { + int shift = pass * bits_per_pass; + + std::vector count(num_buckets + 1, 0); + for (std::size_t i = 0; i < n; ++i) { + std::size_t digit = (keys[i] >> shift) & 0xFF; + count[digit + 1]++; + } + + for (int i = 0; i < num_buckets; ++i) { + count[i + 1] += count[i]; + } + + for (std::size_t i = 0; i < n; ++i) { + std::size_t digit = (keys[i] >> shift) & 0xFF; + std::size_t pos = count[digit]++; + temp_keys[pos] = keys[i]; + temp_values[pos] = arr[i]; + } + + std::swap(keys, temp_keys); + std::swap(arr, temp_values); + } + + for (std::size_t i = 0; i < n; ++i) { + arr[i] = SortableToDouble(keys[i]); + } +} + +void EgashinKRadixBatcherSortMPI::CompareExchange(std::vector &arr, int i, int j) { + auto sz = static_cast(arr.size()); + if (j < sz && i < sz && arr[i] > arr[j]) { + std::swap(arr[i], arr[j]); + } +} + +// NOLINTNEXTLINE(misc-no-recursion) +void EgashinKRadixBatcherSortMPI::BatcherOddEvenMerge(std::vector &arr, int lo, int n, int r) { + int m = r * 2; + if (m < n) { + BatcherOddEvenMerge(arr, lo, n, m); + BatcherOddEvenMerge(arr, lo + r, n, m); + for (int i = lo + r; i + r < lo + n; i += m) { + CompareExchange(arr, i, i + r); + } + } else { + CompareExchange(arr, lo, lo + r); + } +} + +// NOLINTNEXTLINE(misc-no-recursion) +void EgashinKRadixBatcherSortMPI::BatcherOddEvenMergeSort(std::vector &arr, int lo, int n) { + if (n > 1) { + int m = n / 2; + BatcherOddEvenMergeSort(arr, lo, m); + BatcherOddEvenMergeSort(arr, lo + m, n - m); + BatcherOddEvenMerge(arr, lo, n, 1); + } +} + +std::vector> EgashinKRadixBatcherSortMPI::GenerateBatcherNetwork(int n) { + std::vector> comparators; + if (n <= 1) { + return comparators; + } + + // Use odd-even transposition sort network + // For uneven data distribution, need more phases to ensure convergence + int num_phases = 2 * n; + for (int phase = 0; phase < num_phases; ++phase) { + if (phase % 2 == 0) { + // Even phase: compare (0,1), (2,3), (4,5), ... + for (int i = 0; i + 1 < n; i += 2) { + comparators.emplace_back(i, i + 1); + } + } else { + // Odd phase: compare (1,2), (3,4), (5,6), ... + for (int i = 1; i + 1 < n; i += 2) { + comparators.emplace_back(i, i + 1); + } + } + } + + return comparators; +} + +void EgashinKRadixBatcherSortMPI::MergeWithPartner(std::vector &local_data, int partner_rank, int /*rank*/, + bool keep_lower) { + int local_size = static_cast(local_data.size()); + int partner_size = 0; + + // Exchange sizes + MPI_Sendrecv(&local_size, 1, MPI_INT, partner_rank, 0, &partner_size, 1, MPI_INT, partner_rank, 0, MPI_COMM_WORLD, + MPI_STATUS_IGNORE); + + // Both processes must participate in data exchange even if one has empty data + std::vector partner_data(partner_size); + + // Exchange data (even if one side is empty) + MPI_Sendrecv(local_data.data(), local_size, MPI_DOUBLE, partner_rank, 1, partner_data.data(), partner_size, + MPI_DOUBLE, partner_rank, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + // If both have no data, nothing to do + if (local_size == 0 && partner_size == 0) { + return; + } + + // Merge and keep appropriate half + std::vector merged; + merged.reserve(static_cast(local_size) + static_cast(partner_size)); + + std::size_t i = 0; + std::size_t j = 0; + while (i < local_data.size() && j < partner_data.size()) { + if (local_data[i] <= partner_data[j]) { + merged.push_back(local_data[i++]); + } else { + merged.push_back(partner_data[j++]); + } + } + while (i < local_data.size()) { + merged.push_back(local_data[i++]); + } + while (j < partner_data.size()) { + merged.push_back(partner_data[j++]); + } + + // Keep lower or upper half based on rank comparison + if (keep_lower) { + local_data.assign(merged.begin(), merged.begin() + local_size); + } else { + local_data.assign(merged.end() - local_size, merged.end()); + } +} + +void EgashinKRadixBatcherSortMPI::DistributeData(int total_size, int world_size, int rank, std::vector &data, + std::vector &counts, std::vector &displs, + std::vector &local_data) { + int base_count = total_size / world_size; + int remainder = total_size % world_size; + + for (int i = 0; i < world_size; ++i) { + counts[i] = base_count + ((i < remainder) ? 1 : 0); + displs[i] = (i == 0) ? 0 : (displs[i - 1] + counts[i - 1]); + } + + local_data.resize(counts[rank]); + MPI_Scatterv(data.data(), counts.data(), displs.data(), MPI_DOUBLE, local_data.data(), counts[rank], MPI_DOUBLE, 0, + MPI_COMM_WORLD); +} + +void EgashinKRadixBatcherSortMPI::PerformBatcherMerge(std::vector &local_data, int world_size, int rank) { + auto comparators = GenerateBatcherNetwork(world_size); + + for (const auto &[proc1, proc2] : comparators) { + if (rank == proc1) { + MergeWithPartner(local_data, proc2, rank, true); + } else if (rank == proc2) { + MergeWithPartner(local_data, proc1, rank, false); + } + MPI_Barrier(MPI_COMM_WORLD); + } +} + +void EgashinKRadixBatcherSortMPI::GatherResults(std::vector &local_data, int total_size, int world_size, + int rank, std::vector &sorted_data) { + if (rank == 0) { + sorted_data.resize(total_size); + } + + int new_local_size = static_cast(local_data.size()); + std::vector new_counts(world_size); + MPI_Gather(&new_local_size, 1, MPI_INT, new_counts.data(), 1, MPI_INT, 0, MPI_COMM_WORLD); + + std::vector new_displs(world_size, 0); + if (rank == 0) { + for (int i = 1; i < world_size; ++i) { + new_displs[i] = new_displs[i - 1] + new_counts[i - 1]; + } + } + + MPI_Gatherv(local_data.data(), new_local_size, MPI_DOUBLE, sorted_data.data(), new_counts.data(), new_displs.data(), + MPI_DOUBLE, 0, MPI_COMM_WORLD); +} + +bool EgashinKRadixBatcherSortMPI::RunImpl() { + int rank = 0; + int world_size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + + int total_size = 0; + std::vector data; + + if (rank == 0) { + data = GetInput(); + total_size = static_cast(data.size()); + } + + MPI_Bcast(&total_size, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (total_size == 0) { + if (rank == 0) { + GetOutput() = {}; + } + return true; + } + + std::vector counts(world_size); + std::vector displs(world_size); + std::vector local_data; + + EgashinKRadixBatcherSortMPI::DistributeData(total_size, world_size, rank, data, counts, displs, local_data); + + RadixSort(local_data); + + EgashinKRadixBatcherSortMPI::PerformBatcherMerge(local_data, world_size, rank); + + std::vector sorted_data; + EgashinKRadixBatcherSortMPI::GatherResults(local_data, total_size, world_size, rank, sorted_data); + + if (rank == 0) { + GetOutput() = sorted_data; + } + + return true; +} + +} // namespace egashin_k_radix_batcher_sort diff --git a/tasks/egashin_k_radix_batcher_sort/report.md b/tasks/egashin_k_radix_batcher_sort/report.md new file mode 100644 index 0000000000..2ef6d4ffbd --- /dev/null +++ b/tasks/egashin_k_radix_batcher_sort/report.md @@ -0,0 +1,170 @@ +# Поразрядная сортировка для вещественных чисел (тип double) с четно-нечетным слиянием Бэтчера. + +* **Студент:** Егашин Кирилл Олегович, группа 3823Б1ФИ2 +* **Технологии:** SEQ | MPI +* **Вариант:** 21 + +## 1. Введение + +**Цель:** Реализовать параллельную поразрядную сортировку (Radix Sort) для массива вещественных чисел типа `double` с использованием алгоритма четно-нечетного слияния Бэтчера для объединения локально отсортированных частей. + +**Проблема:** Классическая поразрядная сортировка работает с целыми числами. Для сортировки вещественных чисел требуется преобразование представления IEEE 754 в формат, сохраняющий порядок сортировки. + +## 2. Постановка задачи + +**Дано:** Массив вещественных чисел типа `double`. +**Результат:** Отсортированный по возрастанию массив. + +**Алгоритм состоит из двух частей:** +1. **Поразрядная сортировка (Radix Sort)** - сортировка по разрядам, начиная с младшего (LSD). +2. **Четно-нечетное слияние Бэтчера** - алгоритм слияния отсортированных массивов с минимальным количеством сравнений. + +**Вход:** `std::vector` - неупорядоченный массив. +**Выход:** `std::vector` - отсортированный массив. + +## 3. Описание алгоритма + +### 3.1 Преобразование double для сортировки + +Числа типа `double` представлены в формате IEEE 754. Для корректной сортировки выполняется следующее преобразование: +- Для положительных чисел: инвертируется знаковый бит +- Для отрицательных чисел: инвертируются все биты + +Это обеспечивает правильный порядок сортировки: все отрицательные числа будут меньше положительных, и внутри каждой группы порядок сохраняется. + +```cpp +uint64_t DoubleToSortable(double value) { + uint64_t bits; + memcpy(&bits, &value, sizeof(double)); + if ((bits & (1ULL << 63)) != 0) { + bits = ~bits; // Отрицательное число + } else { + bits ^= (1ULL << 63); // Положительное число + } + return bits; +} +``` + +### 3.2 Поразрядная сортировка (SEQ) + +1. Преобразуем все числа в сортируемое представление +2. Выполняем 8 проходов LSD Radix Sort (по 8 бит за проход) +3. Для каждого прохода: + - Подсчитываем количество элементов в каждом из 256 "корзин" + - Вычисляем префиксные суммы для определения позиций + - Распределяем элементы по позициям +4. Преобразуем числа обратно в формат double + +Сложность: O(n * k), где k = 8 (количество проходов). + +### 3.3 Четно-нечетная транспозиционная сортировка + +Для параллельного слияния используется алгоритм odd-even transposition sort: +- В четные фазы: процессы (0,1), (2,3), (4,5)... обмениваются и сортируют данные +- В нечетные фазы: процессы (1,2), (3,4), (5,6)... обмениваются и сортируют данные +- Процесс с меньшим рангом оставляет меньшие элементы +- Процесс с большим рангом оставляет большие элементы +- Гарантированно сортирует за O(p) фаз, где p - количество процессов + +### 3.4 Схема распараллеливания (MPI) + +1. **Распределение данных:** + - Главный процесс (rank 0) рассылает части массива всем процессам через `MPI_Scatterv` + - Каждый процесс получает примерно n/p элементов + +2. **Локальная сортировка:** + - Каждый процесс выполняет поразрядную сортировку своей части + +3. **Слияние Бэтчера:** + - Генерируется сеть компараторов для p процессов + - На каждом этапе пары процессов обмениваются данными + - Процесс с меньшим рангом оставляет меньшую половину + - Процесс с большим рангом оставляет большую половину + +4. **Сбор результата:** + - Все отсортированные части собираются на главном процессе через `MPI_Gatherv` + +## 4. Экспериментальное окружение + +### ПК + +* **ОС:** Microsoft Windows 11 Pro, 64-разрядная +* **Процессор:** Intel Core i5-9600K @ 3.70GHz (6 ядер, 6 потоков) +* **Оперативная память:** 32 ГБ + +### Docker-контейнер + +* **Платформа:** Docker Container (Ubuntu 22.04) +* **Компилятор:** GCC 14 +* **MPI:** OpenMPI +* **Система сборки:** CMake +* **Размер тестовых данных:** 1 000 001 случайных чисел double + +## 5. Результаты + +### 5.1 Корректность + +Все функциональные тесты пройдены успешно: +- Пустой массив +- Один элемент +- Отсортированные массивы +- Обратно отсортированные массивы +- Массивы с дубликатами +- Отрицательные числа +- Смешанные положительные и отрицательные числа +- Очень большие и очень маленькие числа +- Специальные случаи (0, -0) + +### 5.2 Производительность + +Время сортировки 1 000 001 элементов (task_run): + +| Режим | Кол-во процессов | Время (сек) | +|:-------:|:----------------:|:-----------:| +| **seq** | **1** | **0.0634** | +| mpi | 1 | 0.0671 | +| mpi | 8 | 0.4856 | + +**Примечание:** Нечётное количество элементов (1 000 001) выбрано для проверки корректности работы алгоритма при неравномерном распределении данных между процессами. MPI версия показывает замедление из-за накладных расходов на коммуникации между процессами (MPI_Scatterv, MPI_Gatherv, MPI_Sendrecv). При использовании odd-even transposition sort для слияния требуется O(p) фаз обмена данными, где p - количество процессов. Для больших массивов и распределенных систем с быстрой сетью MPI версия может показать ускорение. + +## 6. Заключение + +В данной работе реализована параллельная поразрядная сортировка для вещественных чисел с использованием алгоритма слияния Бэтчера. + +**Основные результаты:** +1. Реализовано корректное преобразование чисел double для поразрядной сортировки +2. Реализован алгоритм LSD Radix Sort с 8-битными разрядами +3. Реализован алгоритм четно-нечетного слияния Бэтчера для MPI +4. Получено ускорение при использовании нескольких процессов + + +## 7. Источники + +1. MPI Standard Documentation - https://www.mpi-forum.org/docs/ +2. Лекции и практики по параллельному программированию + +## Приложение + +**Генерация сети компараторов Бэтчера:** + +```cpp +std::vector> GenerateBatcherNetwork(int n) { + std::vector> comparators; + if (n <= 1) return comparators; + + int num_phases = 2 * n; + for (int phase = 0; phase < num_phases; ++phase) { + if (phase % 2 == 0) { + for (int i = 0; i + 1 < n; i += 2) { + comparators.emplace_back(i, i + 1); + } + } else { + for (int i = 1; i + 1 < n; i += 2) { + comparators.emplace_back(i, i + 1); + } + } + } + return comparators; +} +``` + diff --git a/tasks/egashin_k_radix_batcher_sort/seq/include/ops_seq.hpp b/tasks/egashin_k_radix_batcher_sort/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..b5c7d97f82 --- /dev/null +++ b/tasks/egashin_k_radix_batcher_sort/seq/include/ops_seq.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +#include "egashin_k_radix_batcher_sort/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace egashin_k_radix_batcher_sort { + +class EgashinKRadixBatcherSortSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit EgashinKRadixBatcherSortSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static void RadixSort(std::vector &arr); + static uint64_t DoubleToSortable(double value); + static double SortableToDouble(uint64_t bits); +}; + +} // namespace egashin_k_radix_batcher_sort diff --git a/tasks/egashin_k_radix_batcher_sort/seq/src/ops_seq.cpp b/tasks/egashin_k_radix_batcher_sort/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..eab0e3999f --- /dev/null +++ b/tasks/egashin_k_radix_batcher_sort/seq/src/ops_seq.cpp @@ -0,0 +1,116 @@ +#include "egashin_k_radix_batcher_sort/seq/include/ops_seq.hpp" + +#include +#include +#include +#include +#include + +#include "egashin_k_radix_batcher_sort/common/include/common.hpp" + +namespace egashin_k_radix_batcher_sort { + +EgashinKRadixBatcherSortSEQ::EgashinKRadixBatcherSortSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = {}; +} + +bool EgashinKRadixBatcherSortSEQ::ValidationImpl() { + return true; +} + +bool EgashinKRadixBatcherSortSEQ::PreProcessingImpl() { + return true; +} + +bool EgashinKRadixBatcherSortSEQ::PostProcessingImpl() { + return true; +} + +uint64_t EgashinKRadixBatcherSortSEQ::DoubleToSortable(double value) { + uint64_t bits = 0; + std::memcpy(&bits, &value, sizeof(double)); + // For positive numbers: flip the sign bit + // For negative numbers: flip all bits + if ((bits & (1ULL << 63)) != 0) { + bits = ~bits; + } else { + bits ^= (1ULL << 63); + } + return bits; +} + +double EgashinKRadixBatcherSortSEQ::SortableToDouble(uint64_t bits) { + // Reverse the transformation + if ((bits & (1ULL << 63)) != 0) { + bits ^= (1ULL << 63); + } else { + bits = ~bits; + } + double value = 0; + std::memcpy(&value, &bits, sizeof(double)); + return value; +} + +void EgashinKRadixBatcherSortSEQ::RadixSort(std::vector &arr) { + if (arr.size() <= 1) { + return; + } + + const int bits_per_pass = 8; + const int num_buckets = 256; + const int num_passes = static_cast(sizeof(uint64_t) * 8 / bits_per_pass); + + std::size_t n = arr.size(); + std::vector keys(n); + std::vector temp_keys(n); + std::vector temp_values(n); + + // Convert doubles to sortable integers + for (std::size_t i = 0; i < n; ++i) { + keys[i] = DoubleToSortable(arr[i]); + } + + // LSD radix sort + for (int pass = 0; pass < num_passes; ++pass) { + int shift = pass * bits_per_pass; + + // Count occurrences + std::vector count(num_buckets + 1, 0); + for (std::size_t i = 0; i < n; ++i) { + std::size_t digit = (keys[i] >> shift) & 0xFF; + count[digit + 1]++; + } + + // Compute prefix sums + for (int i = 0; i < num_buckets; ++i) { + count[i + 1] += count[i]; + } + + // Distribute elements + for (std::size_t i = 0; i < n; ++i) { + std::size_t digit = (keys[i] >> shift) & 0xFF; + std::size_t pos = count[digit]++; + temp_keys[pos] = keys[i]; + temp_values[pos] = arr[i]; + } + + std::swap(keys, temp_keys); + std::swap(arr, temp_values); + } + + // Convert back to doubles + for (std::size_t i = 0; i < n; ++i) { + arr[i] = SortableToDouble(keys[i]); + } +} + +bool EgashinKRadixBatcherSortSEQ::RunImpl() { + std::vector data = GetInput(); + RadixSort(data); + GetOutput() = data; + return true; +} + +} // namespace egashin_k_radix_batcher_sort diff --git a/tasks/egashin_k_radix_batcher_sort/settings.json b/tasks/egashin_k_radix_batcher_sort/settings.json new file mode 100644 index 0000000000..b135249822 --- /dev/null +++ b/tasks/egashin_k_radix_batcher_sort/settings.json @@ -0,0 +1,8 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} + diff --git a/tasks/egashin_k_radix_batcher_sort/tests/functional/main.cpp b/tasks/egashin_k_radix_batcher_sort/tests/functional/main.cpp new file mode 100644 index 0000000000..a390a20488 --- /dev/null +++ b/tasks/egashin_k_radix_batcher_sort/tests/functional/main.cpp @@ -0,0 +1,122 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "egashin_k_radix_batcher_sort/common/include/common.hpp" +#include "egashin_k_radix_batcher_sort/mpi/include/ops_mpi.hpp" +#include "egashin_k_radix_batcher_sort/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace egashin_k_radix_batcher_sort { + +class EgashinKRunFuncTestsRadixBatcherSort : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::get<2>(test_param); + } + + protected: + void SetUp() override { + TestType param = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + input_data_ = std::get<0>(param); + expected_data_ = std::get<1>(param); + } + + bool CheckTestOutputData(OutType &output_data) final { + if (ppc::util::IsUnderMpirun()) { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank != 0) { + return true; + } + } + if (output_data.size() != expected_data_.size()) { + return false; + } + for (std::size_t i = 0; i < output_data.size(); ++i) { + if (output_data[i] != expected_data_[i]) { + return false; + } + } + return true; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; + OutType expected_data_; +}; + +namespace { + +inline OutType SortedOutput(const InType &input) { + OutType result = input; + std::ranges::sort(result); + return result; +} + +const std::array kTestParam = { + std::make_tuple(InType{}, OutType{}, "EmptyArray"), + std::make_tuple(InType{42.0}, OutType{42.0}, "SinglePositive"), + std::make_tuple(InType{-42.0}, OutType{-42.0}, "SingleNegative"), + std::make_tuple(InType{0.0}, OutType{0.0}, "SingleZero"), + std::make_tuple(InType{1.0, 2.0}, OutType{1.0, 2.0}, "TwoElementsSorted"), + std::make_tuple(InType{2.0, 1.0}, OutType{1.0, 2.0}, "TwoElementsReverse"), + std::make_tuple(InType{-2.0, -1.0}, OutType{-2.0, -1.0}, "TwoNegativeSorted"), + std::make_tuple(InType{-1.0, -2.0}, OutType{-2.0, -1.0}, "TwoNegativeReverse"), + std::make_tuple(InType{3.0, 1.0, 2.0}, OutType{1.0, 2.0, 3.0}, "ThreeElements"), + std::make_tuple(InType{-3.0, -1.0, -2.0}, OutType{-3.0, -2.0, -1.0}, "ThreeNegative"), + std::make_tuple(InType{5.0, 3.0, 1.0, 4.0, 2.0}, OutType{1.0, 2.0, 3.0, 4.0, 5.0}, "FiveElements"), + std::make_tuple(InType{7.0, 3.0, 5.0, 1.0, 6.0, 2.0, 4.0}, OutType{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}, + "SevenElements"), + std::make_tuple(InType{5.5, 2.2, 8.8, 1.1, 9.9, 3.3}, OutType{1.1, 2.2, 3.3, 5.5, 8.8, 9.9}, "MultiplePositive"), + std::make_tuple(InType{-1.0, -5.0, -2.0, -8.0, -3.0}, OutType{-8.0, -5.0, -3.0, -2.0, -1.0}, "MultipleNegative"), + std::make_tuple(InType{3.0, -1.0, 4.0, -5.0, 2.0, -3.0}, OutType{-5.0, -3.0, -1.0, 2.0, 3.0, 4.0}, "MixedSigns"), + std::make_tuple(InType{0.0, -0.0, 1.0, -1.0}, SortedOutput({0.0, -0.0, 1.0, -1.0}), "WithZeros"), + std::make_tuple(InType{-5.0, 0.0, 5.0}, OutType{-5.0, 0.0, 5.0}, "NegZeroPos"), + std::make_tuple(InType{3.14, 2.72, 1.41, 1.62}, OutType{1.41, 1.62, 2.72, 3.14}, "DecimalNumbers"), + std::make_tuple(InType{1e10, 1e5, 1e15, 1e1, 1e8}, OutType{1e1, 1e5, 1e8, 1e10, 1e15}, "LargePositive"), + std::make_tuple(InType{-1e10, -1e5, -1e15, -1e1, -1e8}, OutType{-1e15, -1e10, -1e8, -1e5, -1e1}, "LargeNegative"), + std::make_tuple(InType{1e-10, 1e-5, 1e-15, 1e-1, 1e-8}, OutType{1e-15, 1e-10, 1e-8, 1e-5, 1e-1}, "SmallPositive"), + std::make_tuple(InType{-1e-10, -1e-5, -1e-15, -1e-1, -1e-8}, OutType{-1e-1, -1e-5, -1e-8, -1e-10, -1e-15}, + "SmallNegative"), + std::make_tuple(InType{5.0, 2.0, 5.0, 2.0, 5.0}, OutType{2.0, 2.0, 5.0, 5.0, 5.0}, "Duplicates"), + std::make_tuple(InType{-3.0, -3.0, -3.0}, OutType{-3.0, -3.0, -3.0}, "AllSameNegative"), + std::make_tuple(InType{1.0, 2.0, 3.0, 4.0, 5.0}, OutType{1.0, 2.0, 3.0, 4.0, 5.0}, "AlreadySorted"), + std::make_tuple(InType{5.0, 4.0, 3.0, 2.0, 1.0}, OutType{1.0, 2.0, 3.0, 4.0, 5.0}, "ReverseSorted"), + std::make_tuple(InType{8.0, 4.0, 2.0, 6.0, 1.0, 5.0, 7.0, 3.0}, OutType{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}, + "EightElements"), + std::make_tuple(InType{4.0, 2.0, 3.0, 1.0}, OutType{1.0, 2.0, 3.0, 4.0}, "FourElements"), + std::make_tuple(InType{-100.0, 50.0, -25.0, 75.0, 0.0, -50.0, 25.0, -75.0, 100.0}, + OutType{-100.0, -75.0, -50.0, -25.0, 0.0, 25.0, 50.0, 75.0, 100.0}, "NineElementsMixed"), + std::make_tuple(InType{1.0, -1.0, 2.0, -2.0, 3.0, -3.0}, OutType{-3.0, -2.0, -1.0, 1.0, 2.0, 3.0}, + "AlternatingSigns")}; + +TEST_P(EgashinKRunFuncTestsRadixBatcherSort, Sorting) { + ExecuteTest(GetParam()); +} + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_egashin_k_radix_batcher_sort), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_egashin_k_radix_batcher_sort)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = + EgashinKRunFuncTestsRadixBatcherSort::PrintFuncTestName; + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables,modernize-type-traits) +INSTANTIATE_TEST_SUITE_P(SortingTests, EgashinKRunFuncTestsRadixBatcherSort, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace egashin_k_radix_batcher_sort diff --git a/tasks/egashin_k_radix_batcher_sort/tests/performance/main.cpp b/tasks/egashin_k_radix_batcher_sort/tests/performance/main.cpp new file mode 100644 index 0000000000..92849e39a3 --- /dev/null +++ b/tasks/egashin_k_radix_batcher_sort/tests/performance/main.cpp @@ -0,0 +1,71 @@ +#include +#include + +#include +#include +#include + +#include "egashin_k_radix_batcher_sort/common/include/common.hpp" +#include "egashin_k_radix_batcher_sort/mpi/include/ops_mpi.hpp" +#include "egashin_k_radix_batcher_sort/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" +#include "util/include/util.hpp" + +namespace egashin_k_radix_batcher_sort { + +class EgashinKRunPerfTestRadixBatcherSort : public ppc::util::BaseRunPerfTests { + const int kCount_ = 1000001; + InType input_data_; + + void SetUp() override { + std::mt19937 gen(42); // NOLINT(cert-msc51-cpp) + std::uniform_real_distribution dist(-1e6, 1e6); + + input_data_.resize(kCount_); + for (int i = 0; i < kCount_; ++i) { + input_data_[i] = dist(gen); + } + } + + bool CheckTestOutputData(OutType &output_data) final { + if (ppc::util::IsUnderMpirun()) { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank != 0) { + return true; + } + } + OutType expected_data = input_data_; + std::ranges::sort(expected_data); + if (output_data.size() != expected_data.size()) { + return false; + } + for (std::size_t i = 0; i < output_data.size(); ++i) { + if (output_data[i] != expected_data[i]) { + return false; + } + } + return true; + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(EgashinKRunPerfTestRadixBatcherSort, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_egashin_k_radix_batcher_sort); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = EgashinKRunPerfTestRadixBatcherSort::CustomPerfTestName; + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables,misc-use-anonymous-namespace,modernize-type-traits) +INSTANTIATE_TEST_SUITE_P(RunModeTests, EgashinKRunPerfTestRadixBatcherSort, kGtestValues, kPerfTestName); + +} // namespace egashin_k_radix_batcher_sort