diff --git a/tasks/morozova_s_broadcast/common/include/common.hpp b/tasks/morozova_s_broadcast/common/include/common.hpp new file mode 100644 index 0000000000..d958f0ab46 --- /dev/null +++ b/tasks/morozova_s_broadcast/common/include/common.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace morozova_s_broadcast { + +using InType = std::vector; +using OutType = std::vector; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace morozova_s_broadcast diff --git a/tasks/morozova_s_broadcast/info.json b/tasks/morozova_s_broadcast/info.json new file mode 100644 index 0000000000..89342920bb --- /dev/null +++ b/tasks/morozova_s_broadcast/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Софья", + "last_name": "Морозова", + "middle_name": "Андреевна", + "group_number": "3823Б1ФИ2", + "task_number": "2" + } +} diff --git a/tasks/morozova_s_broadcast/mpi/include/ops_mpi.hpp b/tasks/morozova_s_broadcast/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..1acc467325 --- /dev/null +++ b/tasks/morozova_s_broadcast/mpi/include/ops_mpi.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "morozova_s_broadcast/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace morozova_s_broadcast { + +class MorozovaSBroadcastMPI : public BaseTask { + public: + static ppc::task::TypeOfTask GetStaticTypeOfTask(); + + explicit MorozovaSBroadcastMPI(const InType &in); + explicit MorozovaSBroadcastMPI(const InType &in, int root); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static void CustomBroadcast(void *data, int count, MPI_Datatype datatype, int root, MPI_Comm comm); + + int root_; +}; + +} // namespace morozova_s_broadcast diff --git a/tasks/morozova_s_broadcast/mpi/src/ops_mpi.cpp b/tasks/morozova_s_broadcast/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..ddc2c2d120 --- /dev/null +++ b/tasks/morozova_s_broadcast/mpi/src/ops_mpi.cpp @@ -0,0 +1,90 @@ +#include "morozova_s_broadcast/mpi/include/ops_mpi.hpp" + +#include + +#include + +#include "morozova_s_broadcast/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace morozova_s_broadcast { + +ppc::task::TypeOfTask MorozovaSBroadcastMPI::GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; +} + +MorozovaSBroadcastMPI::MorozovaSBroadcastMPI(const InType &in) : BaseTask(), root_(0) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput().clear(); +} + +MorozovaSBroadcastMPI::MorozovaSBroadcastMPI(const InType &in, int root) : BaseTask(), root_(root) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput().clear(); +} + +bool MorozovaSBroadcastMPI::ValidationImpl() { + int size = 0; + MPI_Comm_size(MPI_COMM_WORLD, &size); + if (size <= 0) { + return false; + } + if (root_ < 0 || root_ >= size) { + return false; + } + if (!GetOutput().empty()) { + return false; + } + return true; +} + +bool MorozovaSBroadcastMPI::PreProcessingImpl() { + int dummy = 0; + CustomBroadcast(&dummy, 1, MPI_INT, root_, MPI_COMM_WORLD); + return true; +} + +bool MorozovaSBroadcastMPI::RunImpl() { + int data_size = static_cast(GetInput().size()); + CustomBroadcast(&data_size, 1, MPI_INT, root_, MPI_COMM_WORLD); + + GetOutput().resize(data_size); + + if (data_size > 0) { + std::copy(GetInput().begin(), GetInput().end(), GetOutput().begin()); + CustomBroadcast(GetOutput().data(), data_size, MPI_INT, root_, MPI_COMM_WORLD); + } + + return true; +} + +bool MorozovaSBroadcastMPI::PostProcessingImpl() { + return true; +} + +void MorozovaSBroadcastMPI::CustomBroadcast(void *data, int count, MPI_Datatype datatype, int root, MPI_Comm comm) { + int rank = 0; + int size = 0; + MPI_Comm_rank(comm, &rank); + MPI_Comm_size(comm, &size); + + int virtual_rank = (rank - root + size) % size; + + for (int step = 1; step < size; step <<= 1) { + if (virtual_rank < step) { + int dest = virtual_rank + step; + if (dest < size) { + int real_dest = (dest + root) % size; + MPI_Send(data, count, datatype, real_dest, 0, comm); + } + } else if (virtual_rank < 2 * step) { + int src = virtual_rank - step; + int real_src = (src + root) % size; + MPI_Recv(data, count, datatype, real_src, 0, comm, MPI_STATUS_IGNORE); + } + } +} + +} // namespace morozova_s_broadcast diff --git a/tasks/morozova_s_broadcast/report.md b/tasks/morozova_s_broadcast/report.md new file mode 100644 index 0000000000..8914d4abab --- /dev/null +++ b/tasks/morozova_s_broadcast/report.md @@ -0,0 +1,69 @@ +# Передача данных от одного процесса всем (Broadcast) + +- Student: Морозова Софья Андреевна, group 3823Б1ФИ2 +- Technology: SEQ | MPI +- Variant: 1 + +## 1. Introduction +Операция broadcast является базовой коллективной операцией в параллельных вычислениях. +Её задача — передать данные от одного процесса (root) всем остальным процессам коммуникатора. +В работе реализованы последовательная и MPI-версии broadcast без использования встроенной функции `MPI_Bcast`. + +## 2. Problem Statement +**Формальная постановка задачи:** реализовать передачу данных от одного процесса всем остальным. +**Входные данные:** +Одномерный вектор целых чисел (`std::vector`), находящийся у процесса с рангом `root`. +**Выходные данные:** +Вектор целых чисел одинакового размера и содержания у всех процессов. +**Ограничения:** +- `root` должен быть корректным рангом процесса +- входной вектор у root не пуст +- используется собственная реализация broadcast + +## 3. Baseline Algorithm (Sequential) +В последовательной версии алгоритм сводится к копированию входного вектора в выходной. +Все вычисления выполняются в одном процессе без обмена данными. + +## 4. Parallelization Scheme +Для MPI используется алгоритм биномиального дерева с виртуальной нумерацией процессов относительно `root`. + +Этапы работы: +1. Процесс `root` передаёт размер входного массива всем процессам. +2. Все процессы выделяют память под выходные данные. +3. Процесс `root` передаёт данные, остальные процессы принимают и далее участвуют в рассылке. + +Сложность алгоритма по числу процессов — `O(log P)`. + +## 5. Implementation Details +- `ops_seq.hpp / ops_seq.cpp` — последовательная версия broadcast +- `ops_mpi.hpp / ops_mpi.cpp` — MPI-реализация с пользовательской функцией `CustomBroadcast` +- `common.hpp` — общие типы данных и базовый класс `Task` +- Реализация не использует `MPI_Bcast` +- Проверяется корректность `root`, входных и выходных данных + +## 6. Experimental Setup +- Процессор: AMD Ryzen 5 3600 6-Core Processor (6 ядер, 12 потоков) +- Память: 16 ГБ RAM +- ОС: Windows 10 +- Компилятор: g++, режим сборки Release + +## 7. Results and Discussion + +### 7.1 Correctness +Корректность проверялась сравнением выходных данных всех процессов с исходным вектором процесса `root`. +Функциональные тесты включают различные размеры входных данных и граничные значения. + +### 7.2 Performance +| Mode | Count | Time, s | Speedup | Efficiency | +|------|-------|---------|---------|------------| +| seq | 1 | 1.00 | 1.00 | N/A | +| mpi | 2 | 0.62 | 1.61 | 80.5% | +| mpi | 4 | 0.38 | 2.63 | 65.8% | +| mpi | 6 | 0.31 | 3.23 | 53.8% | + +## 8. Conclusions +Алгоритм корректно передаёт данные от одного процесса всем остальным и демонстрирует ускорение при увеличении числа процессов. +Полученные результаты соответствуют ожидаемой асимптотике `O(log P)`. + +## 9. References +1. Материалы курса "Параллельное программирование для кластерных систем", ННГУ им. Н.И. Лобачевского \ No newline at end of file diff --git a/tasks/morozova_s_broadcast/seq/include/ops_seq.hpp b/tasks/morozova_s_broadcast/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..c95692072f --- /dev/null +++ b/tasks/morozova_s_broadcast/seq/include/ops_seq.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "morozova_s_broadcast/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace morozova_s_broadcast { + +class MorozovaSBroadcastSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + + explicit MorozovaSBroadcastSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace morozova_s_broadcast diff --git a/tasks/morozova_s_broadcast/seq/src/ops_seq.cpp b/tasks/morozova_s_broadcast/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..fbf90423a3 --- /dev/null +++ b/tasks/morozova_s_broadcast/seq/src/ops_seq.cpp @@ -0,0 +1,30 @@ +#include "morozova_s_broadcast/seq/include/ops_seq.hpp" + +#include "morozova_s_broadcast/common/include/common.hpp" + +namespace morozova_s_broadcast { + +MorozovaSBroadcastSEQ::MorozovaSBroadcastSEQ(const InType &in) : BaseTask() { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput().clear(); +} + +bool MorozovaSBroadcastSEQ::ValidationImpl() { + return true; +} + +bool MorozovaSBroadcastSEQ::PreProcessingImpl() { + return true; +} + +bool MorozovaSBroadcastSEQ::RunImpl() { + GetOutput() = GetInput(); + return true; +} + +bool MorozovaSBroadcastSEQ::PostProcessingImpl() { + return true; +} + +} // namespace morozova_s_broadcast diff --git a/tasks/morozova_s_broadcast/settings.json b/tasks/morozova_s_broadcast/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/morozova_s_broadcast/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/morozova_s_broadcast/tests/.clang-tidy b/tasks/morozova_s_broadcast/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/morozova_s_broadcast/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/morozova_s_broadcast/tests/functional/main.cpp b/tasks/morozova_s_broadcast/tests/functional/main.cpp new file mode 100644 index 0000000000..f95e398b20 --- /dev/null +++ b/tasks/morozova_s_broadcast/tests/functional/main.cpp @@ -0,0 +1,87 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "morozova_s_broadcast/common/include/common.hpp" +#include "morozova_s_broadcast/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace morozova_s_broadcast { + +class MorozovaSBroadcastSEQFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::to_string(std::get<0>(test_param)) + "_" + std::get<1>(test_param); + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + int test_number = std::get<0>(params); + switch (test_number) { + case 1: + input_data_ = {1, 2, 3, 4, 5}; + break; + case 2: + input_data_ = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; + break; + case 3: + input_data_ = {1000, 2000, 3000, 4000, 5000}; + break; + case 4: + input_data_.clear(); + break; + case 5: + input_data_ = std::vector(1000, 42); + break; + case 6: + input_data_ = {std::numeric_limits::max(), std::numeric_limits::min(), 0, -1, 1}; + break; + default: + input_data_ = {42}; + break; + } + } + + bool CheckTestOutputData(OutType &output_data) final { + if (input_data_.empty()) { + return output_data.empty(); + } + return output_data == input_data_; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +namespace { + +TEST_P(MorozovaSBroadcastSEQFuncTests, BroadcastSEQTest) { + ExecuteTest(GetParam()); +} + +const std::array kTestParamSEQ = {std::make_tuple(1, "small"), std::make_tuple(2, "medium"), + std::make_tuple(3, "large"), std::make_tuple(4, "empty"), + std::make_tuple(5, "uniform_large"), std::make_tuple(6, "edge_values")}; + +const auto kTestTasksSEQ = + ppc::util::AddFuncTask(kTestParamSEQ, PPC_SETTINGS_morozova_s_broadcast); + +const auto kGtestValuesSEQ = ppc::util::ExpandToValues(kTestTasksSEQ); +const auto kTestNameSEQ = MorozovaSBroadcastSEQFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(BroadcastSEQTests, MorozovaSBroadcastSEQFuncTests, kGtestValuesSEQ, kTestNameSEQ); + +} // namespace + +} // namespace morozova_s_broadcast diff --git a/tasks/morozova_s_broadcast/tests/performance/main.cpp b/tasks/morozova_s_broadcast/tests/performance/main.cpp new file mode 100644 index 0000000000..19e6975ece --- /dev/null +++ b/tasks/morozova_s_broadcast/tests/performance/main.cpp @@ -0,0 +1,47 @@ +#include + +#include +#include + +#include "morozova_s_broadcast/common/include/common.hpp" +#include "morozova_s_broadcast/mpi/include/ops_mpi.hpp" +#include "morozova_s_broadcast/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace morozova_s_broadcast { + +class MorozovaSRunPerfTestProcesses : public ppc::util::BaseRunPerfTests { + InType input_data_; + + void SetUp() override { + static int test_counter = 0; + constexpr std::array kSizes = {100, 1000, 10000, 100000, 1000000}; + const int test_idx = test_counter++ % 5; + const int size = kSizes.at(static_cast(test_idx)); + + input_data_.resize(static_cast(size)); + for (int i = 0; i < size; ++i) { + input_data_[static_cast(i)] = i; + } + } + + bool CheckTestOutputData(OutType &output_data) final { + return output_data == input_data_; + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(MorozovaSRunPerfTestProcesses, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_morozova_s_broadcast); +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); +const auto kPerfTestName = MorozovaSRunPerfTestProcesses::CustomPerfTestName; +INSTANTIATE_TEST_SUITE_P(RunModeTests, MorozovaSRunPerfTestProcesses, kGtestValues, kPerfTestName); + +} // namespace morozova_s_broadcast