diff --git a/tasks/shakirova_e_shells_sort_simple/common/include/common.hpp b/tasks/shakirova_e_shells_sort_simple/common/include/common.hpp new file mode 100644 index 0000000000..f7b897b66d --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/common/include/common.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace shakirova_e_shells_sort_simple { + +using InType = std::vector; +using OutType = std::vector; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace shakirova_e_shells_sort_simple diff --git a/tasks/shakirova_e_shells_sort_simple/common/include/shell_sort.hpp b/tasks/shakirova_e_shells_sort_simple/common/include/shell_sort.hpp new file mode 100644 index 0000000000..69d6714e25 --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/common/include/shell_sort.hpp @@ -0,0 +1,26 @@ +#pragma once +#include + +namespace shakirova_e_shells_sort_simple { + +inline void ShellSortImpl(std::vector &vec, int left, int right) { + int n = right - left + 1; + if (n <= 1) { + return; + } + + for (int gap = n / 2; gap > 0; gap /= 2) { + for (int i = left + gap; i <= right; i++) { + int temp = vec[i]; + int j = i; + + while (j >= left + gap && vec[j - gap] > temp) { + vec[j] = vec[j - gap]; + j -= gap; + } + vec[j] = temp; + } + } +} + +} // namespace shakirova_e_shells_sort_simple diff --git a/tasks/shakirova_e_shells_sort_simple/data/test_1.txt b/tasks/shakirova_e_shells_sort_simple/data/test_1.txt new file mode 100644 index 0000000000..970c851d81 --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/data/test_1.txt @@ -0,0 +1,2 @@ +-20 15 -10 25 0 -5 30 +-20 -10 -5 0 15 25 30 \ No newline at end of file diff --git a/tasks/shakirova_e_shells_sort_simple/data/test_2.txt b/tasks/shakirova_e_shells_sort_simple/data/test_2.txt new file mode 100644 index 0000000000..da10ca8f2b --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/data/test_2.txt @@ -0,0 +1,2 @@ +45 23 87 12 65 34 90 21 56 78 +12 21 23 34 45 56 65 78 87 90 \ No newline at end of file diff --git a/tasks/shakirova_e_shells_sort_simple/data/test_3.txt b/tasks/shakirova_e_shells_sort_simple/data/test_3.txt new file mode 100644 index 0000000000..a76595266b --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/data/test_3.txt @@ -0,0 +1,2 @@ +847 -234 567 -89 923 156 -456 734 289 512 678 -123 934 145 867 423 -678 756 290 601 834 -345 467 912 345 778 -567 601 234 889 467 -234 712 356 945 178 -890 623 890 456 723 -456 267 934 501 -234 867 234 756 489 -678 912 345 678 823 -345 456 734 267 -789 901 534 867 290 -456 745 412 889 356 -567 712 245 934 567 -234 801 434 767 312 -678 889 456 723 290 -345 867 434 756 289 -456 912 545 778 323 -234 867 490 734 267 -567 901 534 767 312 -345 889 456 723 290 -678 867 434 756 289 -456 912 545 778 323 -234 867 490 734 267 -567 901 534 767 312 -345 889 456 723 290 -678 867 434 756 289 -456 912 545 778 323 -234 867 490 734 267 -567 901 534 767 312 -345 889 456 723 290 -678 867 434 756 289 -456 912 545 778 323 -234 867 490 734 267 -567 901 534 767 312 -345 889 456 723 290 -678 867 434 756 289 -456 912 545 778 323 -234 867 +-890 -789 -678 -678 -678 -678 -678 -678 -567 -567 -567 -567 -567 -567 -456 -456 -456 -456 -456 -456 -456 -456 -456 -456 -456 -456 -345 -345 -345 -345 -345 -345 -345 -345 -234 -234 -234 -234 -234 -234 -234 -234 -234 -234 -234 -234 -123 -89 145 156 178 234 234 234 245 267 267 267 267 267 267 289 289 289 289 289 290 290 290 290 290 290 312 312 312 312 312 323 323 323 323 323 345 345 345 356 356 412 423 434 434 434 434 434 434 456 456 456 456 456 456 467 467 489 490 490 490 490 490 501 512 534 534 534 534 534 545 545 545 545 545 567 567 567 601 601 623 678 678 712 712 723 723 723 723 723 734 734 734 734 734 745 756 756 756 756 756 767 767 767 767 778 778 778 778 778 801 823 834 847 867 867 867 867 867 867 867 867 867 867 867 867 889 889 889 889 889 889 890 901 901 901 901 901 912 912 912 912 912 912 923 934 934 934 945 \ No newline at end of file diff --git a/tasks/shakirova_e_shells_sort_simple/data/test_4.txt b/tasks/shakirova_e_shells_sort_simple/data/test_4.txt new file mode 100644 index 0000000000..8341646f89 --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/data/test_4.txt @@ -0,0 +1,2 @@ +847 923 156 734 289 512 678 934 145 867 423 756 290 601 834 467 912 345 778 601 234 889 467 712 356 945 178 623 890 456 723 267 934 501 867 234 756 489 912 345 678 823 456 734 267 901 534 867 290 745 412 889 356 712 245 934 567 801 434 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 323 867 490 734 267 901 534 767 312 889 456 723 290 867 434 756 289 912 545 778 +145 156 178 234 234 245 267 267 267 267 267 267 267 267 267 267 267 267 267 267 267 267 267 267 267 267 267 267 267 267 267 267 267 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 289 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 290 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 312 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 323 345 345 356 356 356 412 423 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 434 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 456 467 467 489 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 490 501 512 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 534 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 545 567 601 601 623 678 678 712 712 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 723 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 734 745 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 756 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 767 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 778 801 823 834 847 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 867 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 889 890 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 901 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 912 923 934 934 934 934 945 \ No newline at end of file diff --git a/tasks/shakirova_e_shells_sort_simple/data_report/img1.png b/tasks/shakirova_e_shells_sort_simple/data_report/img1.png new file mode 100644 index 0000000000..0374d5b3a0 Binary files /dev/null and b/tasks/shakirova_e_shells_sort_simple/data_report/img1.png differ diff --git a/tasks/shakirova_e_shells_sort_simple/data_report/img2.png b/tasks/shakirova_e_shells_sort_simple/data_report/img2.png new file mode 100644 index 0000000000..bd3f9b9821 Binary files /dev/null and b/tasks/shakirova_e_shells_sort_simple/data_report/img2.png differ diff --git a/tasks/shakirova_e_shells_sort_simple/info.json b/tasks/shakirova_e_shells_sort_simple/info.json new file mode 100644 index 0000000000..2d46f2ee73 --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/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/shakirova_e_shells_sort_simple/mpi/include/ops_mpi.hpp b/tasks/shakirova_e_shells_sort_simple/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..6482454543 --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "shakirova_e_shells_sort_simple/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace shakirova_e_shells_sort_simple { + +class ShakirovaEShellsSortSimpleMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit ShakirovaEShellsSortSimpleMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace shakirova_e_shells_sort_simple diff --git a/tasks/shakirova_e_shells_sort_simple/mpi/src/ops_mpi.cpp b/tasks/shakirova_e_shells_sort_simple/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..bc4fa0d0b4 --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/mpi/src/ops_mpi.cpp @@ -0,0 +1,88 @@ +#include "shakirova_e_shells_sort_simple/mpi/include/ops_mpi.hpp" + +#include + +#include +#include + +#include "shakirova_e_shells_sort_simple/common/include/common.hpp" +#include "shakirova_e_shells_sort_simple/common/include/shell_sort.hpp" + +namespace shakirova_e_shells_sort_simple { + +ShakirovaEShellsSortSimpleMPI::ShakirovaEShellsSortSimpleMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool ShakirovaEShellsSortSimpleMPI::ValidationImpl() { + return true; +} + +bool ShakirovaEShellsSortSimpleMPI::PreProcessingImpl() { + return true; +} + +bool ShakirovaEShellsSortSimpleMPI::RunImpl() { + int size = 0; + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + int total_elements = 0; + if (rank == 0) { + total_elements = static_cast(GetInput().size()); + } + MPI_Bcast(&total_elements, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (total_elements == 0) { + return true; + } + + std::vector send_counts(size); + std::vector displs(size); + int base_count = total_elements / size; + int remainder = total_elements % size; + int current_displ = 0; + + for (int i = 0; i < size; ++i) { + send_counts[i] = base_count + (i < remainder ? 1 : 0); + displs[i] = current_displ; + current_displ += send_counts[i]; + } + + std::vector local_vec(send_counts[rank]); + MPI_Scatterv(rank == 0 ? GetInput().data() : nullptr, send_counts.data(), displs.data(), MPI_INT, local_vec.data(), + send_counts[rank], MPI_INT, 0, MPI_COMM_WORLD); + + if (!local_vec.empty()) { + ShellSortImpl(local_vec, 0, static_cast(local_vec.size()) - 1); + } + + if (rank == 0) { + GetOutput().resize(total_elements); + } + + MPI_Gatherv(local_vec.data(), send_counts[rank], MPI_INT, rank == 0 ? GetOutput().data() : nullptr, + send_counts.data(), displs.data(), MPI_INT, 0, MPI_COMM_WORLD); + + if (rank == 0) { + auto current_end = GetOutput().begin() + send_counts[0]; + + for (int i = 1; i < size; ++i) { + if (send_counts[i] > 0) { + auto next_end = current_end + send_counts[i]; + std::inplace_merge(GetOutput().begin(), current_end, next_end); + current_end = next_end; + } + } + } + + return true; +} + +bool ShakirovaEShellsSortSimpleMPI::PostProcessingImpl() { + return true; +} + +} // namespace shakirova_e_shells_sort_simple diff --git a/tasks/shakirova_e_shells_sort_simple/report.md b/tasks/shakirova_e_shells_sort_simple/report.md new file mode 100644 index 0000000000..af50232a25 --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/report.md @@ -0,0 +1,372 @@ +# Сортировка Шелла с простым слиянием. + +- Студент: Шакирова Есения Андреевна, группа 3823Б1ПР2 +- Технология: SEQ | MPI +- Вариант: 16 + + +## 1. Введение + +Сортировка массивов является одной из фундаментальных операций в информатике, широко применяемой в базах данных, поисковых системах, анализе данных и других областях. Алгоритм сортировки Шелла (Shell Sort) — это усовершенствованный метод сортировки вставками, который показывает хорошую производительность на средних и больших массивах данных. + +В данной работе разработаны последовательная `SEQ` и параллельная `MPI` реализации алгоритма сортировки Шелла. Последовательная версия выполняет сортировку на одном процессоре, параллельная распределяет данные между процессами с использованием `MPI` и выполняет последующее слияние отсортированных подмассивов. Обе реализации интегрированы в фреймворк `PPC` и полностью протестированы. + + +## 2. Постановка задачи +**Задача:** Отсортировать массив целых чисел по возрастанию, используя алгоритм сортировки Шелла. + +**Входные данные:** Неотсортированный массив целых чисел произвольного размера: ```using InType = std::vector;```; + +**Выходные данные:** Отсортированный массив целых чисел: ```using OutType = std::vector;``` + + +## 3. Описание алгоритма +### 3.1. Алгоритм сортировки Шелла + +Сортировка Шелла — это обобщение сортировки вставками, которая сравнивает элементы, отстоящие друг от друга на определённом расстоянии (gap). Начиная с большого gap, алгоритм постепенно уменьшает его до 1, что эквивалентно обычной сортировке вставками. + +**Основная идея:** +1. Выбирается начальный шаг (gap) = n/2 +2. Элементы, отстоящие на gap позиций, сравниваются и при необходимости меняются местами +3. Gap уменьшается в 2 раза: gap = gap/2 +4. Процесс повторяется до gap = 1 +5. При gap = 1 выполняется обычная сортировка вставками, которая работает быстро на почти отсортированном массиве + +```cpp +inline void ShellSortImpl(std::vector &vec, int left, int right) { + int n = right - left + 1; + if (n <= 1) { + return; + } + + for (int gap = n / 2; gap > 0; gap /= 2) { + for (int i = left + gap; i <= right; i++) { + int temp = vec[i]; + int j = i; + + while (j >= left + gap && vec[j - gap] > temp) { + vec[j] = vec[j - gap]; + j -= gap; + } + vec[j] = temp; + } + } +} +``` + +**Сложность:** +– Временная: `O(n^2)` в худшем случае; +– Пространственная: `O(1)`. + + +### 3.2. Базовый алгоритм (Последовательная версия SEQ) +**Алгоритм работы SEQ-версии:** + +1. Валидация: +- Всегда возвращает `true`, так как любой массив может быть отсортирован; + +2. Предобработка: +- Копирование входного массива в выходной: `GetOutput() = GetInput()`; + +3. Основной цикл: +```cpp + if (GetOutput().empty()) { + return true; + } + + ShellSortImpl(GetOutput(), 0, GetOutput().size() - 1); +``` + +4. Постобработка: +- Возвращает `true` — дополнительная обработка не требуется. + + +### 3.3. Описание параллельного алгоритма (Версия MPI) + +**Схема распараллеливания** + +Параллельная версия основана на стратегии "разделяй и властвуй": +1. Массив разделяется между процессами +2. Каждый процесс независимо сортирует свою часть алгоритмом Шелла +3. Главный процесс последовательно сливает отсортированные части + + +**Распределение нагрузки:** + +Распределение элементов между процессами выполняется статически на основе общего размера массива и количества MPI-процессов + +```cpp +int base_count = total_elements / size; +int remainder = total_elements % size; + +for (int i = 0; i < size; ++i) { + send_counts[i] = base_count + (i < remainder ? 1 : 0); + displs[i] = current_displ; + current_displ += send_counts[i]; +} +``` +- Если n % P ≠ 0, первые remainder процессов получают на 1 элемент больше; +- Разница в нагрузке между процессами не превышает 1 элемента; + +**Этапы работы параллельного алгоритма:** +- Синхронизация параметров `MPI_Bcast`: +```cpp + if (rank == 0) { + total_elements = GetInput().size(); + } + MPI_Bcast(&total_elements, 1, MPI_INT, 0, MPI_COMM_WORLD); +``` + +- Распределение данных `MPI_Scatterv`: +```cpp + MPI_Scatterv( + rank == 0 ? GetInput().data() : nullptr, // Источник (только на rank 0) + send_counts.data(), // Размеры блоков + displs.data(), // Смещения + MPI_INT, + local_vec.data(), // Локальный буфер + send_counts[rank], // Размер локального буфера + MPI_INT, + 0, // Корневой процесс + MPI_COMM_WORLD + ); + +``` + +- Локальная сортировка: +```cpp + if (!local_vec.empty()) { + ShellSortImpl(local_vec, 0, local_vec.size() - 1); + } +``` + +- Сбор данных `MPI_Gatherv`: +```cpp + MPI_Gatherv( + local_vec.data(), // Локальные данные + send_counts[rank], + MPI_INT, + rank == 0 ? GetOutput().data() : nullptr, // Результат (только на rank 0) + send_counts.data(), + displs.data(), + MPI_INT, + 0, + MPI_COMM_WORLD + ); +``` + +- Слияние отсортированных частей: +```cpp + auto current_end = GetOutput().begin() + send_counts[0]; + + for (int i = 1; i < size; ++i) { + if (send_counts[i] > 0) { + auto next_end = current_end + send_counts[i]; + std::inplace_merge(GetOutput().begin(), current_end, next_end); + current_end = next_end; + } + } +``` + +**Сложность:** +- Временная: `O((n/P)^2 + n × P)`: +- - `O((n/P)^2)` — локальная сортировка подмассива алгоритмом Шелла на каждом процессе; +- - `O(n × P)` — последовательное слияние P отсортированных подмассивов на корневом процессе; +- Пространственная: `O(n/P)` на каждом процессе + + +## 4. Детали реализации + +#### 4.1. Тестирование и структура входных данных + +Для проверки корректности работы последовательной `SEQ` и параллельной `MPI` реализаций алгоритма сортировки Шелла используются функциональные и performance-тесты, реализованные на основе фреймворка `Google Test` и утилит фреймворка `PPC`. + +В качестве входных данных используется одномерный массив целых чисел: + +```cpp +using InType = std::vector; +using OutType = std::vector; +``` + +Тестирование охватывает как заранее подготовленные наборы данных, так и автоматически сгенерированные массивы различного размера и содержания. + + +#### 4.1.1. Функциональные тесты + +Функциональные тесты реализованы в классе `ShakirovaEShellsSortSimpleFuncTests`, который наследуется от базового класса фреймворка `PPC`: + +```cpp +class ShakirovaEShellsSortSimpleFuncTests + : public ppc::util::BaseRunFuncTests +``` + +**Подготовка входных данных:** Входной массив извлекается из параметров теста в методе `SetUp`: +```cpp +void SetUp() override { + input_data_ = std::get< + static_cast(ppc::util::GTestParamIndex::kTestParams) + >(GetParam()); +} +``` + +***Проверка корректности результата:** Проверка результата выполняется только на корневом процессе (rank = 0), что соответствует архитектуре MPI-реализации: +```cpp +bool CheckTestOutputData(OutType &output_data) final { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + if (rank == 0) { + std::vector sorted_reference = input_data_; + ShellSortImpl(sorted_reference, 0, sorted_reference.size() - 1); + return output_data == sorted_reference; + } + return true; +} +``` + +В качестве эталона используется та же реализация сортировки Шелла, что исключает влияние сторонних библиотек и обеспечивает воспроизводимость результатов. + + +#### 4.1.2. Наборы тестовых данных + +Функциональные тесты включают следующие категории входных данных: +- Пустой массив; +- Массив из одного элемента; +- Уже отсортированный массив; +- Массив, отсортированный в обратном порядке; +- Массив с повторяющимися элементами; +- Массивы с отрицательными числами; +- Массивы со значениями `INT_MIN` и `INT_MAX`; +- Случайно сгенерированные массивы. + +Пример встроенного набора тестов: + +```cpp +const std::array kTestVectors = { + TestParams{}, + TestParams{42}, + TestParams{9, 7, 5, 3, 1}, + TestParams{10, 20, 30, 40, 50, 60}, + TestParams{7, 7, 7, 7, 7}, + TestParams{-8, -3, -6, -1, -9}, + TestParams{INT_MAX, 0, INT_MIN} +}; +``` + +#### 4.1.3. Загрузка тестов из файлов + +Дополнительно поддерживается загрузка тестовых массивов из текстовых файлов, расположенных в каталоге `data/.` + +Формат файла — две строки с целыми числами, разделёнными пробелами: +- 1 строка - данный массив для сортировки; +- 2 строка - отсортированный массив; + +``` +-20 15 -10 25 0 -5 30 +-20 -10 -5 0 15 25 30 +``` + +Загрузка реализована следующим образом: + +```cpp +std::vector ReadVectorFromFile(const std::string &filename) { + std::ifstream file(filename); + std::vector result; + int value; + + while (file >> value) { + result.push_back(value); + } + return result; +} +``` + +Такой подход позволяет легко добавлять новые тестовые примеры без изменения кода. + + +#### 4.1.4. Performance-тесты + +Для оценки производительности реализованы performance-тесты в классе `ShakirovaEShellsSortSimplePerfTests`, унаследованном от: + +```cpp +ppc::util::BaseRunPerfTests +``` + +**Генерация входных данных:** Входной массив большого размера генерируется автоматически + +```cpp +const int count = 1500000; +input_data_.resize(count); + +std::mt19937 gen(rd()); +std::uniform_int_distribution dist(-150000, 150000); +``` + +Размер массива выбран таким образом, чтобы продемонстрировать различия во времени выполнения между `SEQ` и `MPI`-реализациями. + +**Проверка результата:** Корректность результата также проверяется на корневом процессе путём сравнения с эталонной сортировкой. + + +### 4.2.Интеграция с фреймворком PPC + +Реализация алгоритма полностью интегрирована во фреймворк `PPC` и использует его базовые компоненты: +- `BaseTask` — базовый класс для вычислительных задач; +- `BaseRunFuncTests` — инфраструктура функциональных тестов; +- `BaseRunPerfTests` — инфраструктура performance-тестирования; +- автоматическую параметризацию тестов через `INSTANTIATE_TEST_SUITE_P`. + +Обе версии алгоритма `SEQ` и `MPI` реализуют единый интерфейс задач `PPC`, что обеспечивает их взаимозаменяемость и упрощает сравнительный анализ. + + +## 5. Тестовое окружение +- Аппаратное обеспечение/Операционная система: Intel(R) Core(TM) i5 5200U, 2P+4E ядер, 8Gb Ddr3 1600Mhz, Windows 10, MS-MPI. +- Инструменты сборки: Cmake 4.2.0-rc4, Visual Studio 2022, MSVC, x64 Release. +- Переменные окружения и запуск тестов: Использовались настройки по умолчанию. Performance-тесты для `MPI` запускались с количеством процессов 1, 2, 4, 6 и 8 через `mpiexec`. Переменная `PPC_PERF_MAX_TIME` оставалась без изменений. Проверка корректности MPI-версии выполнялась на процессе с rank == 0. +- Данные для тестов: В функциональных тестах использовались как вручную подготовленные текстовые файлы из каталога `data/` (с массивами целых чисел и ожидаемыми отсортированными результатами), так и встроенные тестовые векторы. Для оценки производительности создавались случайные массивы больших размеров (до 1.5 млн элементов), что позволяло тестировать эффективность и масштабируемость `SEQ` и `MPI` версий алгоритма. + + +## 6. Результаты + +### 6.1 Корректность + +Корректность работы алгоритма сортировки Шелла была проверена с помощью функциональных тестов на различных типах массивов: пустых, одноэлементных, упорядоченных и обратных, с одинаковыми элементами, отрицательными числами, случайных и загруженных из файлов. + +Во всех случаях последовательная и параллельная версии алгоритма давали идентичный результат, полностью совпадающий с эталонной сортировкой, выполненной через `ShellSortImpl`. Для MPI-версии проверка корректности производится только на основном процессе (`rank = 0`), что исключает возможные конфликты при распределённой обработке и обеспечивает точное сравнение с эталонным результатом. + +Такой подход гарантирует стабильность и повторяемость результатов независимо от размера массива и способа его распределения между процессами. + + +### 6.2 Производительность +Время, ускорение, эффективность: + +| Режим | Кол-во процессов | Время, сек | Ускорение | Эффективность параллелизма | +|-------|-----------------|------------|-----------|----------------------------| +| SEQ | 1 | 26 376.7 | 1.00 | N/A | +| MPI | 2 | 1 587 | 16.6 | 83.0% | +| MPI | 4 | 990 | 26.6 | 66.5% | +| MPI | 6 | 753 | 35.0 | 58.3% | +| MPI | 8 | 674 | 39.1 | 48.9% | + +| Ускорение от количества процессов | Эффективность параллелизма | +|---------------------------------------------------|--------------------------------------------| +| ![Ускорение от количества процессов](./data_report/img1.png) | ![Эффективность параллелизма](./data_report/img2.png) | + + +1. **Ускорение MPI-версии:** + MPI-реализация сортировки Шелла демонстрирует значительное ускорение по сравнению с последовательной реализацией. Уже при 2 процессах ускорение достигает ~16×, что очень заметно. + +2. **Эффективность параллельной работы:** + - При 2–4 процессах эффективность остаётся высокой (≈66–83%). Это объясняется тем, что накладные расходы на пересылку данных ещё невелики по сравнению с объёмом вычислений. + - При 6–8 процессах эффективность падает до ≈49–58%, так как основное время начинает уходить на коммуникацию между процессами и управление буферами MPI, а не на вычисления. + + +## 8. Заключение + +Были реализованы последовательная и MPI-версии сортировки Шелла. Функциональные тесты подтвердили корректность работы обеих версий. +Производительность MPI-версии значительно повышается при использовании 2–4 процессов, однако при 6–8 процессах эффективность падает из-за накладных расходов на пересылку данных и особенностей работы MPI в Windows. +Таким образом, MPI-реализацию целесообразно использовать при небольшом числе процессов, когда можно получить заметное ускорение без значительной потери эффективности. + +## 9. Источники +1. Курс лекций "Параллельное программирование" Сысоева Александра Владимировича +2. Документация по курсу: https://learning-process.github.io/parallel_programming_course/ru +3. Курс лекций "Параллельная обработка данных": https://parallel.ru/vvv/mpi \ No newline at end of file diff --git a/tasks/shakirova_e_shells_sort_simple/seq/include/ops_seq.hpp b/tasks/shakirova_e_shells_sort_simple/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..c92068924e --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "shakirova_e_shells_sort_simple/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace shakirova_e_shells_sort_simple { + +class ShakirovaEShellsSortSimpleSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit ShakirovaEShellsSortSimpleSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace shakirova_e_shells_sort_simple diff --git a/tasks/shakirova_e_shells_sort_simple/seq/src/ops_seq.cpp b/tasks/shakirova_e_shells_sort_simple/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..0626529edf --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/seq/src/ops_seq.cpp @@ -0,0 +1,38 @@ +#include "shakirova_e_shells_sort_simple/seq/include/ops_seq.hpp" + +#include + +#include "shakirova_e_shells_sort_simple/common/include/common.hpp" +#include "shakirova_e_shells_sort_simple/common/include/shell_sort.hpp" + +namespace shakirova_e_shells_sort_simple { + +ShakirovaEShellsSortSimpleSEQ::ShakirovaEShellsSortSimpleSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool ShakirovaEShellsSortSimpleSEQ::ValidationImpl() { + return true; +} + +bool ShakirovaEShellsSortSimpleSEQ::PreProcessingImpl() { + GetOutput() = GetInput(); + return true; +} + +bool ShakirovaEShellsSortSimpleSEQ::RunImpl() { + if (GetOutput().empty()) { + return true; + } + + ShellSortImpl(GetOutput(), 0, static_cast(GetOutput().size()) - 1); + + return true; +} + +bool ShakirovaEShellsSortSimpleSEQ::PostProcessingImpl() { + return true; +} + +} // namespace shakirova_e_shells_sort_simple diff --git a/tasks/shakirova_e_shells_sort_simple/settings.json b/tasks/shakirova_e_shells_sort_simple/settings.json new file mode 100644 index 0000000000..7d2c35b298 --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/shakirova_e_shells_sort_simple/tests/.clang-tidy b/tasks/shakirova_e_shells_sort_simple/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/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/shakirova_e_shells_sort_simple/tests/functional/main.cpp b/tasks/shakirova_e_shells_sort_simple/tests/functional/main.cpp new file mode 100644 index 0000000000..aa246deeaf --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/tests/functional/main.cpp @@ -0,0 +1,152 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shakirova_e_shells_sort_simple/common/include/common.hpp" +#include "shakirova_e_shells_sort_simple/common/include/shell_sort.hpp" +#include "shakirova_e_shells_sort_simple/mpi/include/ops_mpi.hpp" +#include "shakirova_e_shells_sort_simple/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace shakirova_e_shells_sort_simple { + +using TestParams = std::vector; + +static int test_counter = 0; + +class ShakirovaEShellsSortSimpleFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestParams &p) { + std::string unique_id = "Test_" + std::to_string(test_counter++); + + if (p.empty()) { + return unique_id + "_EmptyVector"; + } + + std::string val = std::to_string(p[0]); + if (p[0] < 0) { + val = "Neg" + std::to_string(std::abs(p[0])); + } + + return unique_id + "_Size_" + std::to_string(p.size()) + "_First_" + val; + } + + protected: + void SetUp() override { + input_data_ = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + } + + bool CheckTestOutputData(OutType &output_data) final { + int rank = 0; + int initialized = 0; + MPI_Initialized(&initialized); + if (initialized != 0) { + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + } + + if (rank == 0) { + std::vector sorted_reference = input_data_; + if (!sorted_reference.empty()) { + ShellSortImpl(sorted_reference, 0, static_cast(sorted_reference.size()) - 1); + } + return output_data == sorted_reference; + } + return true; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +namespace { + +std::vector GenerateRandomVector(size_t size, int seed) { + std::mt19937 gen(seed); + std::uniform_int_distribution dist(-2000, 2000); + std::vector vec(size); + for (size_t i = 0; i < size; ++i) { + vec[i] = dist(gen); + } + return vec; +} + +std::vector ReadVectorFromFile(const std::string &filename) { + std::vector result; + std::ifstream file(filename); + + if (!file.is_open()) { + return {-999999}; + } + + std::string line; + if (std::getline(file, line)) { + std::istringstream iss(line); + int value = 0; + while (iss >> value) { + result.push_back(value); + } + } + + file.close(); + + if (result.empty()) { + return {-999999}; + } + + return result; +} + +TEST_P(ShakirovaEShellsSortSimpleFuncTests, ShellSortValidation) { + ExecuteTest(GetParam()); +} + +// Встроенные тесты +const std::array kTestVectors = {TestParams{}, + TestParams{42}, + TestParams{9, 7, 5, 3, 1}, + TestParams{10, 20, 30, 40, 50, 60}, + TestParams{7, 7, 7, 7, 7}, + TestParams{-8, -3, -6, -1, -9}, + TestParams{-20, 15, -10, 25, 0, -5, 30}, + TestParams{INT_MAX, 0, INT_MIN}, + GenerateRandomVector(15, 100), + GenerateRandomVector(75, 200), + GenerateRandomVector(150, 300)}; + +// Тесты из файлов +const std::array kFileTestVectors = { + ReadVectorFromFile("data/test_1.txt"), ReadVectorFromFile("data/test_2.txt"), ReadVectorFromFile("data/test_3.txt"), + ReadVectorFromFile("data/test_4.txt")}; + +// Создаем задачи +const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask( + kTestVectors, PPC_SETTINGS_shakirova_e_shells_sort_simple), + ppc::util::AddFuncTask( + kTestVectors, PPC_SETTINGS_shakirova_e_shells_sort_simple), + ppc::util::AddFuncTask( + kFileTestVectors, PPC_SETTINGS_shakirova_e_shells_sort_simple), + ppc::util::AddFuncTask( + kFileTestVectors, PPC_SETTINGS_shakirova_e_shells_sort_simple)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); +const auto kPerfTestName = ShakirovaEShellsSortSimpleFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(ShellSortFunctionalTests, ShakirovaEShellsSortSimpleFuncTests, kGtestValues, kPerfTestName); + +} // namespace +} // namespace shakirova_e_shells_sort_simple diff --git a/tasks/shakirova_e_shells_sort_simple/tests/performance/main.cpp b/tasks/shakirova_e_shells_sort_simple/tests/performance/main.cpp new file mode 100644 index 0000000000..38b681e290 --- /dev/null +++ b/tasks/shakirova_e_shells_sort_simple/tests/performance/main.cpp @@ -0,0 +1,66 @@ +#include +#include + +#include +#include + +#include "shakirova_e_shells_sort_simple/common/include/common.hpp" +#include "shakirova_e_shells_sort_simple/common/include/shell_sort.hpp" +#include "shakirova_e_shells_sort_simple/mpi/include/ops_mpi.hpp" +#include "shakirova_e_shells_sort_simple/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace shakirova_e_shells_sort_simple { + +class ShakirovaEShellsSortSimplePerfTests : public ppc::util::BaseRunPerfTests { + protected: + void SetUp() override { + const int count = 1500000; + input_data_.resize(count); + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution dist(-150000, 150000); + + for (int &val : input_data_) { + val = dist(gen); + } + } + + bool CheckTestOutputData(OutType &output_data) final { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + if (rank != 0) { + return true; + } + + std::vector reference_data = input_data_; + if (!reference_data.empty()) { + ShellSortImpl(reference_data, 0, static_cast(reference_data.size()) - 1); + } + + return output_data == reference_data; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +TEST_P(ShakirovaEShellsSortSimplePerfTests, ExecutePerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_shakirova_e_shells_sort_simple); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); +const auto kPerfTestName = ShakirovaEShellsSortSimplePerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(ShellSortPerformanceTests, ShakirovaEShellsSortSimplePerfTests, kGtestValues, kPerfTestName); + +} // namespace shakirova_e_shells_sort_simple