diff --git a/tasks/kutergin_a_closest_pair/common/include/common.hpp b/tasks/kutergin_a_closest_pair/common/include/common.hpp new file mode 100644 index 0000000000..d3dfb131d2 --- /dev/null +++ b/tasks/kutergin_a_closest_pair/common/include/common.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace kutergin_a_closest_pair { + +using InType = std::vector; +; +using OutType = int; +using TestType = std::tuple, std::string>; +using BaseTask = ppc::task::Task; + +} // namespace kutergin_a_closest_pair diff --git a/tasks/kutergin_a_closest_pair/info.json b/tasks/kutergin_a_closest_pair/info.json new file mode 100644 index 0000000000..1d40028d8a --- /dev/null +++ b/tasks/kutergin_a_closest_pair/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Антон", + "last_name": "Кутергин", + "middle_name": "Андреевич", + "group_number": "3823Б1ФИ1", + "task_number": "7" + } +} diff --git a/tasks/kutergin_a_closest_pair/mpi/include/ops_mpi.hpp b/tasks/kutergin_a_closest_pair/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..099ad9843f --- /dev/null +++ b/tasks/kutergin_a_closest_pair/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "kutergin_a_closest_pair/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace kutergin_a_closest_pair { + +class KuterginAClosestPairMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit KuterginAClosestPairMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace kutergin_a_closest_pair diff --git a/tasks/kutergin_a_closest_pair/mpi/src/ops_mpi.cpp b/tasks/kutergin_a_closest_pair/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..b3bf2dccde --- /dev/null +++ b/tasks/kutergin_a_closest_pair/mpi/src/ops_mpi.cpp @@ -0,0 +1,162 @@ +#include "kutergin_a_closest_pair/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include + +#include "kutergin_a_closest_pair/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace kutergin_a_closest_pair { + +KuterginAClosestPairMPI::KuterginAClosestPairMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = -1; +} + +bool KuterginAClosestPairMPI::ValidationImpl() { + return true; +} + +bool KuterginAClosestPairMPI::PreProcessingImpl() { + return true; +} + +// Вспомогательные функции (не методы класса) +namespace { + +std::vector DistributeData(int rank, int size, int n, const std::vector &v) { + int local_size = n / size; + int remainder = n % size; + + int start = rank * local_size + std::min(rank, remainder); + int end = start + local_size + (rank < remainder ? 1 : 0); + + if (rank == size - 1) { + end = n; + } + + std::vector local_data(end - start); + if (rank == 0) { + std::copy(v.begin() + start, v.begin() + end, local_data.begin()); + + for (int i = 1; i < size; ++i) { + int other_start = i * local_size + std::min(i, remainder); + int other_end = other_start + local_size + (i < remainder ? 1 : 0); + if (i == size - 1) { + other_end = n; + } + + MPI_Send(v.data() + other_start, other_end - other_start, MPI_INT, i, 0, MPI_COMM_WORLD); + } + } else { + MPI_Recv(local_data.data(), static_cast(local_data.size()), MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } + + return local_data; +} + +int FindLocalMin(const std::vector &local_data, int start_idx, int &found_idx) { + int local_min = std::numeric_limits::max(); + found_idx = -1; + + for (int i = 0; i < static_cast(local_data.size()) - 1; ++i) { + int diff = std::abs(local_data[i + 1] - local_data[i]); + if (diff < local_min) { + local_min = diff; + found_idx = start_idx + i; + } + } + + return local_min; +} + +int CalculateStartIndex(int rank, int size, int n) { + int local_size = n / size; + int remainder = n % size; + return rank * local_size + std::min(rank, remainder); +} + +int CalculateEndIndex(int rank, int size, int n) { + int local_size = n / size; + int remainder = n % size; + int end = CalculateStartIndex(rank, size, n) + local_size + (rank < remainder ? 1 : 0); + if (rank == size - 1) { + end = n; + } + return end; +} + +int CheckBoundary(int rank, int size, int end, int n, const std::vector &v, const std::vector &local_data, + int current_min, int ¤t_idx) { + if (rank < size - 1 && end < n) { + int boundary_diff = std::abs(v[end] - local_data.back()); + if (boundary_diff < current_min) { + current_min = boundary_diff; + current_idx = end - 1; + } + } + return current_min; +} + +} // namespace + +bool KuterginAClosestPairMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + const auto &v = GetInput(); + int n = static_cast(v.size()); + + MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (n < 2) { + GetOutput() = -1; + return true; + } + + // Распределение данных + auto local_data = DistributeData(rank, size, n, v); + if (local_data.empty()) { + GetOutput() = -1; + return true; + } + + // Локальный поиск + int start_idx = CalculateStartIndex(rank, size, n); + int local_idx = -1; + int local_min = FindLocalMin(local_data, start_idx, local_idx); + + // Проверка границ + int end = CalculateEndIndex(rank, size, n); + local_min = CheckBoundary(rank, size, end, n, v, local_data, local_min, local_idx); + + // Глобальная редукция + struct MinIndex { + int val = 0; + int idx = -1; + }; + + MinIndex local_result; + local_result.val = local_min; + local_result.idx = local_idx; + + MinIndex global_result; + + MPI_Allreduce(&local_result, &global_result, 1, MPI_2INT, MPI_MINLOC, MPI_COMM_WORLD); + + GetOutput() = global_result.idx; + return true; +} + +bool KuterginAClosestPairMPI::PostProcessingImpl() { + return true; +} + +} // namespace kutergin_a_closest_pair diff --git a/tasks/kutergin_a_closest_pair/report.md b/tasks/kutergin_a_closest_pair/report.md new file mode 100644 index 0000000000..2497c38975 --- /dev/null +++ b/tasks/kutergin_a_closest_pair/report.md @@ -0,0 +1,51 @@ +# Нахождение наиболее близких соседних элементов вектора + +- Student: Кутергин Антон Андреевич, group 3823Б1ФИ1 +- Technology: SEQ | MPI +- Variant: 7 + +## 1 Введение + +Нужно было реализовать параллельный и последовательный алгоритм для поиска наиболее близких соседних элементов вектора + +## 2 Постановка задачи + +На вход подается вектор v, нужно найти такой индекс i, что велечина |v[i] - v[i+1]| минимальна среди всех пар элемента + +## 3. Baseline Algorithm (Sequential) + +Последовательно просматривает пары v[i], v[i+1], сравнивает разности и возвращает индекс максимума + +## 4. Parallelization Scheme + +Вектор разбивается на MPI процессы, корректно обрабатывая границы между процессами, чтобы последнему числу этого процесса и первому следующего процесса также образуют пару. Каждый ищет свой локальный минимум среди всех своих соседних пар. Возвращаем глобальный индекс пары с помощью MPI_Reduce + +## 6. Experimental Setup + +-HARDWARE/OS: CPU - Intel Core i5-8300H, cores/threads - 4/8, RAM - 12gb, OS - Ubuntu 24.04 (DevContainer / WSL 2) +TOOLCHAIN: g++ 13.3.0, build type - Release + + +## 7. Results and Discussion + +### 7.1 Correctness + +Функциональные тесты: вектор может принимать как положительные, так и отрицательные значения или содержать и положительные и отрицательные значения, так же обрабатывается случай, когда вектор пуст или у него одно значение и пару ему не найти. +MPI версия запускалась на 4 ядрах - эталонное ускорение в 4 раза. +Тест на производительность: генерация n чисел и запуск алгоритмов, чтобы узнать время выполнения. + +### 7.2 Performance + +| Размер данных | MPI версия (сек) | SEQ версия (сек) | Ускорение | +|---------------|------------------------|-------------------------|-----------| +| 10,000,000 | 0.0269160954 | 0.0310927963 | 1.22x | +| 100,000,000 | 0.411453807 | 0.5857917309 | 1.39x | + + +## 8. Conclusions + +Алгоритм нахождения наиболее близких соседних элементов вектора работает как последовательно, так и параллельно. Чем больше данных, тем быстрее работает MPI версия алгоритма, но не сильно, т.к. в задаче мы просто проходим по вектору и находим разницу и накладные расходы MPI не особо много выигрывают. + +## 9. References +1. Лекции по параллельному программированию +2. Практические занятия по параллельному программированию \ No newline at end of file diff --git a/tasks/kutergin_a_closest_pair/seq/include/ops_seq.hpp b/tasks/kutergin_a_closest_pair/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..30f1875a23 --- /dev/null +++ b/tasks/kutergin_a_closest_pair/seq/include/ops_seq.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "kutergin_a_closest_pair/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace kutergin_a_closest_pair { + +class KuterginAClosestPairSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit KuterginAClosestPairSEQ(const InType &in); + + private: + std::vector data_; + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace kutergin_a_closest_pair diff --git a/tasks/kutergin_a_closest_pair/seq/src/ops_seq.cpp b/tasks/kutergin_a_closest_pair/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..4ed1efa8e8 --- /dev/null +++ b/tasks/kutergin_a_closest_pair/seq/src/ops_seq.cpp @@ -0,0 +1,55 @@ +#include "kutergin_a_closest_pair/seq/include/ops_seq.hpp" + +#include +#include +#include + +#include "kutergin_a_closest_pair/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace kutergin_a_closest_pair { + +KuterginAClosestPairSEQ::KuterginAClosestPairSEQ(const InType &in) : data_() { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = -1; + data_ = in; +} + +bool KuterginAClosestPairSEQ::ValidationImpl() { + return true; +} + +bool KuterginAClosestPairSEQ::PreProcessingImpl() { + return true; +} + +bool KuterginAClosestPairSEQ::RunImpl() { + const auto &v = GetInput(); + int n = static_cast(v.size()); + + if (n < 2) { + GetOutput() = -1; + return true; + } + + int min_diff = std::numeric_limits::max(); + int best_idx = -1; + + for (int i = 0; i < n - 1; ++i) { + int diff = std::abs(v[i + 1] - v[i]); + if (diff < min_diff) { + min_diff = diff; + best_idx = i; + } + } + + GetOutput() = best_idx; + return true; +} + +bool KuterginAClosestPairSEQ::PostProcessingImpl() { + return true; +} + +} // namespace kutergin_a_closest_pair diff --git a/tasks/kutergin_a_closest_pair/settings.json b/tasks/kutergin_a_closest_pair/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/kutergin_a_closest_pair/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/kutergin_a_closest_pair/tests/.clang-tidy b/tasks/kutergin_a_closest_pair/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/kutergin_a_closest_pair/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/kutergin_a_closest_pair/tests/functional/main.cpp b/tasks/kutergin_a_closest_pair/tests/functional/main.cpp new file mode 100644 index 0000000000..f52887587c --- /dev/null +++ b/tasks/kutergin_a_closest_pair/tests/functional/main.cpp @@ -0,0 +1,85 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kutergin_a_closest_pair/common/include/common.hpp" +#include "kutergin_a_closest_pair/mpi/include/ops_mpi.hpp" +#include "kutergin_a_closest_pair/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" + +namespace kutergin_a_closest_pair { + +class KuterginAClosestPairFuncTests : public ppc::util::BaseRunFuncTests { + public: + KuterginAClosestPairFuncTests() : expected_(0) {} + + static std::string PrintTestParam(const TestType &test_param) { + return std::get<1>(test_param); + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + input_data_ = std::get<0>(std::get<0>(params)); + expected_ = std::get<1>(std::get<0>(params)); + std::cout << "Test: " << std::get<1>(params) << "\nInput: "; + for (const auto &v : input_data_) { + std::cout << v << ' '; + } + std::cout << "\nExpected index: " << expected_ << "\n"; + } + + bool CheckTestOutputData(OutType &output_data) override { + return output_data == expected_; + } + + InType GetTestInputData() override { + return input_data_; + } + + private: + InType input_data_; + OutType expected_; +}; + +namespace { +std::vector v1{1, 3, 5, 6, 9}; // closest pair 5-6 => index 2 +std::vector v2{10, 11, 30, 40}; // 10-11 => index 0 +std::vector v3{5, 2, 3, 10}; // 2-3 => index 1 +std::vector v4{100, 99, 101, 200}; // 100-99 => index 0 +std::vector v5{1}; // no pair => -1 +std::vector v6{}; // no pair => -1 +std::vector v7{4, 4, 4, 4}; // 4-4 => index 0 +std::vector v8{-10, -5, -7, 0}; // closest pair -5,-7 => index 1 +std::vector v9{-1, -2, -8, -10, -11}; // closest pair -1,-2 => index 0 +std::vector v10{-100, 50, -50, 0}; // closest pair 50,-50 => index 1 + +const std::array kTestParam = { + std::make_tuple(std::make_tuple(v1, 2), "test1"), std::make_tuple(std::make_tuple(v2, 0), "test2"), + std::make_tuple(std::make_tuple(v3, 1), "test3"), std::make_tuple(std::make_tuple(v4, 0), "test4"), + std::make_tuple(std::make_tuple(v5, -1), "test5"), std::make_tuple(std::make_tuple(v6, -1), "test6"), + std::make_tuple(std::make_tuple(v7, 0), "test7"), std::make_tuple(std::make_tuple(v8, 1), "test8"), + std::make_tuple(std::make_tuple(v9, 0), "test9"), std::make_tuple(std::make_tuple(v10, 2), "test10")}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kutergin_a_closest_pair), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kutergin_a_closest_pair)); + +const auto kGTestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kNameGen = KuterginAClosestPairFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(ClosestPairTests, KuterginAClosestPairFuncTests, kGTestValues, kNameGen); + +TEST_P(KuterginAClosestPairFuncTests, ClosestPairTest) { + ExecuteTest(GetParam()); +} +} // namespace + +} // namespace kutergin_a_closest_pair diff --git a/tasks/kutergin_a_closest_pair/tests/performance/main.cpp b/tasks/kutergin_a_closest_pair/tests/performance/main.cpp new file mode 100644 index 0000000000..73e5af7526 --- /dev/null +++ b/tasks/kutergin_a_closest_pair/tests/performance/main.cpp @@ -0,0 +1,51 @@ +#include + +#include +#include +#include + +#include "kutergin_a_closest_pair/common/include/common.hpp" +#include "kutergin_a_closest_pair/mpi/include/ops_mpi.hpp" +#include "kutergin_a_closest_pair/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace kutergin_a_closest_pair { + +class KuterginAClosestPairPerfTests : public ppc::util::BaseRunPerfTests { + public: + void SetUp() override { + const size_t vector_size = 100000000; + std::vector vector(vector_size); + + for (size_t i = 0; i < vector_size; i++) { + vector[i] = static_cast(i % 1000) + ((i & 1) == 0 ? 1 : -1); + } + input_data_ = std::move(vector); + } + + bool CheckTestOutputData(OutType &output_data) final { + return output_data == 0; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +TEST_P(KuterginAClosestPairPerfTests, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_kutergin_a_closest_pair); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = KuterginAClosestPairPerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, KuterginAClosestPairPerfTests, kGtestValues, kPerfTestName); + +} // namespace kutergin_a_closest_pair