diff --git a/tasks/khruev_a_min_elem_vec/common/include/common.hpp b/tasks/khruev_a_min_elem_vec/common/include/common.hpp new file mode 100644 index 0000000000..6861f4556a --- /dev/null +++ b/tasks/khruev_a_min_elem_vec/common/include/common.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace khruev_a_min_elem_vec { + +using InType = std::vector; +using OutType = int; +using TestType = std::tuple, int>, std::string>; // meta information +using BaseTask = ppc::task::Task; + +} // namespace khruev_a_min_elem_vec diff --git a/tasks/khruev_a_min_elem_vec/info.json b/tasks/khruev_a_min_elem_vec/info.json new file mode 100644 index 0000000000..3416ff83c9 --- /dev/null +++ b/tasks/khruev_a_min_elem_vec/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Антон", + "last_name": "Хруев", + "middle_name": "Олегович", + "group_number": "3823Б1ФИ1", + "task_number": "1" + } +} diff --git a/tasks/khruev_a_min_elem_vec/mpi/include/ops_mpi.hpp b/tasks/khruev_a_min_elem_vec/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..7cf906454f --- /dev/null +++ b/tasks/khruev_a_min_elem_vec/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "khruev_a_min_elem_vec/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace khruev_a_min_elem_vec { + +class KhruevAMinElemVecMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit KhruevAMinElemVecMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace khruev_a_min_elem_vec diff --git a/tasks/khruev_a_min_elem_vec/mpi/src/ops_mpi.cpp b/tasks/khruev_a_min_elem_vec/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..36ff2fe59e --- /dev/null +++ b/tasks/khruev_a_min_elem_vec/mpi/src/ops_mpi.cpp @@ -0,0 +1,74 @@ +#include "khruev_a_min_elem_vec/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include + +#include "khruev_a_min_elem_vec/common/include/common.hpp" + +namespace khruev_a_min_elem_vec { + +KhruevAMinElemVecMPI::KhruevAMinElemVecMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); // mpi scoreboard + GetInput() = in; // dannie doljna bit vidna vsem func rodytelya and stabilizaciya + GetOutput() = 0; +} + +bool KhruevAMinElemVecMPI::ValidationImpl() { // input check + return GetOutput() == 0; +} + +bool KhruevAMinElemVecMPI::PreProcessingImpl() { + return true; +} + +bool KhruevAMinElemVecMPI::RunImpl() { + const auto &input = GetInput(); + if (input.empty()) { + GetOutput() = INT_MAX; + return true; + } + + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + int n = static_cast(input.size()); + int int_part = n / size; + int remainder = n % size; + + std::vector sendcounts(size); + std::vector displs(size); + + for (int i = 0; i < size; ++i) { + sendcounts[i] = int_part + (i < remainder ? 1 : 0); + displs[i] = (i == 0 ? 0 : displs[i - 1] + sendcounts[i - 1]); + } + + std::vector local_chunk(sendcounts[rank] > 0 ? sendcounts[rank] : 1); + MPI_Scatterv(input.data(), sendcounts.data(), displs.data(), MPI_INT, + sendcounts[rank] > 0 ? local_chunk.data() : nullptr, sendcounts[rank], MPI_INT, 0, MPI_COMM_WORLD); + + int local_min = INT_MAX; + if (sendcounts[rank] > 0) { + local_min = *std::min_element(local_chunk.begin(), local_chunk.begin() + sendcounts[rank]); + } + + int global_min = 0; + + MPI_Reduce(&local_min, &global_min, 1, MPI_INT, MPI_MIN, 0, MPI_COMM_WORLD); + MPI_Bcast(&global_min, 1, MPI_INT, 0, MPI_COMM_WORLD); + + GetOutput() = global_min; + + return true; +} + +bool KhruevAMinElemVecMPI::PostProcessingImpl() { + return true; +} + +} // namespace khruev_a_min_elem_vec diff --git a/tasks/khruev_a_min_elem_vec/report.md b/tasks/khruev_a_min_elem_vec/report.md new file mode 100644 index 0000000000..509755cbeb --- /dev/null +++ b/tasks/khruev_a_min_elem_vec/report.md @@ -0,0 +1,50 @@ +# Минимальное значение элементов вектора + +- Student: Хруев Антон Олегович, group 3823Б1ФИ1 +- Technology: SEQ | MPI +- Variant: 4 + +## 1. Введение +В этом задании нужно было реализовать последовательный и параллельный алгоритмы поиска минимального элемента списка. + +## 2. Постановка задачи +Есть вектор $V$ состоящий из $n$ элементов, необходимо найти наименьший его элемент $a$. + + +## 3. Baseline Algorithm (Sequential) +Последовательно в цикле перебираем элементы вектора $V$, сравнивая минимум с текущим элементом и если элемент меньше, то обновляем минимум. После $n$ итераций гарантировано находим $a$. + +## 4. Parallelization Scheme +- Разделим вектор $V$ на $k$ частей, где $k$ - количество процессов. Каждому процессу отправим данные из 0-го с помощью функции ScatterV. Предварительно определяется какую часть вектора возьмет каждый из процессов. Для этого инициализируется 2 вектора: **sendcounts**, который определяет сколько элементов получит процесс с индексом $i$ (это количество определяется с учетом остатка $n$ % $k$ = $r$, который распределяется равномерно между процессами с рангом меньшим $r$); **displs**, определяющий смещение. После нахождения локальных минимумов, вызовем функцию MPI_Reduce, которая найдет минимум среди локальных и отправит результат 0-му процессу. + +## 5. Experimental Setup +- Hardware/OS: CPU model - AMD Ryzen 3 7320U, cores/threads - 4/2, RAM=8GB, OS version - Ubuntu 24.04.2 LTS +- Toolchain: compiler - g++, version - g++ 13.3.0, build type - Release + + + +## 6. Результаты + +### 6.1 Корректность +**Функциональные тесты.** $V$ может состоять из: положительных, отрицательных, одинаковых чисел. +Так же может содержать один или несколько отрицательных чисел среди положительных или не содержать чисел вовсе. +Параллельная реализация алгоритма запускалась на 4 ядрах, поэтому эталонное усокрение - 4 раза. +**Тест на производительность.** Генерация n чисел в порядке возрастания. + +### 6.2 Производительность + +| Размер данных (n) | SEQ версия (с) | MPI версия (с) | Эффективность | +|-------------|-------|---------|---------| +| 100 | 0.000005 | 0.000225 | 0.02× | +| 10000 | 0.000115 | 0.0003031 | 0.37× | +| 1000000 | 0.011989 | 0.0142523 | 0.83× | +| 100000000| 1.08006 | 1.30234 | 0.83× | + +## 7. Заключение +Результатом работы стали реализации последовательных и параллельных алгоритмов нахождения минимального элемента $a$ вектора $V$. Можно заметить, что SEQ версия работает быстрее MPI. Это происходит из-за большого количества накладных расходов при вызовах MPI_Scatterv, MPI_Reduce, MPI_Bcast, и малого количества ядер процессора. + +## 8. Ссылки +1. Лекции по параллельному программированию +2. Практические занятия по параллельному программированию + + \ No newline at end of file diff --git a/tasks/khruev_a_min_elem_vec/seq/include/ops_seq.hpp b/tasks/khruev_a_min_elem_vec/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..52494cf6af --- /dev/null +++ b/tasks/khruev_a_min_elem_vec/seq/include/ops_seq.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "khruev_a_min_elem_vec/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace khruev_a_min_elem_vec { + +class KhruevAMinElemVecSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit KhruevAMinElemVecSEQ(const InType &in); + + private: + std::vector data_; + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace khruev_a_min_elem_vec diff --git a/tasks/khruev_a_min_elem_vec/seq/src/ops_seq.cpp b/tasks/khruev_a_min_elem_vec/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..0a551bc74c --- /dev/null +++ b/tasks/khruev_a_min_elem_vec/seq/src/ops_seq.cpp @@ -0,0 +1,45 @@ +#include "khruev_a_min_elem_vec/seq/include/ops_seq.hpp" + +#include +#include +#include +#include + +#include "khruev_a_min_elem_vec/common/include/common.hpp" + +namespace khruev_a_min_elem_vec { + +KhruevAMinElemVecSEQ::KhruevAMinElemVecSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool KhruevAMinElemVecSEQ::ValidationImpl() { + return (GetOutput() == 0); +} + +bool KhruevAMinElemVecSEQ::PreProcessingImpl() { + return true; +} + +bool KhruevAMinElemVecSEQ::RunImpl() { + if (GetInput().empty()) { + GetOutput() = INT_MAX; + return true; + } + int mininmum = GetInput()[0]; + size_t vec_size = GetInput().size(); + for (size_t i = 1; i < vec_size; i++) { + mininmum = std::min(GetInput()[i], mininmum); + } + GetOutput() = mininmum; + + return true; +} + +bool KhruevAMinElemVecSEQ::PostProcessingImpl() { + return true; +} + +} // namespace khruev_a_min_elem_vec diff --git a/tasks/khruev_a_min_elem_vec/settings.json b/tasks/khruev_a_min_elem_vec/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/khruev_a_min_elem_vec/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/khruev_a_min_elem_vec/tests/.clang-tidy b/tasks/khruev_a_min_elem_vec/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/khruev_a_min_elem_vec/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/khruev_a_min_elem_vec/tests/functional/main.cpp b/tasks/khruev_a_min_elem_vec/tests/functional/main.cpp new file mode 100644 index 0000000000..8e9d23913d --- /dev/null +++ b/tasks/khruev_a_min_elem_vec/tests/functional/main.cpp @@ -0,0 +1,101 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "khruev_a_min_elem_vec/common/include/common.hpp" +#include "khruev_a_min_elem_vec/mpi/include/ops_mpi.hpp" +#include "khruev_a_min_elem_vec/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace khruev_a_min_elem_vec { + +class KhruevAMinElemVecFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::get<1>(test_param); + } + + protected: + void SetUp() override { + // size_t size = 10; + // for (size_t i = 1; i <= size; i++) { + // input_data_.push_back(i); + // } + // expected_ = 1; + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + std::cout << "SetUp for " << std::get<1>(params) << '\n'; + input_data_ = std::vector(std::get<0>(std::get<0>(params))); + std::cout << "We set input data: \n"; + for (auto x : input_data_) { + std::cout << x << ' '; + } + std::cout << '\n'; + // expected_ = std::get<1>(std::get<0>(params)); + std::cout << "We set expected result: " << std::get<1>(std::get<0>(params)) << '\n'; + } + + bool CheckTestOutputData(OutType &output_data) final { + std::cout << "CheckTestOutputData for " + << std::get<1>(std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam())) + << '\n'; + return (std::get<1>(std::get<0>(std::get(ppc::util::GTestParamIndex::kTestParams)>( + GetParam()))) == output_data); + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; + // OutType expected_; +}; + +namespace { + +TEST_P(KhruevAMinElemVecFuncTests, MinElemVecc) { + ExecuteTest(GetParam()); +} +std::vector instanse1{1, 2, 3, 4, 5, 6}; +std::vector instanse2{2, 2, -13, 4, 1, 5, 6}; +std::vector instanse3{-100, 2, 30, 4, 12, 6, 1, 6, 2}; +std::vector instanse4{1, 2, 3, 4, 5, 6, 9, 1, 1, 0}; +std::vector instanse5{1, 2, 3, 4}; +std::vector instanse6{2, 5, 2, 0, -10}; +std::vector instanse7{1, 2}; +std::vector instanse8{-1, -2, -8, -10, -11}; +std::vector instanse9{1, 1, 1, 1, 1}; +std::vector instanse10{}; + +const std::array kTestParam = {std::make_tuple(std::make_tuple(instanse1, 1), "test1"), + std::make_tuple(std::make_tuple(instanse2, -13), "test2"), + std::make_tuple(std::make_tuple(instanse3, -100), "test3"), + std::make_tuple(std::make_tuple(instanse4, 0), "test4"), + std::make_tuple(std::make_tuple(instanse5, 1), "test5"), + std::make_tuple(std::make_tuple(instanse6, -10), "test6"), + std::make_tuple(std::make_tuple(instanse7, 1), "test7"), + std::make_tuple(std::make_tuple(instanse8, -11), "test8"), + std::make_tuple(std::make_tuple(instanse9, 1), "test9"), + std::make_tuple(std::make_tuple(instanse10, INT_MAX), "test10")}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_khruev_a_min_elem_vec), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_khruev_a_min_elem_vec)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = KhruevAMinElemVecFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(MinElemVec, KhruevAMinElemVecFuncTests, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace khruev_a_min_elem_vec diff --git a/tasks/khruev_a_min_elem_vec/tests/performance/main.cpp b/tasks/khruev_a_min_elem_vec/tests/performance/main.cpp new file mode 100644 index 0000000000..892fcaa179 --- /dev/null +++ b/tasks/khruev_a_min_elem_vec/tests/performance/main.cpp @@ -0,0 +1,47 @@ +#include + +#include + +#include "khruev_a_min_elem_vec/common/include/common.hpp" +#include "khruev_a_min_elem_vec/mpi/include/ops_mpi.hpp" +#include "khruev_a_min_elem_vec/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace khruev_a_min_elem_vec { + +class KhruevAMinElemVecPerfTests : public ppc::util::BaseRunPerfTests { + const int kCount_ = 100000000; + InType input_data_; + // OutType expected_; + + void SetUp() override { + size_t size = kCount_; + for (size_t i = size; i >= 1; i--) { + input_data_.push_back(static_cast(i)); + } + // expected_ = 1; + } + + bool CheckTestOutputData(OutType &output_data) final { + return 1 == output_data; + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(KhruevAMinElemVecPerfTests, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks(PPC_SETTINGS_khruev_a_min_elem_vec); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = KhruevAMinElemVecPerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunPerf, KhruevAMinElemVecPerfTests, kGtestValues, kPerfTestName); + +} // namespace khruev_a_min_elem_vec