diff --git a/tasks/kurpiakov_a_elem_vec_sum/common/include/common.hpp b/tasks/kurpiakov_a_elem_vec_sum/common/include/common.hpp new file mode 100644 index 0000000000..119e075061 --- /dev/null +++ b/tasks/kurpiakov_a_elem_vec_sum/common/include/common.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include +#include + +#include "task/include/task.hpp" + +namespace kurpiakov_a_elem_vec_sum { +using InType = std::tuple>; +using OutType = int64_t; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace kurpiakov_a_elem_vec_sum diff --git a/tasks/kurpiakov_a_elem_vec_sum/info.json b/tasks/kurpiakov_a_elem_vec_sum/info.json new file mode 100644 index 0000000000..16af473fa9 --- /dev/null +++ b/tasks/kurpiakov_a_elem_vec_sum/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Алексей", + "last_name": "Курпяков", + "middle_name": "Георгиевич", + "group_number": "3823Б1ФИ3", + "task_number": "1" + } +} diff --git a/tasks/kurpiakov_a_elem_vec_sum/mpi/include/ops_mpi.hpp b/tasks/kurpiakov_a_elem_vec_sum/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..930963e476 --- /dev/null +++ b/tasks/kurpiakov_a_elem_vec_sum/mpi/include/ops_mpi.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "kurpiakov_a_elem_vec_sum/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace kurpiakov_a_elem_vec_sum { +class KurpiakovAElemVecSumMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit KurpiakovAElemVecSumMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace kurpiakov_a_elem_vec_sum diff --git a/tasks/kurpiakov_a_elem_vec_sum/mpi/src/ops_mpi.cpp b/tasks/kurpiakov_a_elem_vec_sum/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..63d0c54bc3 --- /dev/null +++ b/tasks/kurpiakov_a_elem_vec_sum/mpi/src/ops_mpi.cpp @@ -0,0 +1,88 @@ +#include "kurpiakov_a_elem_vec_sum/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include + +#include "kurpiakov_a_elem_vec_sum/common/include/common.hpp" + +namespace kurpiakov_a_elem_vec_sum { + +KurpiakovAElemVecSumMPI::KurpiakovAElemVecSumMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool KurpiakovAElemVecSumMPI::ValidationImpl() { + bool res = (GetOutput() == 0) && (std::cmp_equal((std::get<1>(GetInput()).size()), std::get<0>(GetInput()))); + return res; +} + +bool KurpiakovAElemVecSumMPI::PreProcessingImpl() { + GetOutput() = 0; + return true; +} + +bool KurpiakovAElemVecSumMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + int total_size = 0; + if (rank == 0) { + total_size = std::get<0>(GetInput()); + } + + MPI_Bcast(&total_size, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (total_size == 0) { + GetOutput() = 0LL; + return true; + } + + std::vector batch(size); + std::vector displs(size); + int batch_size = total_size / size; + int remainder = total_size % size; + for (int i = 0; i < size; ++i) { + batch[i] = batch_size + (i < remainder ? 1 : 0); + displs[i] = (i == 0) ? 0 : displs[i - 1] + batch[i - 1]; + } + + int local_size = batch[rank]; + std::vector local_data(local_size); + + int *sendbuf = nullptr; + if (rank == 0) { + sendbuf = const_cast(std::get<1>(GetInput()).data()); + } + + MPI_Scatterv(sendbuf, batch.data(), displs.data(), MPI_INT, local_data.data(), local_size, MPI_INT, 0, + MPI_COMM_WORLD); + + OutType local_sum = 0LL; + for (int i = 0; i < local_size; ++i) { + local_sum += static_cast(local_data[i]); + } + + OutType global_sum = 0LL; + MPI_Reduce(&local_sum, &global_sum, 1, MPI_LONG_LONG, MPI_SUM, 0, MPI_COMM_WORLD); + + MPI_Bcast(&global_sum, 1, MPI_LONG_LONG, 0, MPI_COMM_WORLD); + + GetOutput() = global_sum; + + MPI_Barrier(MPI_COMM_WORLD); + + return true; +} + +bool KurpiakovAElemVecSumMPI::PostProcessingImpl() { + return true; +} + +} // namespace kurpiakov_a_elem_vec_sum diff --git a/tasks/kurpiakov_a_elem_vec_sum/report.md b/tasks/kurpiakov_a_elem_vec_sum/report.md new file mode 100644 index 0000000000..b2a7d7ce0b --- /dev/null +++ b/tasks/kurpiakov_a_elem_vec_sum/report.md @@ -0,0 +1,79 @@ +# Сумма элементов вектора + +- Студент: Курпяков Алексей Георгиевич, 3823Б1ФИ3 +- Технологии: SEQ | MPI +- Вариант: 1 + +## 1. Введение +В рамках данной работы был реализован алгоритм суммирования элементов вектора: последовательна и параллельная реализация. + +## 2. Постановка задачи +Дан вектор v из N элементов. Необходимо найти найти сумму всех его элементов. +Рассмотрим элементы v = {1, 2, 3 ... N} +Решением задачи будет нахождения суммы: sum = v[0] + v[1] + ... + v[N - 1] + +## 3. Описание линейного алгоритма + +Последовательная версия: +проходимся по всему массиву, собираем префикс сумму по нему. + + +## 4. Описание схемы параллельного алгоритма +Пусть у нас K процессов, вектор размера N, тогда алгоритм такой: +Процессы делят массив на M частей. +Части выделяются следующим образом: +Сначала считаем минимум, который будет каждый процесс обрабатывать: +batch = N/K +Затем вычисляем остаток от деления: +remainder = M % N +Первые remainder процессов заберут по 1 элементу из массива, остальные отработают только свой пакет. +Затем 0-ой процесс рассылает каждому потоку свой участок памяти и его размер остальным процессам. +После того как каждый процесс отработал свой участок и посчитал сумму внутри своего вектора 0-ой процесс собрает результаты и суммирует их. + +## 5. Окружение +- Hardware/OS: CPU - AMD Rizen 7 7840HS, cores - 8, RAM - 16GB, Ubuntu 24 on WSL +- Toolchain: GCC 13, Release +- Environment: PPC_NUM_PROC = 8 +- Data: |v| = 10000000, v[i] = i + +## 6. Результаты экспериментов и выводы + +Функциональные тесты содержат 9 тестов: + - Тест1: пустой вектор + - Тест2: одиночный элемент + - Тест3: положительные числа в векторе + - Тест4: вектор состоящий из нулей + - Тест5: значения с разными знаками + - Тест6: значения граничащие с переполнением типа int + - Тест7: переполнение типа int + - Тест8: только отрицательные числа + - Тест9: вектор четной длины, такой что для любого v[i] существует v[j] = -v[i] (i != j) + +Тест на производительность - |v| = {5000000, 7500000 10000000}, v[i] = i + +Ниже таблица с результатами Perf тестов. + +В таблице представлено: n - размер вектора, время выполнения SEQ и MPI версии для 4-х процессов (в милисекундах): + +| Размер данных (n) | SEQ версия (ms)| MPI версия (ms) | Ускорение | +|-------------------|----------------|------------------|----------| +| 5 000 000 | 96 | 114 | 0,84 | +| 7 500 000 | 173,5 | 183 | 0,94 | +| 10 000 000 | 372 | 219 | 1,69 | + +*результат был получен при запуске build/bin/ppc_perf_tests* + +При замерах была получена данная таблица. +Мнjю не было получено ожидаемое ускорение в 8 раз. +Это нормально, так как чтобы появился рабочий процесс нужно выполнить команду операционной системы для его создания, что занимает много времени в масштабах задачи суммирования элементов вектора. +Операция суммирования отлично векторизуется и выполняется за 1 тик процессора на современных процессорах с архитектурой x86.Так же много времени забирает операция раздачи элементов вектора каждому процессу и рассчет смещений и размеров пераедаваемых пакетов. Поэтому ожидаемо что прирост скорости начнет проявлятся при действительно больших данных. + +## 7. Заключение +В результате проделанной работы были реализованы версии MPI, SEQ алгоритма нахождения суммы элементов вектора. +Было так же показано, что MPI версия работает быстрее SEQ при больших данных, а при небольших - наоборот. + + +## Источники +1. [cppreference.com](https://en.cppreference.com/) +2. [стандарт с++](https://www.open-std.org) +3. [Документация OpenMPI](https://www.open-mpi.org/doc/) diff --git a/tasks/kurpiakov_a_elem_vec_sum/seq/include/ops_seq.hpp b/tasks/kurpiakov_a_elem_vec_sum/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..9f59a291da --- /dev/null +++ b/tasks/kurpiakov_a_elem_vec_sum/seq/include/ops_seq.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "kurpiakov_a_elem_vec_sum/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace kurpiakov_a_elem_vec_sum { +class KurpiakovAElemVecSumSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit KurpiakovAElemVecSumSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace kurpiakov_a_elem_vec_sum diff --git a/tasks/kurpiakov_a_elem_vec_sum/seq/src/ops_seq.cpp b/tasks/kurpiakov_a_elem_vec_sum/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..45ef88ea19 --- /dev/null +++ b/tasks/kurpiakov_a_elem_vec_sum/seq/src/ops_seq.cpp @@ -0,0 +1,39 @@ +#include "kurpiakov_a_elem_vec_sum/seq/include/ops_seq.hpp" + +#include +#include + +#include "kurpiakov_a_elem_vec_sum/common/include/common.hpp" + +namespace kurpiakov_a_elem_vec_sum { +KurpiakovAElemVecSumSEQ::KurpiakovAElemVecSumSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool KurpiakovAElemVecSumSEQ::ValidationImpl() { + bool res = (GetOutput() == 0) && (std::cmp_equal((std::get<1>(GetInput()).size()), std::get<0>(GetInput()))); + return res; +} + +bool KurpiakovAElemVecSumSEQ::PreProcessingImpl() { + GetOutput() = 0; + return true; +} + +bool KurpiakovAElemVecSumSEQ::RunImpl() { + std::vector vec = std::get<1>(GetInput()); + OutType res = 0LL; + for (const int &it : vec) { + res += static_cast(it); + } + GetOutput() = res; + return true; +} + +bool KurpiakovAElemVecSumSEQ::PostProcessingImpl() { + return true; +} + +} // namespace kurpiakov_a_elem_vec_sum diff --git a/tasks/kurpiakov_a_elem_vec_sum/settings.json b/tasks/kurpiakov_a_elem_vec_sum/settings.json new file mode 100644 index 0000000000..7d2c35b298 --- /dev/null +++ b/tasks/kurpiakov_a_elem_vec_sum/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/kurpiakov_a_elem_vec_sum/tests/functional/main.cpp b/tasks/kurpiakov_a_elem_vec_sum/tests/functional/main.cpp new file mode 100644 index 0000000000..2cbae588a6 --- /dev/null +++ b/tasks/kurpiakov_a_elem_vec_sum/tests/functional/main.cpp @@ -0,0 +1,75 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kurpiakov_a_elem_vec_sum/common/include/common.hpp" +#include "kurpiakov_a_elem_vec_sum/mpi/include/ops_mpi.hpp" +#include "kurpiakov_a_elem_vec_sum/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace kurpiakov_a_elem_vec_sum { +class KurpiakovAElemVecSumFuncTest : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::get<1>(test_param); + } + + protected: + void SetUp() override { + TestType param = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + input_data_ = std::get<0>(param); + expected_data_ = std::get<2>(param); + } + + bool CheckTestOutputData(OutType &output_data) final { + return (output_data == expected_data_); + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_{0, {}}; + OutType expected_data_{0}; +}; + +namespace { +TEST_P(KurpiakovAElemVecSumFuncTest, ElemVecSum) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + std::make_tuple(std::make_tuple(0, std::vector{}), "test1_empty", 0LL), + std::make_tuple(std::make_tuple(1, std::vector{5}), "test2_single", 5LL), + std::make_tuple(std::make_tuple(3, std::vector{1, 2, 3}), "test3_positive", 6LL), + std::make_tuple(std::make_tuple(3, std::vector{0, 0, 0}), "test4_zeros", 0LL), + std::make_tuple(std::make_tuple(3, std::vector{1, -2, 3}), "test5_mixed", 2LL), + std::make_tuple(std::make_tuple(2, std::vector{2147483646, 1}), "test6_border", 2147483647LL), + std::make_tuple(std::make_tuple(2, std::vector{2147483646, 2147483647}), "test7_overflow", 4294967293LL), + std::make_tuple(std::make_tuple(3, std::vector{-1, -2, -3}), "test8_negative", -6LL), + std::make_tuple(std::make_tuple(4, std::vector{1, -1, 2, -2}), "test9_alternating", 0LL)}; + +const auto kTestTasksList = + std::tuple_cat(ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_kurpiakov_a_elem_vec_sum), + ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_kurpiakov_a_elem_vec_sum)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = KurpiakovAElemVecSumFuncTest::PrintFuncTestName; + +// NOLINTNEXTLINE +INSTANTIATE_TEST_SUITE_P(KurpiakovAVec, KurpiakovAElemVecSumFuncTest, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace kurpiakov_a_elem_vec_sum diff --git a/tasks/kurpiakov_a_elem_vec_sum/tests/performance/main.cpp b/tasks/kurpiakov_a_elem_vec_sum/tests/performance/main.cpp new file mode 100644 index 0000000000..50e07fd266 --- /dev/null +++ b/tasks/kurpiakov_a_elem_vec_sum/tests/performance/main.cpp @@ -0,0 +1,57 @@ +#include + +#include +#include +#include + +#include "kurpiakov_a_elem_vec_sum/common/include/common.hpp" +#include "kurpiakov_a_elem_vec_sum/mpi/include/ops_mpi.hpp" +#include "kurpiakov_a_elem_vec_sum/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace kurpiakov_a_elem_vec_sum { + +class KurpiakovAElemVecSumPerfTests : public ppc::util::BaseRunPerfTests { + InType input_data_{0, {}}; + OutType expected_data_{0}; + + void SetUp() override { + const int vector_size = 10'000'000; + std::vector input(vector_size); + + for (int i = 0; i < vector_size; i++) { + input[i] = i + 1; + } + + expected_data_ = static_cast(vector_size) * (vector_size + 1) / 2; + input_data_ = InType(vector_size, input); + } + + bool CheckTestOutputData(OutType &output_data) final { + return (output_data == expected_data_); + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +namespace { + +TEST_P(KurpiakovAElemVecSumPerfTests, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_kurpiakov_a_elem_vec_sum); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = KurpiakovAElemVecSumPerfTests::CustomPerfTestName; + +// NOLINTNEXTLINE +INSTANTIATE_TEST_SUITE_P(KurpiakovAVecPerf, KurpiakovAElemVecSumPerfTests, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace kurpiakov_a_elem_vec_sum