diff --git a/tasks/batushin_i_quick_sort_with_simple_merge/common/include/common.hpp b/tasks/batushin_i_quick_sort_with_simple_merge/common/include/common.hpp new file mode 100644 index 0000000000..7b47fd76aa --- /dev/null +++ b/tasks/batushin_i_quick_sort_with_simple_merge/common/include/common.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace batushin_i_quick_sort_with_simple_merge { + +using InType = std::vector; +using OutType = std::vector; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace batushin_i_quick_sort_with_simple_merge diff --git a/tasks/batushin_i_quick_sort_with_simple_merge/info.json b/tasks/batushin_i_quick_sort_with_simple_merge/info.json new file mode 100644 index 0000000000..b7b8a4cf51 --- /dev/null +++ b/tasks/batushin_i_quick_sort_with_simple_merge/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/batushin_i_quick_sort_with_simple_merge/mpi/include/ops_mpi.hpp b/tasks/batushin_i_quick_sort_with_simple_merge/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..629f69bfde --- /dev/null +++ b/tasks/batushin_i_quick_sort_with_simple_merge/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "batushin_i_quick_sort_with_simple_merge/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace batushin_i_quick_sort_with_simple_merge { + +class BatushinIQuickSortWithSimpleMergeMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit BatushinIQuickSortWithSimpleMergeMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace batushin_i_quick_sort_with_simple_merge diff --git a/tasks/batushin_i_quick_sort_with_simple_merge/mpi/src/ops_mpi.cpp b/tasks/batushin_i_quick_sort_with_simple_merge/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..9396288863 --- /dev/null +++ b/tasks/batushin_i_quick_sort_with_simple_merge/mpi/src/ops_mpi.cpp @@ -0,0 +1,282 @@ +#include "batushin_i_quick_sort_with_simple_merge/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include + +#include "batushin_i_quick_sort_with_simple_merge/common/include/common.hpp" + +namespace batushin_i_quick_sort_with_simple_merge { + +BatushinIQuickSortWithSimpleMergeMPI::BatushinIQuickSortWithSimpleMergeMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = std::vector(); +} + +bool BatushinIQuickSortWithSimpleMergeMPI::ValidationImpl() { + int initialized = 0; + MPI_Initialized(&initialized); + return initialized != 0; +} + +bool BatushinIQuickSortWithSimpleMergeMPI::PreProcessingImpl() { + return true; +} + +namespace { + +void SortSmallArray(std::vector &data, int begin, int end) { + for (int i = begin + 1; i <= end; ++i) { + int key = data[i]; + int j = i - 1; + while (j >= begin && data[j] > key) { + data[j + 1] = data[j]; + --j; + } + data[j + 1] = key; + } +} + +void PartitionArray(std::vector &data, int begin, int end, int &left_end, int &right_begin) { + if (begin >= end) { + left_end = begin - 1; + right_begin = end + 1; + return; + } + + int mid = begin + ((end - begin) / 2); + std::swap(data[mid], data[begin]); + int pivot = data[begin]; + + int i = begin; + int j = end; + while (i <= j) { + while (data[i] < pivot) { + ++i; + } + while (data[j] > pivot) { + --j; + } + if (i <= j) { + std::swap(data[i], data[j]); + ++i; + --j; + } + } + left_end = j; + right_begin = i; +} + +void IterativeQuickSort(std::vector &data) { + if (data.size() <= 1) { + return; + } + + const int threshold = 16; + struct Segment { + int begin, end; + }; + std::stack stk; + stk.push({0, static_cast(data.size() - 1)}); + + while (!stk.empty()) { + Segment seg = stk.top(); + stk.pop(); + if (seg.begin >= seg.end) { + continue; + } + + if (seg.end - seg.begin + 1 <= threshold) { + SortSmallArray(data, seg.begin, seg.end); + continue; + } + + int left_end = 0; + int right_begin = 0; + PartitionArray(data, seg.begin, seg.end, left_end, right_begin); + + if (seg.begin <= left_end) { + stk.push({seg.begin, left_end}); + } + if (right_begin <= seg.end) { + stk.push({right_begin, seg.end}); + } + } +} + +std::pair ComputeLocalRange(int rank, int size, int total) { + int base = total / size; + int extra = total % size; + int start = (rank * base) + std::min(rank, extra); + int end = start + base + (rank < extra ? 1 : 0) - 1; + return std::make_pair(start, end); +} + +void DistributeData(int rank, int size, const std::vector &global_input, std::vector &local_data) { + auto range = ComputeLocalRange(rank, size, static_cast(global_input.size())); + int local_start = range.first; + int local_end = range.second; + int local_count = (local_start <= local_end) ? (local_end - local_start + 1) : 0; + + if (rank == 0) { + if (local_count > 0) { + local_data.resize(local_count); + std::copy(global_input.begin() + local_start, global_input.begin() + local_start + local_count, + local_data.begin()); + } + for (int proc_rank = 1; proc_rank < size; ++proc_rank) { + auto proc_range = ComputeLocalRange(proc_rank, size, static_cast(global_input.size())); + int s = proc_range.first; + int e = proc_range.second; + int cnt = (s <= e) ? (e - s + 1) : 0; + if (cnt > 0) { + std::vector send_buffer(global_input.begin() + s, global_input.begin() + s + cnt); + MPI_Send(send_buffer.data(), cnt, MPI_INT, proc_rank, 0, MPI_COMM_WORLD); + } + } + } else { + if (local_count > 0) { + local_data.resize(local_count); + MPI_Recv(local_data.data(), local_count, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } + } +} + +void BroadcastResult(int rank, std::vector &result, std::vector &output) { + int result_size = 0; + + if (rank == 0) { + result_size = static_cast(result.size()); + output = std::move(result); + } + MPI_Bcast(&result_size, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (result_size > 0) { + if (rank == 0) { + MPI_Bcast(output.data(), result_size, MPI_INT, 0, MPI_COMM_WORLD); + } else { + output.resize(result_size); + MPI_Bcast(output.data(), result_size, MPI_INT, 0, MPI_COMM_WORLD); + } + } else { + if (rank != 0) { + output.clear(); + } + } +} + +std::vector> CollectAllBlocks(int size, const std::vector &local_data) { + std::vector> all_blocks; + all_blocks.reserve(size); + + if (!local_data.empty()) { + all_blocks.emplace_back(local_data.begin(), local_data.end()); + } else { + all_blocks.emplace_back(); + } + + for (int src = 1; src < size; ++src) { + int count = 0; + MPI_Recv(&count, 1, MPI_INT, src, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + all_blocks.emplace_back(); + if (count > 0) { + all_blocks.back().resize(count); + MPI_Recv(all_blocks.back().data(), count, MPI_INT, src, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } + } + + return all_blocks; +} + +std::vector MergeTwoBlocks(const std::vector &a, const std::vector &b) { + std::vector merged; + merged.reserve(a.size() + b.size()); + + auto it1 = a.begin(); + auto it2 = b.begin(); + while (it1 != a.end() && it2 != b.end()) { + if (*it1 <= *it2) { + merged.push_back(*it1++); + } else { + merged.push_back(*it2++); + } + } + while (it1 != a.end()) { + merged.push_back(*it1++); + } + while (it2 != b.end()) { + merged.push_back(*it2++); + } + + return merged; +} + +std::vector MergeSortedBlocks(const std::vector> &all_blocks) { + std::vector result; + for (const auto &block : all_blocks) { + if (block.empty()) { + continue; + } + if (result.empty()) { + result.assign(block.begin(), block.end()); + } else { + result = MergeTwoBlocks(result, block); + } + } + return result; +} + +std::vector GatherAndMerge(int rank, int size, const std::vector &local_data) { + if (rank != 0) { + int count = static_cast(local_data.size()); + MPI_Send(&count, 1, MPI_INT, 0, 0, MPI_COMM_WORLD); + if (count > 0) { + MPI_Send(local_data.data(), count, MPI_INT, 0, 0, MPI_COMM_WORLD); + } + return {}; + } + + auto all_blocks = CollectAllBlocks(size, local_data); + return MergeSortedBlocks(all_blocks); +} + +} // namespace + +bool BatushinIQuickSortWithSimpleMergeMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + const auto &global_input = GetInput(); + int total_size = static_cast(global_input.size()); + + if (total_size == 0) { + GetOutput().clear(); + int dummy = 0; + MPI_Bcast(&dummy, 1, MPI_INT, 0, MPI_COMM_WORLD); + return true; + } + + std::vector local_data; + DistributeData(rank, size, global_input, local_data); + + if (!local_data.empty()) { + IterativeQuickSort(local_data); + } + + std::vector result = GatherAndMerge(rank, size, local_data); + BroadcastResult(rank, result, GetOutput()); + + return true; +} + +bool BatushinIQuickSortWithSimpleMergeMPI::PostProcessingImpl() { + return GetOutput().size() == GetInput().size(); +} + +} // namespace batushin_i_quick_sort_with_simple_merge diff --git a/tasks/batushin_i_quick_sort_with_simple_merge/report.md b/tasks/batushin_i_quick_sort_with_simple_merge/report.md new file mode 100644 index 0000000000..caf2f29220 --- /dev/null +++ b/tasks/batushin_i_quick_sort_with_simple_merge/report.md @@ -0,0 +1,476 @@ +# Быстрая сортировка с простым слиянием + +- Студент: Батушин Илья Александрович, группа 3823Б1ПР2 +- Технологии: SEQ, MPI +- Вариант: 14 + +## 1. Введение + +Сортировка данных является одной из фундаментальных задач, находящей применение в базах данных, алгоритмах поиска, машинном обучении и многих других областях. В условиях роста объёмов обрабатываемых данных параллельные алгоритмы сортировки становятся всё более актуальными. Данная работа посвящена разработке и реализации параллельного алгоритма сортировки, сочетающего эффективную локальную быструю сортировку с последующим централизованным слиянием отсортированных блоков. Реализация выполнена с использованием технологии MPI (Message Passing Interface), обеспечивающей масштабируемость на распределённых системах. + +## 2. Постановка задачи + +Дан одномерный массив целых чисел. Требуется отсортировать массив по возрастанию. + +**Входные данные:** +- `input` - одномерный вектор целых чисел произвольной длины + +**Выходные данные:** +- `output` - отсортированный по возрастанию вектор целых чисел + +**Ограничения:** +- Элементы массива — 32-битные целые числа (`int`) +- Размер массива может быть равен нулю (пустой массив) +- Алгоритм должен корректно обрабатывать массивы с дубликатами, отрицательными числами и уже отсортированные последовательности + +**Пример:** + +Входные данные: `input` = [5, 3, 8, 1, 9, 2, 7, 4, 6, 0] +Выходные данные: `output` = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + +## 3. Описание базового алгоритма (последовательная версия) + +Последовательная реализация основана на итеративной быстрой сортировке с выбором опорного элемента из середины подмассива. + +Алгоритм состоит из следующих шагов: + +1. **Инициализация:** Создание стека для хранения сегментов, требующих сортировки +2. **Обработка сегментов:** Пока стек не пуст: + - Извлечение сегмента из стека + - Пропуск сегментов длины ≤ 1 + - Выбор опорного элемента из середины сегмента и перемещение его в начало + - Разбиение массива по схеме Хоара + - Добавление левой и правой частей в стек +3. **Завершение:** Возврат отсортированного массива + +**Сложность алгоритма** + +**Время:** O(N log N) в среднем, O(N²) в худшем случае + +**Память:** O(log N) для стека (итеративная реализация) + +## 4. Схема распараллеливания + +Параллельная реализация использует гибридный подход: +- Разделение данных между процессами по блочному принципу с учётом остатка +- Локальная сортировка каждого процесса независимо +- Централизованное слияние отсортированных блоков в процессе с rank 0 + +**Распределение данных:** +```cpp +base = total / size // Базовое количество элементов на процесс +extra = total % size // Остаточные элементы для распределения + +// Процессы с rank < extra получают дополнительный элемент +start = (rank * base) + std::min(rank, extra) +count = base + (rank < extra ? 1 : 0) +``` + +**Роли процессов** +- **rank 0:** Координирует выполнение, распределяет данные, собирает и сливает результаты, рассылает финальный ответ +- **rank 1..P-1:** Выполняют локальную сортировку своих блоков и отправляют результаты rank 0 + +**Схема коммуникации** +- **Фаза распределения:** Rank 0 отправляет данные другим процессам +- **Фаза вычислений:** Все процессы независимо сортируют свои блоки +- **Фаза сбора:** Все процессы отправляют отсортированные блоки rank 0 +- **Фаза слияния:** Rank 0 последовательно сливает все блоки в один отсортированный массив +- **Фаза синхронизации:** Rank 0 рассылает финальный результат всем процессам + +## 5. Детали реализации + +**Файлы:** +- `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` - тесты производительности + +**Ключевые классы:** +- `BatushinIQuickSortWithSimpleMergeSEQ` - последовательная реализация +- `BatushinIQuickSortWithSimpleMergeMPI` - параллельная MPI реализация + +**Основные методы:** +- `ValidationImpl()` - проверка входных данных +- `PreProcessingImpl()` - подготовительные вычисления +- `RunImpl()` - основной алгоритм +- `PostProcessingImpl()` - завершающая обработка + +**Вспомогательные функции:** +- `ComputeLocalRange()` - вычисление диапазона элементов для процесса +- `DistributeData()` - распределение исходных данных между процессами +- `IterativeQuickSort()` - итеративная быстрая сортировка с оптимизацией для малых подмассивов +- `SortSmallArray()` - сортировка вставками для подмассивов размером ≤ 16 +- `PartitionArray()` - разбиение массива по схеме Хоара с выбором медианы из трёх +- `CollectAllBlocks()` - централизованный сбор отсортированных блоков от всех процессов в rank 0 +- `MergeSortedBlocks()` - последовательное слияние отсортированных блоков в единый массив +- `BroadcastResult()` - рассылка финального результата всем процессам + +**Допущения:** +- Все процессы имеют доступ ко всему исходному массиву (в MPI версии) +- Размер массива может быть произвольным (включая 0) +- Элементы массива — 32-битные целые числа + +**Обрабатываемые граничные случаи:** +- Пустой массив +- Массив из одного элемента +- Массивы с дубликатами +- Массивы с отрицательными числами +- Уже отсортированные массивы +- Обратно отсортированные массивы +- Неравномерное распределение элементов между процессами + +## 6. Экспериментальное окружение + +### 6.1 Аппаратное обеспечение/ОС: + +- **Процессор:** Intel Core i5-1135G7 +- **Ядра:** 4 физических ядра (8 логических потоков) +- **ОЗУ:** 8 ГБ DDR4 +- **ОС:** WSL Ubuntu 24.04.3 LTS (Linux kernel 5.15) + +### 6.2 Программный инструментарий + +- **Компилятор:** g++ 13.3.0 +- **Тип сборки:** Release +- **Стандарт C++:** C++20 +- **MPI:** OpenMPI 4.1.6 + +### 6.3 Тестовое окружение + +```bash +PPC_NUM_PROC=1,2,3,4,5,6,7,8 +``` +**Размер тестового массива:** 5 000 000 элементов + +## 7. Результаты + +### 7.1. Корректность работы + +Все функциональные тесты пройдены успешно: +- Пустой массив +- Массивы различных размеров (от 1 до 10+ элементов) +- Случаи с одним элементом +- Массивы с отрицательными числами +- Массивы с дубликатами +- Уже отсортированные и обратно отсортированные массивы +- Неравномерное распределение элементов между процессами +- SEQ и MPI версии выдают идентичные результаты для всех тестовых случаев + +### 7.2. Производительность + +**Время выполнения (секунды) для матрицы 5000×5000:** + +| Версия | Количество процессов | Task Run время | +|--------|---------------------|----------------| +| SEQ | 1 | 0.2066 | +| MPI | 1 | 0.3681 | +| MPI | 2 | 0.2113 | +| MPI | 3 | 0.1664 | +| MPI | 4 | 0.1535 | +| MPI | 5 | 0.3833 | +| MPI | 6 | 0.3695 | +| MPI | 7 | 0.3223 | +| MPI | 8 | 0.3501 | + +**Ускорение относительно SEQ версии:** + +| Количество процессов | Ускорение | Эффективность | +|---------------------|-----------|---------------| +| 1 | 0.56× | 56% | +| 2 | 0.98× | 49% | +| 3 | 1.24× | 41% | +| 4 | 1.35× | 34% | +| 5 | 0.54× | 11% | +| 6 | 0.56× | 9% | +| 7 | 0.64× | 9% | +| 8 | 0.59× | 7% | + + +**Формула ускорения:** Ускорение = Время SEQ / Время MPI + +**Формула эффективности:** Эффективность = (Ускорение / Количество процессов) × 100% + +### 7.3. Анализ эффективности + +- **Лучшее ускорение:** 1.35× достигнуто при использовании 4 процессов +- **Оптимальная конфигурация:** 3-4 процесса +- **Эффективность MPI:** снижается при увеличении числа процессов сверх 4 из-за oversubscribe + +### 7.4. Наблюдения + +1. **MPI с 1 процессом** медленнее SEQ из-за накладных расходов на коммуникацию +2. **MPI с 2 процессами** почти достигает производительности SEQ (0.98×) +3. **MPI с 3-4 процессами** демонстрирует реальное ускорение (1.24–1.35×) +4. **MPI с 5+ процессами** показывает снижение производительности из-за конкуренции за ресурсы на 4-ядерной системе + +## 8. Выводы + +### 8.1. Достигнутые результаты + +- **Корректность:** Разработанный алгоритм успешно прошёл все функциональные тесты, включая граничные случаи +- **Эффективность параллелизации:** Достигнуто ускорение до 1.35× на 4 процессах для массива из 5 млн элементов +- **Оптимальная конфигурация:** Наилучшие результаты получены при использовании 3-4 процессов, что соответствует числу физических ядер процессора + +### 8.2. Ограничения и проблемы + +- **Накладные расходы MPI:** ППри малом числе процессов (1-2) накладные расходы превышают выгоду от параллелизации +- **Ограничения аппаратуры:** Максимальное ускорение ограничено 4 физическими ядрами процессора +- **Oversubscribe:** При использовании более 4 процессов наблюдается деградация производительности из-за конкуренции за ресурсы +- **Размер данных:** Для матриц меньшего размера накладные расходы MPI могут превышать выгоду от параллелизации + +## 9. Источники +1. Лекции по параллельному программированию Сысоева А. В +2. Материалы курса: https://github.com/learning-process/ppc-2025-processes-engineers + +## 10. Приложение + +```cpp +namespace { + +void SortSmallArray(std::vector &data, int begin, int end) { + for (int i = begin + 1; i <= end; ++i) { + int key = data[i]; + int j = i - 1; + while (j >= begin && data[j] > key) { + data[j + 1] = data[j]; + --j; + } + data[j + 1] = key; + } +} + +void PartitionArray(std::vector &data, int begin, int end, int &left_end, int &right_begin) { + if (begin >= end) { + left_end = begin - 1; + right_begin = end + 1; + return; + } + + int mid = begin + ((end - begin) / 2); + std::swap(data[mid], data[begin]); + int pivot = data[begin]; + + int i = begin; + int j = end; + while (i <= j) { + while (data[i] < pivot) { + ++i; + } + while (data[j] > pivot) { + --j; + } + if (i <= j) { + std::swap(data[i], data[j]); + ++i; + --j; + } + } + left_end = j; + right_begin = i; +} + +void IterativeQuickSort(std::vector &data) { + if (data.size() <= 1) { + return; + } + + const int threshold = 16; + struct Segment { + int begin, end; + }; + std::stack stk; + stk.push({0, static_cast(data.size() - 1)}); + + while (!stk.empty()) { + Segment seg = stk.top(); + stk.pop(); + if (seg.begin >= seg.end) { + continue; + } + + if (seg.end - seg.begin + 1 <= threshold) { + SortSmallArray(data, seg.begin, seg.end); + continue; + } + + int left_end = 0; + int right_begin = 0; + PartitionArray(data, seg.begin, seg.end, left_end, right_begin); + + if (seg.begin <= left_end) { + stk.push({seg.begin, left_end}); + } + if (right_begin <= seg.end) { + stk.push({right_begin, seg.end}); + } + } +} + +std::pair ComputeLocalRange(int rank, int size, int total) { + int base = total / size; + int extra = total % size; + int start = (rank * base) + std::min(rank, extra); + int end = start + base + (rank < extra ? 1 : 0) - 1; + return std::make_pair(start, end); +} + +void DistributeData(int rank, int size, const std::vector &global_input, std::vector &local_data) { + auto range = ComputeLocalRange(rank, size, static_cast(global_input.size())); + int local_start = range.first; + int local_end = range.second; + int local_count = (local_start <= local_end) ? (local_end - local_start + 1) : 0; + + if (rank == 0) { + if (local_count > 0) { + local_data.resize(local_count); + std::copy(global_input.begin() + local_start, global_input.begin() + local_start + local_count, + local_data.begin()); + } + for (int proc_rank = 1; proc_rank < size; ++proc_rank) { + auto proc_range = ComputeLocalRange(proc_rank, size, static_cast(global_input.size())); + int s = proc_range.first; + int e = proc_range.second; + int cnt = (s <= e) ? (e - s + 1) : 0; + if (cnt > 0) { + std::vector send_buffer(global_input.begin() + s, global_input.begin() + s + cnt); + MPI_Send(send_buffer.data(), cnt, MPI_INT, proc_rank, 0, MPI_COMM_WORLD); + } + } + } else { + if (local_count > 0) { + local_data.resize(local_count); + MPI_Recv(local_data.data(), local_count, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } + } +} + +void BroadcastResult(int rank, std::vector &result, std::vector &output) { + int result_size = 0; + + if (rank == 0) { + result_size = static_cast(result.size()); + output = std::move(result); + } + MPI_Bcast(&result_size, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (result_size > 0) { + if (rank == 0) { + MPI_Bcast(output.data(), result_size, MPI_INT, 0, MPI_COMM_WORLD); + } else { + output.resize(result_size); + MPI_Bcast(output.data(), result_size, MPI_INT, 0, MPI_COMM_WORLD); + } + } else { + if (rank != 0) { + output.clear(); + } + } +} + +std::vector> CollectAllBlocks(int size, const std::vector &local_data) { + std::vector> all_blocks; + all_blocks.reserve(size); + + if (!local_data.empty()) { + all_blocks.emplace_back(local_data.begin(), local_data.end()); + } else { + all_blocks.emplace_back(); + } + + for (int src = 1; src < size; ++src) { + int count = 0; + MPI_Recv(&count, 1, MPI_INT, src, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + all_blocks.emplace_back(); + if (count > 0) { + all_blocks.back().resize(count); + MPI_Recv(all_blocks.back().data(), count, MPI_INT, src, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } + } + + return all_blocks; +} + +std::vector MergeTwoBlocks(const std::vector &a, const std::vector &b) { + std::vector merged; + merged.reserve(a.size() + b.size()); + + auto it1 = a.begin(); + auto it2 = b.begin(); + while (it1 != a.end() && it2 != b.end()) { + if (*it1 <= *it2) { + merged.push_back(*it1++); + } else { + merged.push_back(*it2++); + } + } + while (it1 != a.end()) { + merged.push_back(*it1++); + } + while (it2 != b.end()) { + merged.push_back(*it2++); + } + + return merged; +} + +std::vector MergeSortedBlocks(const std::vector> &all_blocks) { + std::vector result; + for (const auto &block : all_blocks) { + if (block.empty()) { + continue; + } + if (result.empty()) { + result.assign(block.begin(), block.end()); + } else { + result = MergeTwoBlocks(result, block); + } + } + return result; +} + +std::vector GatherAndMerge(int rank, int size, const std::vector &local_data) { + if (rank != 0) { + int count = static_cast(local_data.size()); + MPI_Send(&count, 1, MPI_INT, 0, 0, MPI_COMM_WORLD); + if (count > 0) { + MPI_Send(local_data.data(), count, MPI_INT, 0, 0, MPI_COMM_WORLD); + } + return {}; + } + + auto all_blocks = CollectAllBlocks(size, local_data); + return MergeSortedBlocks(all_blocks); +} + +} // namespace + +bool BatushinIQuickSortWithSimpleMergeMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + const auto &global_input = GetInput(); + int total_size = static_cast(global_input.size()); + + if (total_size == 0) { + GetOutput().clear(); + int dummy = 0; + MPI_Bcast(&dummy, 1, MPI_INT, 0, MPI_COMM_WORLD); + return true; + } + + std::vector local_data; + DistributeData(rank, size, global_input, local_data); + + if (!local_data.empty()) { + IterativeQuickSort(local_data); + } + + std::vector result = GatherAndMerge(rank, size, local_data); + BroadcastResult(rank, result, GetOutput()); + + return true; +} +``` \ No newline at end of file diff --git a/tasks/batushin_i_quick_sort_with_simple_merge/seq/include/ops_seq.hpp b/tasks/batushin_i_quick_sort_with_simple_merge/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..9c2cd3826e --- /dev/null +++ b/tasks/batushin_i_quick_sort_with_simple_merge/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "batushin_i_quick_sort_with_simple_merge/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace batushin_i_quick_sort_with_simple_merge { + +class BatushinIQuickSortWithSimpleMergeSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit BatushinIQuickSortWithSimpleMergeSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace batushin_i_quick_sort_with_simple_merge diff --git a/tasks/batushin_i_quick_sort_with_simple_merge/seq/src/ops_seq.cpp b/tasks/batushin_i_quick_sort_with_simple_merge/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..6a7f6ba805 --- /dev/null +++ b/tasks/batushin_i_quick_sort_with_simple_merge/seq/src/ops_seq.cpp @@ -0,0 +1,83 @@ +#include "batushin_i_quick_sort_with_simple_merge/seq/include/ops_seq.hpp" + +#include +#include +#include + +#include "batushin_i_quick_sort_with_simple_merge/common/include/common.hpp" + +namespace batushin_i_quick_sort_with_simple_merge { + +BatushinIQuickSortWithSimpleMergeSEQ::BatushinIQuickSortWithSimpleMergeSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput().resize(in.size()); +} + +bool BatushinIQuickSortWithSimpleMergeSEQ::ValidationImpl() { + return true; +} + +bool BatushinIQuickSortWithSimpleMergeSEQ::PreProcessingImpl() { + GetOutput() = GetInput(); + return true; +} + +namespace { + +void IterativeQuickSort(std::vector &data) { + if (data.size() <= 1) { + return; + } + + struct Segment { + int begin; + int end; + }; + std::stack stk; + stk.push({0, static_cast(data.size() - 1)}); + while (!stk.empty()) { + Segment seg = stk.top(); + stk.pop(); + if (seg.begin >= seg.end) { + continue; + } + + int mid = seg.begin + ((seg.end - seg.begin) / 2); + std::swap(data[mid], data[seg.begin]); + int pivot = data[seg.begin]; + + int i = seg.begin - 1; + int j = seg.end + 1; + while (true) { + ++i; + while (data[i] < pivot) { + ++i; + } + --j; + while (data[j] > pivot) { + --j; + } + if (i >= j) { + break; + } + std::swap(data[i], data[j]); + } + + stk.push({seg.begin, j}); + stk.push({j + 1, seg.end}); + } +} + +} // namespace + +bool BatushinIQuickSortWithSimpleMergeSEQ::RunImpl() { + IterativeQuickSort(GetOutput()); + return true; +} + +bool BatushinIQuickSortWithSimpleMergeSEQ::PostProcessingImpl() { + return GetOutput().size() == GetInput().size(); +} + +} // namespace batushin_i_quick_sort_with_simple_merge diff --git a/tasks/batushin_i_quick_sort_with_simple_merge/settings.json b/tasks/batushin_i_quick_sort_with_simple_merge/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/batushin_i_quick_sort_with_simple_merge/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/batushin_i_quick_sort_with_simple_merge/tests/.clang-tidy b/tasks/batushin_i_quick_sort_with_simple_merge/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/batushin_i_quick_sort_with_simple_merge/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/batushin_i_quick_sort_with_simple_merge/tests/functional/main.cpp b/tasks/batushin_i_quick_sort_with_simple_merge/tests/functional/main.cpp new file mode 100644 index 0000000000..e956c96829 --- /dev/null +++ b/tasks/batushin_i_quick_sort_with_simple_merge/tests/functional/main.cpp @@ -0,0 +1,100 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "batushin_i_quick_sort_with_simple_merge/common/include/common.hpp" +#include "batushin_i_quick_sort_with_simple_merge/mpi/include/ops_mpi.hpp" +#include "batushin_i_quick_sort_with_simple_merge/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace batushin_i_quick_sort_with_simple_merge { + +class BatushinIQuickSortWithSimpleMergeFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::get<0>(test_param); + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + input_data_ = std::get<1>(params); + expected_result_ = std::get<2>(params); + } + + bool CheckTestOutputData(OutType &output_data) final { + return expected_result_ == output_data; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; + OutType expected_result_; +}; + +namespace { + +InType CreateInput(std::vector data) { + return data; +} + +std::vector SortedCopy(const std::vector &v) { + auto res = v; + std::ranges::sort(res, std::less<>()); + return res; +} + +TEST_P(BatushinIQuickSortWithSimpleMergeFuncTests, MatmulFromPic) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + std::make_tuple("empty", CreateInput({}), std::vector({})), + std::make_tuple("single", CreateInput({42}), std::vector({42})), + std::make_tuple("two_desc", CreateInput({5, 3}), std::vector({3, 5})), + std::make_tuple("reverse_5", CreateInput({5, 4, 3, 2, 1}), SortedCopy({5, 4, 3, 2, 1})), + std::make_tuple("random_7", CreateInput({3, 1, 4, 1, 5, 9, 2}), SortedCopy({3, 1, 4, 1, 5, 9, 2})), + std::make_tuple("all_same", CreateInput({7, 7, 7, 7}), std::vector({7, 7, 7, 7})), + std::make_tuple("with_negatives", CreateInput({-1, -3, 2, 0, -5}), SortedCopy({-1, -3, 2, 0, -5})), + std::make_tuple("large_asc", CreateInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), + std::vector({1, 2, 3, 4, 5, 6, 7, 8, 9, 10})), + std::make_tuple("large_desc", CreateInput({10, 9, 8, 7, 6, 5, 4, 3, 2, 1}), + SortedCopy({10, 9, 8, 7, 6, 5, 4, 3, 2, 1})), + std::make_tuple("duplicates_mixed", CreateInput({2, 1, 2, 3, 1, 3}), SortedCopy({2, 1, 2, 3, 1, 3})), + std::make_tuple("zigzag_6", CreateInput({1, 5, 2, 4, 3, 6}), SortedCopy({1, 5, 2, 4, 3, 6})), + std::make_tuple("large_sorted_20", + CreateInput({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}), + std::vector({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19})), + std::make_tuple("large_reverse_17", CreateInput({16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}), + std::vector({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})), + std::make_tuple("edge_case_16", CreateInput({15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}), + std::vector({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})), + std::make_tuple("already_sorted", CreateInput({-5, -2, 0, 3, 7}), std::vector({-5, -2, 0, 3, 7}))}; + +const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_batushin_i_quick_sort_with_simple_merge), + ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_batushin_i_quick_sort_with_simple_merge)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = + BatushinIQuickSortWithSimpleMergeFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(QuickSortWithSimpleMergeTests, BatushinIQuickSortWithSimpleMergeFuncTests, kGtestValues, + kPerfTestName); + +} // namespace + +} // namespace batushin_i_quick_sort_with_simple_merge diff --git a/tasks/batushin_i_quick_sort_with_simple_merge/tests/performance/main.cpp b/tasks/batushin_i_quick_sort_with_simple_merge/tests/performance/main.cpp new file mode 100644 index 0000000000..f036f81f26 --- /dev/null +++ b/tasks/batushin_i_quick_sort_with_simple_merge/tests/performance/main.cpp @@ -0,0 +1,59 @@ +#include + +#include +#include +#include +#include + +#include "batushin_i_quick_sort_with_simple_merge/common/include/common.hpp" +#include "batushin_i_quick_sort_with_simple_merge/mpi/include/ops_mpi.hpp" +#include "batushin_i_quick_sort_with_simple_merge/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace batushin_i_quick_sort_with_simple_merge { + +class BatushinIQuickSortWithSimpleMergePerfTests : public ppc::util::BaseRunPerfTests { + private: + InType input_data_; + OutType expected_result_; + + public: + void SetUp() override { + const size_t n = 5000000; + std::vector data(n); + + unsigned int seed = 123456789; + for (size_t i = 0; i < n; ++i) { + seed = (seed * 1103515245U) + 12345U; + data[i] = static_cast((seed / 65536U) % 2000001U) - 1000000; + } + + input_data_ = data; + expected_result_ = data; + std::ranges::sort(expected_result_, std::less<>()); + } + + bool CheckTestOutputData(OutType &output_data) final { + return expected_result_ == output_data; + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(BatushinIQuickSortWithSimpleMergePerfTests, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_batushin_i_quick_sort_with_simple_merge); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = BatushinIQuickSortWithSimpleMergePerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, BatushinIQuickSortWithSimpleMergePerfTests, kGtestValues, kPerfTestName); + +} // namespace batushin_i_quick_sort_with_simple_merge