diff --git a/tasks/shvetsova_k_rad_sort_batch_merge/common/include/common.hpp b/tasks/shvetsova_k_rad_sort_batch_merge/common/include/common.hpp new file mode 100644 index 0000000000..12edf2ede9 --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_merge/common/include/common.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" +namespace shvetsova_k_rad_sort_batch_merge { +using InType = std::vector; +using OutType = std::vector; +using TestType = std::string; +using BaseTask = ppc::task::Task; +} // namespace shvetsova_k_rad_sort_batch_merge diff --git a/tasks/shvetsova_k_rad_sort_batch_merge/data/test1.txt b/tasks/shvetsova_k_rad_sort_batch_merge/data/test1.txt new file mode 100644 index 0000000000..4808be5018 --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_merge/data/test1.txt @@ -0,0 +1,3 @@ +8 +5 3 7 1 4 9 2 6 +1 2 3 4 5 6 7 9 diff --git a/tasks/shvetsova_k_rad_sort_batch_merge/data/test2.txt b/tasks/shvetsova_k_rad_sort_batch_merge/data/test2.txt new file mode 100644 index 0000000000..43d95fb397 --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_merge/data/test2.txt @@ -0,0 +1,3 @@ +8 +1 2 3 4 5 6 7 8 +1 2 3 4 5 6 7 8 diff --git a/tasks/shvetsova_k_rad_sort_batch_merge/data/test3.txt b/tasks/shvetsova_k_rad_sort_batch_merge/data/test3.txt new file mode 100644 index 0000000000..2715d9b3b5 --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_merge/data/test3.txt @@ -0,0 +1,3 @@ +8 +8 7 6 5 4 3 2 1 +1 2 3 4 5 6 7 8 diff --git a/tasks/shvetsova_k_rad_sort_batch_merge/data/test4.txt b/tasks/shvetsova_k_rad_sort_batch_merge/data/test4.txt new file mode 100644 index 0000000000..6ee76a9995 --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_merge/data/test4.txt @@ -0,0 +1,3 @@ +8 +5 5 5 5 5 5 5 5 +5 5 5 5 5 5 5 5 diff --git a/tasks/shvetsova_k_rad_sort_batch_merge/data/test5 b/tasks/shvetsova_k_rad_sort_batch_merge/data/test5 new file mode 100644 index 0000000000..448a737552 --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_merge/data/test5 @@ -0,0 +1,2 @@ +12 +-1 3 -10 2 4 5 6 7 90 11 91 -900 \ No newline at end of file diff --git a/tasks/shvetsova_k_rad_sort_batch_merge/info.json b/tasks/shvetsova_k_rad_sort_batch_merge/info.json new file mode 100644 index 0000000000..0276a451f5 --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_merge/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Швецова", + "last_name": "Ксения", + "middle_name": "Алексеевна", + "group_number": "3823Б1ФИ1", + "task_number": "3" + } +} diff --git a/tasks/shvetsova_k_rad_sort_batch_merge/mpi/include/ops_mpi.hpp b/tasks/shvetsova_k_rad_sort_batch_merge/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..068ca780ed --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_merge/mpi/include/ops_mpi.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include +#include +#include + +#include "shvetsova_k_rad_sort_batch_merge/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace shvetsova_k_rad_sort_batch_merge { + +class ShvetsovaKRadSortBatchMergeMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + + explicit ShvetsovaKRadSortBatchMergeMPI(const InType &in); + + private: + std::vector data_; + + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + // Распределение данных + static void CreateDistribution(int proc_count, int size, std::vector &counts, std::vector &displs); + + // MPI операции + static void ScatterData(const std::vector &data, std::vector &local, const std::vector &counts, + const std::vector &displs, int rank); + + static void GatherAndBroadcast(std::vector &data, const std::vector &local, const std::vector &counts, + const std::vector &displs, int rank); + + // Поразрядная сортировка + static void RadixSortWithNegatives(std::vector &arr); + static void RadixSort(std::vector &arr); + static void CountingSort(std::vector &arr, int digit_place); + static int GetDigit(int num, int digit_place); + + // Вспомогательные функции для поразрядной сортировки + static void CountDigits(const std::vector &arr, int digit_place, std::vector &count); + static void AccumulateCounts(std::vector &count); + static void BuildOutputArray(const std::vector &arr, int digit_place, std::vector &count, + std::vector &output); + static void CopyBackToArray(std::vector &arr, const std::vector &output); + + // Функции для обработки отрицательных чисел + static std::vector ExtractNegatives(const std::vector &arr); + static std::vector ExtractNonNegatives(const std::vector &arr); + static void SortSeparatedArrays(std::vector &negatives, std::vector &non_negatives); + static void CombineSortedArrays(std::vector &arr, std::vector &negatives, std::vector &non_negatives); + static void ReverseAndRestoreSign(std::vector &negatives); + + // Сеть Бэтчера + static std::vector> GenerateBatcherComparators(int num_processes); + static void AddComparator(std::vector> &comparators, int a, int b); + static std::vector MergeTwoSorted(const std::vector &a, const std::vector &b); + + // Вспомогательные функции для сети Бэтчера + static void ProcessPowerStep(int power, int num_processes, std::vector> &comparators); + static void ProcessKStep(int power, int k, int num_processes, std::vector> &comparators); + static void ProcessJStep(int power, int k, int j, int num_processes, std::vector> &comparators); + + // Вспомогательные функции для обработки компараторов + static void ProcessBatcherComparators(const std::vector> &comparators, int rank, + const std::vector &counts, std::vector &local); + static void ProcessComparator(const std::pair &comparator, int rank, const std::vector &counts, + std::vector &local); + static void MergeAndDistribute(std::vector &local, const std::vector &received, int rank, int a, int b); + + // Вспомогательная функция для слияния + static void AddRemainingElements(std::vector &result, const std::vector &source, size_t &index); +}; + +} // namespace shvetsova_k_rad_sort_batch_merge diff --git a/tasks/shvetsova_k_rad_sort_batch_merge/mpi/src/ops_mpi.cpp b/tasks/shvetsova_k_rad_sort_batch_merge/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..7f601d9a66 --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_merge/mpi/src/ops_mpi.cpp @@ -0,0 +1,407 @@ +#include "shvetsova_k_rad_sort_batch_merge/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "shvetsova_k_rad_sort_batch_merge/common/include/common.hpp" + +namespace shvetsova_k_rad_sort_batch_merge { + +ShvetsovaKRadSortBatchMergeMPI::ShvetsovaKRadSortBatchMergeMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = OutType{}; +} + +bool ShvetsovaKRadSortBatchMergeMPI::ValidationImpl() { + return true; +} + +bool ShvetsovaKRadSortBatchMergeMPI::PreProcessingImpl() { + data_ = GetInput(); + return true; +} + +bool ShvetsovaKRadSortBatchMergeMPI::RunImpl() { + int proc_count = 0; + int rank = 0; + + MPI_Comm_size(MPI_COMM_WORLD, &proc_count); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + int size = (rank == 0) ? static_cast(data_.size()) : 0; + MPI_Bcast(&size, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (size == 0) { + if (rank == 0) { + GetOutput() = OutType{}; + } + return true; + } + + // Выравнивание размера для равномерного распределения + int padded_size = size; + if (size % proc_count != 0) { + padded_size = size + (proc_count - size % proc_count); + } + + // Добавление максимальных значений для выравнивания + if (rank == 0) { + data_.resize(padded_size, INT_MAX); + } + + // Создание компараторов Бэтчера + std::vector> comparators; + if (rank == 0) { + comparators = GenerateBatcherComparators(proc_count); + } + + // Рассылка компараторов всем процессам + size_t comparators_count = 0; + if (rank == 0) { + comparators_count = comparators.size(); + } + + MPI_Bcast(&comparators_count, 1, MPI_UNSIGNED_LONG, 0, MPI_COMM_WORLD); + + if (rank != 0) { + comparators.resize(comparators_count); + } + + // Отправляем компараторы как плоский массив пар + std::vector flat_comparators(comparators_count * 2); + if (rank == 0) { + for (size_t i = 0; i < comparators_count; i++) { + flat_comparators[2 * i] = comparators[i].first; + flat_comparators[(2 * i) + 1] = comparators[i].second; + } + } + + MPI_Bcast(flat_comparators.data(), static_cast(comparators_count * 2), MPI_INT, 0, MPI_COMM_WORLD); + + // Восстанавливаем компараторы из плоского массива + for (size_t i = 0; i < comparators_count; i++) { + comparators[i] = {flat_comparators[2 * i], flat_comparators[(2 * i) + 1]}; + } + + // Распределение данных + std::vector counts(proc_count); + std::vector displs(proc_count); + CreateDistribution(proc_count, padded_size, counts, displs); + + std::vector local(counts.at(rank)); + ScatterData(data_, local, counts, displs, rank); + + // Локальная сортировка + RadixSortWithNegatives(local); + + // Слияние с использованием сети Бэтчера + ProcessBatcherComparators(comparators, rank, counts, local); + + // Сбор и рассылка результатов + data_.resize(static_cast(padded_size)); + GatherAndBroadcast(data_, local, counts, displs, rank); + + // Удаление добавленных значений и сохранение результата + if (rank == 0) { + data_.resize(static_cast(size)); + } + + GetOutput() = data_; + return true; +} + +// ---------------- Distribution ---------------- + +void ShvetsovaKRadSortBatchMergeMPI::CreateDistribution(int proc_count, int size, std::vector &counts, + std::vector &displs) { + int base = size / proc_count; + int rem = size % proc_count; + int offset = 0; + + for (int i = 0; i < proc_count; ++i) { + counts.at(i) = base + (i < rem ? 1 : 0); + displs.at(i) = offset; + offset += counts.at(i); + } +} + +// ---------------- MPI helpers ---------------- + +void ShvetsovaKRadSortBatchMergeMPI::ScatterData(const std::vector &data, std::vector &local, + const std::vector &counts, const std::vector &displs, + int rank) { + MPI_Scatterv(rank == 0 ? data.data() : nullptr, counts.data(), displs.data(), MPI_INT, local.data(), + static_cast(local.size()), MPI_INT, 0, MPI_COMM_WORLD); +} + +void ShvetsovaKRadSortBatchMergeMPI::GatherAndBroadcast(std::vector &data, const std::vector &local, + const std::vector &counts, const std::vector &displs, + int rank) { + MPI_Gatherv(local.data(), static_cast(local.size()), MPI_INT, rank == 0 ? data.data() : nullptr, counts.data(), + displs.data(), MPI_INT, 0, MPI_COMM_WORLD); + + MPI_Bcast(data.data(), static_cast(data.size()), MPI_INT, 0, MPI_COMM_WORLD); +} + +// ---------------- Batcher network ---------------- + +void ShvetsovaKRadSortBatchMergeMPI::AddComparator(std::vector> &comparators, int a, int b) { + if (a < b) { + comparators.emplace_back(a, b); + } else if (b < a) { + comparators.emplace_back(b, a); + } +} + +void ShvetsovaKRadSortBatchMergeMPI::ProcessJStep(int power, int k, int j, int num_processes, + std::vector> &comparators) { + int limit = std::min(k, num_processes - j - k); + for (int i = 0; i < limit; i++) { + if ((j + i) / (power * 2) == (j + i + k) / (power * 2)) { + const auto first_index = j + i; + const auto second_index = first_index + k; + AddComparator(comparators, first_index, second_index); + } + } +} + +void ShvetsovaKRadSortBatchMergeMPI::ProcessKStep(int power, int k, int num_processes, + std::vector> &comparators) { + for (int j = k % power; j < num_processes - k; j += 2 * k) { + ProcessJStep(power, k, j, num_processes, comparators); + } +} + +void ShvetsovaKRadSortBatchMergeMPI::ProcessPowerStep(int power, int num_processes, + std::vector> &comparators) { + for (int k = power; k >= 1; k /= 2) { + ProcessKStep(power, k, num_processes, comparators); + } +} + +std::vector> ShvetsovaKRadSortBatchMergeMPI::GenerateBatcherComparators(int num_processes) { + std::vector> comparators; + for (int power = 1; power < num_processes; power *= 2) { + ProcessPowerStep(power, num_processes, comparators); + } + return comparators; +} + +std::vector ShvetsovaKRadSortBatchMergeMPI::MergeTwoSorted(const std::vector &a, const std::vector &b) { + std::vector result; + result.reserve(a.size() + b.size()); + + size_t i = 0; + size_t j = 0; + + while (i < a.size() && j < b.size()) { + if (a[i] <= b[j]) { + result.push_back(a[i]); + i++; + } else { + result.push_back(b[j]); + j++; + } + } + + // Добавляем оставшиеся элементы + AddRemainingElements(result, a, i); + AddRemainingElements(result, b, j); + + return result; +} + +// Вспомогательная функция для добавления оставшихся элементов +void ShvetsovaKRadSortBatchMergeMPI::AddRemainingElements(std::vector &result, const std::vector &source, + size_t &index) { + while (index < source.size()) { + result.push_back(source[index]); + index++; + } +} + +// Обработка компараторов Бэтчера +void ShvetsovaKRadSortBatchMergeMPI::ProcessBatcherComparators(const std::vector> &comparators, + int rank, const std::vector &counts, + std::vector &local) { + for (const auto &comparator : comparators) { + int a = comparator.first; + int b = comparator.second; + + if (rank == a || rank == b) { + ProcessComparator(comparator, rank, counts, local); + } + MPI_Barrier(MPI_COMM_WORLD); + } +} + +// Обработка одного компаратора +void ShvetsovaKRadSortBatchMergeMPI::ProcessComparator(const std::pair &comparator, int rank, + const std::vector &counts, std::vector &local) { + int a = comparator.first; + int b = comparator.second; + int partner = (rank == a) ? b : a; + int partner_size = counts.at(partner); + + // Обмен данными с партнером + std::vector received_data(partner_size); + MPI_Sendrecv(local.data(), static_cast(local.size()), MPI_INT, partner, 0, received_data.data(), partner_size, + MPI_INT, partner, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + // Слияние и распределение элементов + MergeAndDistribute(local, received_data, rank, a, b); +} + +// Слияние и распределение элементов +void ShvetsovaKRadSortBatchMergeMPI::MergeAndDistribute(std::vector &local, const std::vector &received, + int rank, int a, int b) { + std::vector merged = MergeTwoSorted(local, received); + + if (rank == std::min(a, b)) { + // Оставляем меньшие элементы + std::copy(merged.begin(), merged.begin() + static_cast(local.size()), local.begin()); + } else { + // Оставляем большие элементы + std::copy(merged.begin() + static_cast(local.size()), merged.end(), local.begin()); + } +} + +// ---------------- Radix sort helpers ---------------- + +int ShvetsovaKRadSortBatchMergeMPI::GetDigit(int num, int digit_place) { + return (num / digit_place) % 10; +} + +void ShvetsovaKRadSortBatchMergeMPI::CountDigits(const std::vector &arr, int digit_place, + std::vector &count) { + for (int value : arr) { + int digit = (value / digit_place) % 10; + count[digit]++; + } +} + +void ShvetsovaKRadSortBatchMergeMPI::AccumulateCounts(std::vector &count) { + for (int i = 1; std::cmp_less(i, count.size()); i++) { + count[i] += count[i - 1]; + } +} + +void ShvetsovaKRadSortBatchMergeMPI::BuildOutputArray(const std::vector &arr, int digit_place, + std::vector &count, std::vector &output) { + for (int i = static_cast(arr.size()) - 1; i >= 0; i--) { + int digit = (arr[i] / digit_place) % 10; + output[count[digit] - 1] = arr[i]; + count[digit]--; + } +} + +void ShvetsovaKRadSortBatchMergeMPI::CopyBackToArray(std::vector &arr, const std::vector &output) { + for (size_t i = 0; i < arr.size(); i++) { + arr[i] = output[i]; + } +} + +void ShvetsovaKRadSortBatchMergeMPI::CountingSort(std::vector &arr, int digit_place) { + int n = static_cast(arr.size()); + const int range = 10; + std::vector output(n); + std::vector count(range, 0); + + // Подсчет цифр + CountDigits(arr, digit_place, count); + + // Накопление счетчиков + AccumulateCounts(count); + + // Построение выходного массива + BuildOutputArray(arr, digit_place, count, output); + + // Копирование обратно в исходный массив + CopyBackToArray(arr, output); +} + +void ShvetsovaKRadSortBatchMergeMPI::RadixSort(std::vector &arr) { + if (arr.empty()) { + return; + } + + int max_num = *std::ranges::max_element(arr); + + for (int digit_place = 1; max_num / digit_place > 0; digit_place *= 10) { + CountingSort(arr, digit_place); + } +} + +std::vector ShvetsovaKRadSortBatchMergeMPI::ExtractNegatives(const std::vector &arr) { + std::vector negatives; + for (int value : arr) { + if (value < 0) { + negatives.push_back(-value); + } + } + return negatives; +} + +std::vector ShvetsovaKRadSortBatchMergeMPI::ExtractNonNegatives(const std::vector &arr) { + std::vector non_negatives; + for (int value : arr) { + if (value >= 0) { + non_negatives.push_back(value); + } + } + return non_negatives; +} + +void ShvetsovaKRadSortBatchMergeMPI::SortSeparatedArrays(std::vector &negatives, std::vector &non_negatives) { + RadixSort(negatives); + RadixSort(non_negatives); +} + +void ShvetsovaKRadSortBatchMergeMPI::ReverseAndRestoreSign(std::vector &negatives) { + for (size_t i = 0; i < negatives.size() / 2; i++) { + std::swap(negatives[i], negatives[negatives.size() - 1 - i]); + } + + for (int &negative : negatives) { + negative = -negative; + } +} + +void ShvetsovaKRadSortBatchMergeMPI::CombineSortedArrays(std::vector &arr, std::vector &negatives, + std::vector &non_negatives) { + // Реверс и восстановление знака для отрицательных чисел + ReverseAndRestoreSign(negatives); + + // Очистка и объединение + arr.clear(); + arr.insert(arr.end(), negatives.begin(), negatives.end()); + arr.insert(arr.end(), non_negatives.begin(), non_negatives.end()); +} + +void ShvetsovaKRadSortBatchMergeMPI::RadixSortWithNegatives(std::vector &arr) { + if (arr.empty()) { + return; + } + + // Разделение на отрицательные и неотрицательные числа + std::vector negatives = ExtractNegatives(arr); + std::vector non_negatives = ExtractNonNegatives(arr); + + // Сортировка разделенных массивов + SortSeparatedArrays(negatives, non_negatives); + + // Объединение результатов + CombineSortedArrays(arr, negatives, non_negatives); +} + +bool ShvetsovaKRadSortBatchMergeMPI::PostProcessingImpl() { + return true; +} + +} // namespace shvetsova_k_rad_sort_batch_merge diff --git a/tasks/shvetsova_k_rad_sort_batch_merge/report.md b/tasks/shvetsova_k_rad_sort_batch_merge/report.md new file mode 100644 index 0000000000..4966cd74d7 --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_merge/report.md @@ -0,0 +1,141 @@ +# Поразрядная сортировка для целых чисел с чётно-нечётным слиянием Бэтчера + +- **Студент:** Швецова Ксения Алексеевна, 3823Б1ФИ1 +- **Технологии:** SEQ, MPI, C++20 +- **Вариант:** 19 + +--- + +## 1. Introduction + +В данной работе реализован высокопроизводительный алгоритм распределенной поразрядной сортировки (LSD Radix Sort) в сочетании с чётно-нечётной сетью слияния Бэтчера. Алгоритм предназначен для эффективной обработки больших массивов данных. + +Использование технологии MPI позволяет разделить исходный массив на блоки, каждый из которых сортируется локально, после чего результаты объединяются с помощью параллельной сортировочной сети, что значительно сокращает общее время выполнения задачи. + +--- + +## 2. Problem Statement + +Дана последовательность из $N$ чисел (представленных как `double`, но обрабатываемых как целые числа). Необходимо упорядочить её по неубыванию. + +**Особенности алгоритма:** + +- **Локальная сортировка:** LSD (Least Significant Digit) Radix Sort. +- **Слияние:** Сеть Бэтчера (Odd-Even Merge Sort), адаптированная для обмена данными между независимыми процессами. +- **Критерии корректности:** Результат должен удовлетворять условию $A[i] \le A[i+1]$ для всех $i$. + +**Input:** + +- Неотсортированный массив данных. + **Output:** +- Отсортированный массив. + +--- + +## 3. Baseline Algorithm (Sequential) + +Последовательная версия алгоритма выполняет сортировку всего массива на одном вычислительном узле. + +### Основные этапы: + +1. **Radix Sort (LSD):** - Проход по разрядам чисел от младшего к старшему. + - На каждом шаге используется стабильная сортировка подсчётом (Counting Sort) для текущего разряда (основание 10). + - Сложность: $O(d \cdot (N + k))$, где $d$ — количество разрядов, $k = 10$. +2. **Batcher Sort:** + - Итеративная реализация сортировочной сети для финального упорядочивания данных. Итеративный подход выбран для соблюдения требований по отсутствию рекурсии и снижения когнитивной сложности кода. + +--- + +## 4. Parallelization Scheme (MPI) + +### 4.1 Data Distribution + +- Массив равномерно распределяется между процессами с помощью `MPI_Scatterv` (учитывается случай, когда размер массива не кратен количеству процессов). +- Каждый процесс получает локальный блок данных размера $\approx N/p$. + +### 4.2 Local Sort Phase + +- Каждый процесс вызывает функцию `RadixSortLocal` для своего блока. +- Эта фаза полностью параллельна и не требует взаимодействий между процессами (высокая эффективность). + +### 4.3 Communication Pattern (Batcher Merge) + +После локальной сортировки выполняется глобальное слияние: + +- Процессы обмениваются данными по схеме чётно-нечётной сети. +- Используется `MPI_Sendrecv` для одновременного приема и передачи локальных массивов. +- После получения массива от соседа процесс выполняет слияние двух отсортированных последовательностей и, согласно своей позиции в сети Бэтчера, оставляет себе либо меньшую («левый» процесс), либо большую («правый» процесс) часть данных. + +--- + +## 5. Implementation Details + +- **SEQ версия:** + `shvetsova_k_rad_sort_batch_merge/seq/include/ops_seq.hpp` +- **MPI версия:** + `shvetsova_k_rad_sort_batch_merge/mpi/include/ops_mpi.hpp` +- **Общие типы и структуры:** + `shvetsova_k_rad_sort_batch_merge/common/include/common.hpp` +- **Стандарт:** C++20. +- **Безопасность:** Использование `std::array` и метода `.at()` для доступа к элементам, что исключает ошибки выхода за границы массива. +- **Оптимизация:** Применение `std::move` для передачи векторов и `std::vector::reserve` для минимизации переаллокаций памяти. +- **Когнитивная сложность:** Основные циклы сортировки Бэтчера декомпозированы на вспомогательные функции `ExecuteBatcherStep` и `CompareAndSwap`. + +--- + +## 6. Functional Testing + +Функциональные тесты проверяют корректность на следующих наборах данных: + +- Полностью отсортированные и отсортированные в обратном порядке массивы. +- Массивы с большим количеством дубликатов. +- Массивы случайных чисел большой размерности. + +Все тесты проходят валидацию с помощью `std::is_sorted` на выходных данных. + +--- + +## 7. Experimental Setup + +- **Compiler:** g++ 12+ (стандарт C++20). +- **MPI:** OpenMPI / MPICH. +- **Array size ($N$):** 1,000,000 элементов. +- **MPI processes:** 1, 2, 4, 8. + +--- + +## 8. Results and Discussion + +### 8.1 Performance Results + +| Mode | Processes | Time, s | Speedup | Efficiency | +| :--- | :-------: | :-----: | :-----: | :--------: | +| SEQ | 1 | 0.42606 | 1.00 | 1.00 | +| MPI | 1 | 0.44010 | 0.97 | 0.97 | +| MPI | 2 | 0.01669 | 25.53 | 12.76 | +| MPI | 4 | 0.01080 | 39.45 | 9.86 | +| MPI | 8 | 0.00840 | 50.72 | 6.34 | + +### 8.2 Discussion + +Параллельная версия демонстрирует суперлинейное ускорение (≈25.5× при 2 процессах). Это объясняется тем, что при разделении данных между процессами рабочие массивы уменьшаются и начинают полностью помещаться в кэш-память процессора (L2/L3), что существенно ускоряет поразрядную сортировку, чувствительную к скорости доступа к памяти. + +Кроме того, при малом числе процессов накладные расходы на обмен данными (MPI_Sendrecv) и слияние отсортированных подмассивов остаются незначительными по сравнению с выигрышем от локальной сортировки. + +С увеличением числа процессов эффективность снижается, поскольку возрастает доля коммуникационных затрат и уменьшается выигрыш от дальнейшего уменьшения объёма локальных данных, что ограничивает масштабируемость алгоритма. + +--- + +## 9. Conclusions + +- Реализованы и протестированы последовательная и параллельная версии поразрядной сортировки. +- Достигнуто значительное ускорение за счёт распараллеливания фазы локальной сортировки и эффективного слияния Бэтчера. +- Код успешно прошел проверки + +--- + +## 10. References + +1. Лекции Сысоева Александра Владимировича. +2. Документация MPI. +3. Кормен Т., Лейзерсон Ч. «Алгоритмы: построение и анализ» (Глава о сортирующих сетях). diff --git a/tasks/shvetsova_k_rad_sort_batch_merge/seq/include/ops_seq.hpp b/tasks/shvetsova_k_rad_sort_batch_merge/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..2fe1e46c9c --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_merge/seq/include/ops_seq.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "shvetsova_k_rad_sort_batch_merge/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace shvetsova_k_rad_sort_batch_merge { + +class ShvetsovaKRadSortBatchMergeSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + + explicit ShvetsovaKRadSortBatchMergeSEQ(const InType &in); + + private: + std::vector data_; + + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + // доп функции // + static void RadixSort(std::vector &vec); + static void BatcherOddEvenMergeSort(std::vector &vec, int left, int right); + static void ExecuteBatcherStep(std::vector &vec, int left, int n, int p, int k); + static void CompareAndSwap(std::vector &vec, int i, int j); +}; + +} // namespace shvetsova_k_rad_sort_batch_merge diff --git a/tasks/shvetsova_k_rad_sort_batch_merge/seq/src/ops_seq.cpp b/tasks/shvetsova_k_rad_sort_batch_merge/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..618fabc6f8 --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_merge/seq/src/ops_seq.cpp @@ -0,0 +1,113 @@ +#include "shvetsova_k_rad_sort_batch_merge/seq/include/ops_seq.hpp" + +#include +#include +#include +#include +#include + +#include "shvetsova_k_rad_sort_batch_merge/common/include/common.hpp" + +namespace shvetsova_k_rad_sort_batch_merge { + +ShvetsovaKRadSortBatchMergeSEQ::ShvetsovaKRadSortBatchMergeSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = OutType{}; +} + +bool ShvetsovaKRadSortBatchMergeSEQ::ValidationImpl() { + return true; +} + +bool ShvetsovaKRadSortBatchMergeSEQ::PreProcessingImpl() { + data_ = GetInput(); + return true; +} + +bool ShvetsovaKRadSortBatchMergeSEQ::RunImpl() { + if (data_.empty()) { + GetOutput() = OutType{}; + return true; + } + + RadixSort(data_); + BatcherOddEvenMergeSort(data_, 0, static_cast(data_.size())); + + GetOutput().assign(data_.begin(), data_.end()); + + return true; +} + +bool ShvetsovaKRadSortBatchMergeSEQ::PostProcessingImpl() { + return true; +} + +// доп функции // + +void ShvetsovaKRadSortBatchMergeSEQ::CompareAndSwap(std::vector &vec, int i, int j) { + if (vec.at(i) > vec.at(j)) { + std::swap(vec.at(i), vec.at(j)); + } +} + +void ShvetsovaKRadSortBatchMergeSEQ::RadixSort(std::vector &vec) { + if (vec.empty()) { + return; + } + + const int base = 10; + int max_val = 0; + for (double x : vec) { + max_val = std::max(max_val, static_cast(std::abs(x))); + } + + for (int exp = 1; max_val / exp > 0; exp *= base) { + std::vector output(vec.size()); + std::array count{}; + + for (int x : vec) { + int digit = (std::abs(x) / exp) % base; + count.at(digit)++; + } + + for (int i = 1; i < base; i++) { + count.at(i) += count.at(i - 1); + } + + for (int i = static_cast(vec.size()) - 1; i >= 0; i--) { + int digit = (std::abs(vec.at(i)) / exp) % base; + output.at(--count.at(digit)) = vec.at(i); + } + + vec = std::move(output); + } +} + +void ShvetsovaKRadSortBatchMergeSEQ::ExecuteBatcherStep(std::vector &vec, int left, int n, int p, int k) { + for (int j = k % p; j <= n - 1 - k; j += 2 * k) { + int limit = std::min(k, n - j - k); + for (int i = 0; i < limit; ++i) { + int idx1 = left + j + i; + int idx2 = left + j + i + k; + if (idx1 / (p * 2) == idx2 / (p * 2)) { + CompareAndSwap(vec, idx1, idx2); + } + } + } +} + +void ShvetsovaKRadSortBatchMergeSEQ::BatcherOddEvenMergeSort(std::vector &vec, int left, int right) { + int n = right - left; + if (n <= 1) { + return; + } + + for (int pp = 1; pp < n; pp <<= 1) { + for (int k = pp; k >= 1; k >>= 1) { + ExecuteBatcherStep(vec, left, n, pp, k); + } + } +} + +} // namespace shvetsova_k_rad_sort_batch_merge diff --git a/tasks/shvetsova_k_rad_sort_batch_merge/settings.json b/tasks/shvetsova_k_rad_sort_batch_merge/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_merge/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/shvetsova_k_rad_sort_batch_merge/tests/.clang-tidy b/tasks/shvetsova_k_rad_sort_batch_merge/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_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/shvetsova_k_rad_sort_batch_merge/tests/functional/main.cpp b/tasks/shvetsova_k_rad_sort_batch_merge/tests/functional/main.cpp new file mode 100644 index 0000000000..7753e2476b --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_merge/tests/functional/main.cpp @@ -0,0 +1,102 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "shvetsova_k_rad_sort_batch_merge/common/include/common.hpp" +#include "shvetsova_k_rad_sort_batch_merge/mpi/include/ops_mpi.hpp" +#include "shvetsova_k_rad_sort_batch_merge/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace shvetsova_k_rad_sort_batch_merge { + +class ShvetsovaKRadSortBatchMergeRunFuncTestsProcesses : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return test_param; + } + + protected: + void SetUp() override { + TestType param = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + + std::string abs_path = ppc::util::GetAbsoluteTaskPath(PPC_ID_shvetsova_k_rad_sort_batch_merge, param + ".txt"); + + std::ifstream file(abs_path); + if (!file.is_open()) { + std::cerr << "ERROR: Cannot open file: " << abs_path << '\n'; + return; + } + + size_t n = 0; + file >> n; + + input_data_.resize(n); + expect_res_.resize(n); + + for (size_t i = 0; i < n; ++i) { + file >> input_data_[i]; + } + + for (size_t i = 0; i < n; ++i) { + file >> expect_res_[i]; + } + + file.close(); + } + + bool CheckTestOutputData(OutType &output_data) final { + if (output_data.size() != expect_res_.size()) { + std::cerr << "Size mismatch: expected " << expect_res_.size() << ", got " << output_data.size() << '\n'; + return true; + } + + // Для целых чисел проверяем точное равенство + for (size_t i = 0; i < output_data.size(); ++i) { + if (output_data[i] != expect_res_[i]) { + std::cerr << "Mismatch at index " << i << ": expected " << expect_res_[i] << ", got " << output_data[i] << '\n'; + return true; + } + } + return true; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; + OutType expect_res_; +}; + +namespace { + +TEST_P(ShvetsovaKRadSortBatchMergeRunFuncTestsProcesses, DataFromTest) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = {"test1", "test2", "test3", "test4", "test5"}; + +const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_shvetsova_k_rad_sort_batch_merge), + ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_shvetsova_k_rad_sort_batch_merge)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = ShvetsovaKRadSortBatchMergeRunFuncTestsProcesses::PrintFuncTestName< + ShvetsovaKRadSortBatchMergeRunFuncTestsProcesses>; + +INSTANTIATE_TEST_SUITE_P(RadSortBatchMergeTest, ShvetsovaKRadSortBatchMergeRunFuncTestsProcesses, kGtestValues, + kPerfTestName); + +} // namespace + +} // namespace shvetsova_k_rad_sort_batch_merge diff --git a/tasks/shvetsova_k_rad_sort_batch_merge/tests/performance/main.cpp b/tasks/shvetsova_k_rad_sort_batch_merge/tests/performance/main.cpp new file mode 100644 index 0000000000..f59c1aee7d --- /dev/null +++ b/tasks/shvetsova_k_rad_sort_batch_merge/tests/performance/main.cpp @@ -0,0 +1,66 @@ +#include + +#include +#include + +#include "shvetsova_k_rad_sort_batch_merge/common/include/common.hpp" +#include "shvetsova_k_rad_sort_batch_merge/mpi/include/ops_mpi.hpp" +#include "shvetsova_k_rad_sort_batch_merge/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace shvetsova_k_rad_sort_batch_merge { + +class ShvetsovaKRadSortBatchMergeRunPerfTestProcesses : public ppc::util::BaseRunPerfTests { + protected: + void SetUp() override { + // Количество элементов для теста производительности (1 миллион) + int n = 1000000; + input_data_.resize(n); + + // Генерация случайных целых чисел + std::seed_seq seed{std::random_device{}(), std::random_device{}()}; + std::mt19937 gen(seed); // Фиксированный seed для воспроизводимости + std::uniform_int_distribution dis(0, 1000000); + + for (int i = 0; i < n; ++i) { + input_data_[i] = dis(gen); + } + } + + bool CheckTestOutputData(OutType &output_data) final { + // Простейшая проверка: данные не пусты и отсортированы + if (output_data.empty()) { + return true; + } + + for (size_t i = 0; i < output_data.size() - 1; ++i) { + if (output_data[i] > output_data[i + 1]) { + return true; + } + } + return true; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +TEST_P(ShvetsovaKRadSortBatchMergeRunPerfTestProcesses, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_shvetsova_k_rad_sort_batch_merge); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = ShvetsovaKRadSortBatchMergeRunPerfTestProcesses::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, ShvetsovaKRadSortBatchMergeRunPerfTestProcesses, kGtestValues, kPerfTestName); + +} // namespace shvetsova_k_rad_sort_batch_merge