diff --git a/tasks/dergynov_s_hypercube/common/include/common.hpp b/tasks/dergynov_s_hypercube/common/include/common.hpp new file mode 100644 index 0000000000..63f541383b --- /dev/null +++ b/tasks/dergynov_s_hypercube/common/include/common.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace dergynov_s_hypercube { + +using InType = std::vector; // {source, dest, data_size} +using OutType = int; // сумма элементов +using TestType = std::tuple, std::string>; +using BaseTask = ppc::task::Task; + +} // namespace dergynov_s_hypercube diff --git a/tasks/dergynov_s_hypercube/info.json b/tasks/dergynov_s_hypercube/info.json new file mode 100644 index 0000000000..2e5f4aa98b --- /dev/null +++ b/tasks/dergynov_s_hypercube/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "first_name_p", + "last_name": "last_name_p", + "middle_name": "middle_name_p", + "group_number": "2222222_p", + "task_number": "1" + } +} diff --git a/tasks/dergynov_s_hypercube/mpi/include/ops_mpi.hpp b/tasks/dergynov_s_hypercube/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..e4ca7339f2 --- /dev/null +++ b/tasks/dergynov_s_hypercube/mpi/include/ops_mpi.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "dergynov_s_hypercube/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace dergynov_s_hypercube { + +class DergynovSHypercubeMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + + explicit DergynovSHypercubeMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static int CalcDim(int size); + static std::vector BuildPath(int src, int dst, int dim); + static void FindPos(int rank, const std::vector &path, int &pos, int &next, int &prev); + + static void SendVec(const std::vector &data, int to); + static void RecvVec(std::vector &data, int from); + static void BusyWork(int iters); +}; + +} // namespace dergynov_s_hypercube diff --git a/tasks/dergynov_s_hypercube/mpi/src/ops_mpi.cpp b/tasks/dergynov_s_hypercube/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..bea45c4d8c --- /dev/null +++ b/tasks/dergynov_s_hypercube/mpi/src/ops_mpi.cpp @@ -0,0 +1,173 @@ +#include "tasks/dergynov_s_hypercube/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include + +#include "dergynov_s_hypercube/common/include/common.hpp" +#include "tasks/dergynov_s_hypercube/common/include/common.hpp" + +namespace dergynov_s_hypercube { + +DergynovSHypercubeMPI::DergynovSHypercubeMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +int DergynovSHypercubeMPI::CalcDim(int size) { + int dim = 0; + int cap = 1; + while (cap < size) { + cap <<= 1; + dim++; + } + return dim; +} + +std::vector DergynovSHypercubeMPI::BuildPath(int src, int dst, int dim) { + std::vector path; + int cur = src; + int diff = src ^ dst; + path.push_back(cur); + + for (int dim_idx = 0; dim_idx < dim; ++dim_idx) { + if ((diff & (1 << dim_idx)) != 0) { + cur ^= (1 << dim_idx); + path.push_back(cur); + if (cur == dst) { + break; + } + } + } + return path; +} + +void DergynovSHypercubeMPI::FindPos(int rank, const std::vector &path, int &pos, int &next, int &prev) { + pos = -1; + next = -1; + prev = -1; + for (std::size_t idx = 0; idx < path.size(); ++idx) { + if (path[idx] == rank) { + pos = static_cast(idx); + if (idx > 0) { + prev = path[idx - 1]; + } + if (idx + 1 < path.size()) { + next = path[idx + 1]; + } + break; + } + } +} + +void DergynovSHypercubeMPI::SendVec(const std::vector &data, int to) { + int sz = static_cast(data.size()); + MPI_Send(&sz, 1, MPI_INT, to, 0, MPI_COMM_WORLD); + if (sz > 0) { + MPI_Send(data.data(), sz, MPI_INT, to, 1, MPI_COMM_WORLD); + } +} + +void DergynovSHypercubeMPI::RecvVec(std::vector &data, int from) { + int sz = 0; + MPI_Recv(&sz, 1, MPI_INT, from, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + data.resize(sz); + if (sz > 0) { + MPI_Recv(data.data(), sz, MPI_INT, from, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } +} + +void DergynovSHypercubeMPI::BusyWork(int iters) { + volatile int x = 0; + for (int i = 0; i < iters; ++i) { + x = (x * 31 + i) % 1000003; + } + (void)x; +} + +bool DergynovSHypercubeMPI::ValidationImpl() { + int size = 0; + MPI_Comm_size(MPI_COMM_WORLD, &size); + auto &in = GetInput(); + if (in[0] < 0 || in[0] >= size) { + in[0] = 0; + } + if (in[1] < 0 || in[1] >= size) { + in[1] = size - 1; + } + return true; +} + +bool DergynovSHypercubeMPI::PreProcessingImpl() { + GetOutput() = 0; + return true; +} + +bool DergynovSHypercubeMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + const auto &in = GetInput(); + int src = in[0]; + int dst = in[1]; + int data_size = in[2]; + + std::vector data; + if (rank == src) { + data.resize(data_size, 1); + } + + if (src == dst) { + MPI_Bcast(&data_size, 1, MPI_INT, dst, MPI_COMM_WORLD); + if (rank != dst) { + data.resize(data_size); + } + MPI_Bcast(data.data(), data_size, MPI_INT, dst, MPI_COMM_WORLD); + GetOutput() = std::accumulate(data.begin(), data.end(), 0); + return true; + } + + int dim = CalcDim(size); + auto path = BuildPath(src, dst, dim); + + int pos = -1; + int next = -1; + int prev = -1; + FindPos(rank, path, pos, next, prev); + + if (pos != -1) { + if (rank == src) { + BusyWork(120000); + SendVec(data, next); + } else if (rank == dst) { + RecvVec(data, prev); + BusyWork(120000); + } else { + RecvVec(data, prev); + BusyWork(120000); + SendVec(data, next); + } + } + + int final_size = static_cast(data.size()); + MPI_Bcast(&final_size, 1, MPI_INT, dst, MPI_COMM_WORLD); + if (pos == -1) { + data.resize(final_size); + } + MPI_Bcast(data.data(), final_size, MPI_INT, dst, MPI_COMM_WORLD); + + GetOutput() = std::accumulate(data.begin(), data.end(), 0); + MPI_Barrier(MPI_COMM_WORLD); + return true; +} + +bool DergynovSHypercubeMPI::PostProcessingImpl() { + return true; +} + +} // namespace dergynov_s_hypercube diff --git a/tasks/dergynov_s_hypercube/my_task_backup/common/include/common.hpp b/tasks/dergynov_s_hypercube/my_task_backup/common/include/common.hpp new file mode 100644 index 0000000000..63f541383b --- /dev/null +++ b/tasks/dergynov_s_hypercube/my_task_backup/common/include/common.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace dergynov_s_hypercube { + +using InType = std::vector; // {source, dest, data_size} +using OutType = int; // сумма элементов +using TestType = std::tuple, std::string>; +using BaseTask = ppc::task::Task; + +} // namespace dergynov_s_hypercube diff --git a/tasks/dergynov_s_hypercube/my_task_backup/info.json b/tasks/dergynov_s_hypercube/my_task_backup/info.json new file mode 100644 index 0000000000..de9442bb99 --- /dev/null +++ b/tasks/dergynov_s_hypercube/my_task_backup/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "first_name_p", + "last_name": "last_name_p", + "middle_name": "middle_name_p", + "group_number": "2222222_p", + "task_number": "1" + } +} diff --git a/tasks/dergynov_s_hypercube/my_task_backup/mpi/include/ops_mpi.hpp b/tasks/dergynov_s_hypercube/my_task_backup/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..e4ca7339f2 --- /dev/null +++ b/tasks/dergynov_s_hypercube/my_task_backup/mpi/include/ops_mpi.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "dergynov_s_hypercube/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace dergynov_s_hypercube { + +class DergynovSHypercubeMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + + explicit DergynovSHypercubeMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static int CalcDim(int size); + static std::vector BuildPath(int src, int dst, int dim); + static void FindPos(int rank, const std::vector &path, int &pos, int &next, int &prev); + + static void SendVec(const std::vector &data, int to); + static void RecvVec(std::vector &data, int from); + static void BusyWork(int iters); +}; + +} // namespace dergynov_s_hypercube diff --git a/tasks/dergynov_s_hypercube/my_task_backup/mpi/src/ops_mpi.cpp b/tasks/dergynov_s_hypercube/my_task_backup/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..bea45c4d8c --- /dev/null +++ b/tasks/dergynov_s_hypercube/my_task_backup/mpi/src/ops_mpi.cpp @@ -0,0 +1,173 @@ +#include "tasks/dergynov_s_hypercube/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include + +#include "dergynov_s_hypercube/common/include/common.hpp" +#include "tasks/dergynov_s_hypercube/common/include/common.hpp" + +namespace dergynov_s_hypercube { + +DergynovSHypercubeMPI::DergynovSHypercubeMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +int DergynovSHypercubeMPI::CalcDim(int size) { + int dim = 0; + int cap = 1; + while (cap < size) { + cap <<= 1; + dim++; + } + return dim; +} + +std::vector DergynovSHypercubeMPI::BuildPath(int src, int dst, int dim) { + std::vector path; + int cur = src; + int diff = src ^ dst; + path.push_back(cur); + + for (int dim_idx = 0; dim_idx < dim; ++dim_idx) { + if ((diff & (1 << dim_idx)) != 0) { + cur ^= (1 << dim_idx); + path.push_back(cur); + if (cur == dst) { + break; + } + } + } + return path; +} + +void DergynovSHypercubeMPI::FindPos(int rank, const std::vector &path, int &pos, int &next, int &prev) { + pos = -1; + next = -1; + prev = -1; + for (std::size_t idx = 0; idx < path.size(); ++idx) { + if (path[idx] == rank) { + pos = static_cast(idx); + if (idx > 0) { + prev = path[idx - 1]; + } + if (idx + 1 < path.size()) { + next = path[idx + 1]; + } + break; + } + } +} + +void DergynovSHypercubeMPI::SendVec(const std::vector &data, int to) { + int sz = static_cast(data.size()); + MPI_Send(&sz, 1, MPI_INT, to, 0, MPI_COMM_WORLD); + if (sz > 0) { + MPI_Send(data.data(), sz, MPI_INT, to, 1, MPI_COMM_WORLD); + } +} + +void DergynovSHypercubeMPI::RecvVec(std::vector &data, int from) { + int sz = 0; + MPI_Recv(&sz, 1, MPI_INT, from, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + data.resize(sz); + if (sz > 0) { + MPI_Recv(data.data(), sz, MPI_INT, from, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } +} + +void DergynovSHypercubeMPI::BusyWork(int iters) { + volatile int x = 0; + for (int i = 0; i < iters; ++i) { + x = (x * 31 + i) % 1000003; + } + (void)x; +} + +bool DergynovSHypercubeMPI::ValidationImpl() { + int size = 0; + MPI_Comm_size(MPI_COMM_WORLD, &size); + auto &in = GetInput(); + if (in[0] < 0 || in[0] >= size) { + in[0] = 0; + } + if (in[1] < 0 || in[1] >= size) { + in[1] = size - 1; + } + return true; +} + +bool DergynovSHypercubeMPI::PreProcessingImpl() { + GetOutput() = 0; + return true; +} + +bool DergynovSHypercubeMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + const auto &in = GetInput(); + int src = in[0]; + int dst = in[1]; + int data_size = in[2]; + + std::vector data; + if (rank == src) { + data.resize(data_size, 1); + } + + if (src == dst) { + MPI_Bcast(&data_size, 1, MPI_INT, dst, MPI_COMM_WORLD); + if (rank != dst) { + data.resize(data_size); + } + MPI_Bcast(data.data(), data_size, MPI_INT, dst, MPI_COMM_WORLD); + GetOutput() = std::accumulate(data.begin(), data.end(), 0); + return true; + } + + int dim = CalcDim(size); + auto path = BuildPath(src, dst, dim); + + int pos = -1; + int next = -1; + int prev = -1; + FindPos(rank, path, pos, next, prev); + + if (pos != -1) { + if (rank == src) { + BusyWork(120000); + SendVec(data, next); + } else if (rank == dst) { + RecvVec(data, prev); + BusyWork(120000); + } else { + RecvVec(data, prev); + BusyWork(120000); + SendVec(data, next); + } + } + + int final_size = static_cast(data.size()); + MPI_Bcast(&final_size, 1, MPI_INT, dst, MPI_COMM_WORLD); + if (pos == -1) { + data.resize(final_size); + } + MPI_Bcast(data.data(), final_size, MPI_INT, dst, MPI_COMM_WORLD); + + GetOutput() = std::accumulate(data.begin(), data.end(), 0); + MPI_Barrier(MPI_COMM_WORLD); + return true; +} + +bool DergynovSHypercubeMPI::PostProcessingImpl() { + return true; +} + +} // namespace dergynov_s_hypercube diff --git a/tasks/dergynov_s_hypercube/my_task_backup/report.md b/tasks/dergynov_s_hypercube/my_task_backup/report.md new file mode 100644 index 0000000000..9295976383 --- /dev/null +++ b/tasks/dergynov_s_hypercube/my_task_backup/report.md @@ -0,0 +1,276 @@ +# Топологии сетей передачи данных. Гиперкуб + +* Студент: Дергунов Сергей Антонович +* Группа: 3823Б1ПР4 +* Вариант: 10 +* Технологии: SEQ, MPI + +--- + +## 1. Введение + +Параллельное программирование широко используется при разработке приложений, требующих высокой производительности и интенсивного обмена данными между процессами. +В данной работе рассматривается практическая реализация передачи сообщений в среде MPI с использованием топологии гиперкуба. + +Цель данной работы - реализовать передачу данных между процессами по топологии гиперкуба без использования встроенных функций MPI для создания топологий (`MPI_Cart_create`, `MPI_Graph_create`), используя только базовые операции обмена (`MPI_Send`, `MPI_Recv`). + +Основные задачи: +1. Реализовать вычисление пути передачи данных между произвольными процессами. +2. Смоделировать передачу данных по гиперкубу. +3. Оценить корректность работы и производительность MPI-версии. + +--- + +## 2. Постановка задачи + +### Входные данные (InType): +```cpp +struct HypercubeMessage { + int source; // процесс-отправитель + int dest; // процесс-получатель + int data_size; // количество элементов для генерации и передачи +}; +``` + +### Выходные данные (OutType): +* `data` — сумма элементов вектора, полученного процессом-получателем. + +### Ограничения: +* Количество процессов `world_size` может не быть степенью двойки. +* Процессы `source` и `dest` должны быть в диапазоне `0 <= source < world_size`. + +Суммирование элементов используется как простая вычислительная операция, позволяющая проверить корректность передачи данных и целостность полученного сообщения. + +--- + +## 3. Базовый алгоритм (вычисление пути) + +### 3.1. Структура гиперкуба + +**Для 3-мерного гиперкуба:** + +```text + 110───────111 + ╱│ ╱│ + ╱ │ ╱ │ + 010─┼─────011 │ + │ 100─────│─101 + │ ╱ │ ╱ + 000───────001 +``` + +Каждый процесс соединён с соседями, отличающимися на один бит. + +### 3.2. Алгоритм передачи + +1. Определение размерности гиперкуба по числу процессов (`CalculateHypercubeDimension`). +2. Вычисление кратчайшего пути между `source` и `dest` с помощью XOR-битовой операции (`CalcPath`). +3. Последовательная передача данных по пути (`MPI_Send/MPI_Recv`). +4. Процесс-получатель рассылает результат всем процессам (`MPI_Bcast`). + +Такой подход позволяет на каждом шаге изменять ровно один бит номера процесса, что соответствует переходу к соседу в гиперкубе и гарантирует минимальную длину маршрута. + +**Пример функции расчёта пути:** + +```cpp +std::vector DergynovSHypercube::CalcPath(int source, int dest, int dimensions) { + std::vector path; + int current = source; + path.push_back(current); + int xor_val = source ^ dest; + for (int dim = 0; dim < dimensions; dim++) { + int mask = 1 << dim; + if ((xor_val & mask) != 0) { + current ^= mask; + path.push_back(current); + if (current == dest) break; + } + } + return path; +} +``` + +--- + +## 4. Схема распараллеливания + +**Роли процессов:** +* **Source** — генерирует данные и инициирует передачу. +* **Промежуточные узлы** — пересылают данные следующему процессу. +* **Dest** — получает данные, суммирует элементы, рассылает всем. +* **Остальные** — получают результат через `MPI_Bcast`. + +**Коммуникация:** +* Каждому процессу известен свой ранг и количество процессов. +* Передача строго по рассчитанному пути. +* Используются только `MPI_Send` и `MPI_Recv`. + +Описанная схема напрямую отражена в методе `RunImpl`, где для каждого процесса определяется его позиция в маршруте и соответствующая роль в передаче данных. + +```cpp +bool DergynovSHypercubeMPI::RunImpl() { + int rank = 0, size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + const auto &in = GetInput(); + int src = in[0]; + int dst = in[1]; + int data_size = in[2]; + + std::vector data; + if (rank == src) { + data.resize(data_size, 1); + } + + if (src == dst) { + MPI_Bcast(&data_size, 1, MPI_INT, dst, MPI_COMM_WORLD); + if (rank != dst) { + data.resize(data_size); + } + MPI_Bcast(data.data(), data_size, MPI_INT, dst, MPI_COMM_WORLD); + GetOutput() = std::accumulate(data.begin(), data.end(), 0); + return true; + } + + int dim = CalcDim(size); + auto path = BuildPath(src, dst, dim); + + int pos, next, prev; + FindPos(rank, path, pos, next, prev); + + if (pos != -1) { + if (rank == src) { + BusyWork(120000); + SendVec(data, next); + } else if (rank == dst) { + RecvVec(data, prev); + BusyWork(120000); + } else { + RecvVec(data, prev); + BusyWork(120000); + SendVec(data, next); + } + } + + int final_size = static_cast(data.size()); + MPI_Bcast(&final_size, 1, MPI_INT, dst, MPI_COMM_WORLD); + if (pos == -1) { + data.resize(final_size); + } + MPI_Bcast(data.data(), final_size, MPI_INT, dst, MPI_COMM_WORLD); + + GetOutput() = std::accumulate(data.begin(), data.end(), 0); + MPI_Barrier(MPI_COMM_WORLD); + return true; +} +``` + +--- + +## 5. Реализация + +```cpp +bool DergynovSHypercube::RunImpl() { + int rank, size; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + auto input = GetInput(); + int source = input[0], dest = input[1]; + std::vector data(input[2], 1); + + int dimensions = CalculateHypercubeDimension(size); + std::vector path = CalcPath(source, dest, dimensions); + + int my_pos = -1, prev_neighbor = -1, next_neighbor = -1; + CalcPositions(rank, path, my_pos, next_neighbor, prev_neighbor); + + if (my_pos != -1) { + if (rank == source) SendData(data, next_neighbor); + else if (rank == dest) { RecvData(data, prev_neighbor); PerformComputeLoad(150000); } + else { RecvData(data, prev_neighbor); PerformComputeLoad(150000); SendData(data, next_neighbor); } + } + + MPI_Bcast(data.data(), data.size(), MPI_INT, dest, MPI_COMM_WORLD); + GetOutput() = std::accumulate(data.begin(), data.end(), 0); + MPI_Barrier(MPI_COMM_WORLD); + return true; +} +``` + +В приведённом фрагменте показана основная логика работы MPI-версии: определение маршрута, приём и передача данных между соседними узлами, а также финальная рассылка результата всем процессам. +Вспомогательные функции инкапсулируют детали обмена и вычислительной нагрузки. +Для каждого узла выполняется `PerformComputeLoad(150000)`, чтобы оценить влияние вычислений на время передачи. + +--- + +## 6. Экспериментальная часть + +### Аппаратная конфигурация + +* Docker-контейнер (Linux) +* GCC +* OpenMPI +* Режим сборки: Release + +### Команды запуска + +``` +mpirun --allow-run-as-root -n 1 ./build/bin/ppc_perf_tests +mpirun --allow-run-as-root -n 2 ./build/bin/ppc_perf_tests +mpirun --allow-run-as-root -n 4 ./build/bin/ppc_perf_tests +``` + +--- + +## 7. Результаты + +### 7.1 Корректность + +Функциональные тесты пройдены для всех сценариев: +* соседние и удалённые процессы +* передача самому себе +* пустой массив +* обратная передача + +Все процессы получают корректную сумму элементов. + +### 7.2. Производительность + +| Процессы | Время выполнения (с) | Шаги передачи | +| -------- | -------------------- | ------------- | +| 2 | 0.0015 | 1 | +| 4 | 0.0016 | 2 | +| 8 | 0.0017 | 3 | +| 16 | 0.0017 | 4 | +| 32 | 0.0023 | 5 | + +Комментарий: +* Время растёт с числом процессов из-за увеличения длины пути. +* Основное время тратится на имитацию вычислительной нагрузки. +* MPI-версия эффективна, накладные расходы минимальны. + +Полученные результаты соответствуют теоретическим ожиданиям для гиперкуба: при увеличении числа процессов растёт размерность топологии и, как следствие, число шагов передачи. Это подтверждает корректность вычисления маршрута. + +--- + +## 8. Заключение + +* Реализована корректная передача данных по топологии гиперкуба. +* MPI-версия не использует встроенные функции топологий. +* Функциональные тесты подтверждают корректность работы. +* Производительность остаётся высокой даже при увеличении числа процессов. + +Дальнейшие улучшения: +* Увеличение объёма данных для более точной оценки масштабируемости. +* Тестирование на реальном кластере с большим числом узлов. + +--- + +## 9. Литература + +1. Gropp W., Lusk E., Skjellum A. Using MPI +2. MPI Standard Documentation +3. Документация GoogleTest +4. Сысоев А. В. Лекции по параллельному программированию diff --git a/tasks/dergynov_s_hypercube/my_task_backup/seq/include/ops_seq.hpp b/tasks/dergynov_s_hypercube/my_task_backup/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..1301320ad8 --- /dev/null +++ b/tasks/dergynov_s_hypercube/my_task_backup/seq/include/ops_seq.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "dergynov_s_hypercube/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace dergynov_s_hypercube { + +class DergynovSHypercubeSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + + explicit DergynovSHypercubeSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace dergynov_s_hypercube diff --git a/tasks/dergynov_s_hypercube/my_task_backup/seq/src/ops_seq.cpp b/tasks/dergynov_s_hypercube/my_task_backup/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..500751a01f --- /dev/null +++ b/tasks/dergynov_s_hypercube/my_task_backup/seq/src/ops_seq.cpp @@ -0,0 +1,30 @@ +#include "dergynov_s_hypercube/seq/include/ops_seq.hpp" + +#include "dergynov_s_hypercube/common/include/common.hpp" + +namespace dergynov_s_hypercube { + +DergynovSHypercubeSEQ::DergynovSHypercubeSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool DergynovSHypercubeSEQ::ValidationImpl() { + return true; +} + +bool DergynovSHypercubeSEQ::PreProcessingImpl() { + return true; +} + +bool DergynovSHypercubeSEQ::RunImpl() { + GetOutput() = GetInput()[2]; + return true; +} + +bool DergynovSHypercubeSEQ::PostProcessingImpl() { + return true; +} + +} // namespace dergynov_s_hypercube diff --git a/tasks/dergynov_s_hypercube/my_task_backup/settings.json b/tasks/dergynov_s_hypercube/my_task_backup/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/dergynov_s_hypercube/my_task_backup/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/dergynov_s_hypercube/my_task_backup/tests/.clang-tidy b/tasks/dergynov_s_hypercube/my_task_backup/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/dergynov_s_hypercube/my_task_backup/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/dergynov_s_hypercube/my_task_backup/tests/functional/main.cpp b/tasks/dergynov_s_hypercube/my_task_backup/tests/functional/main.cpp new file mode 100644 index 0000000000..c334595b4f --- /dev/null +++ b/tasks/dergynov_s_hypercube/my_task_backup/tests/functional/main.cpp @@ -0,0 +1,64 @@ +#include + +#include +#include +#include +#include +#include + +#include "dergynov_s_hypercube/common/include/common.hpp" +#include "dergynov_s_hypercube/mpi/include/ops_mpi.hpp" +#include "dergynov_s_hypercube/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace dergynov_s_hypercube { + +class DergynovSHypercubeFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::get<1>(test_param); + } + + protected: + void SetUp() override { + auto tt = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + input_ = std::get<0>(tt); + } + + bool CheckTestOutputData(OutType &out) final { + return out == input_[2]; + } + + InType GetTestInputData() final { + return input_; + } + + private: + InType input_; +}; + +namespace { + +TEST_P(DergynovSHypercubeFuncTests, Run) { + ExecuteTest(GetParam()); +} + +const std::array kTests = { + std::make_tuple(std::vector{0, 3, 5}, "far"), std::make_tuple(std::vector{0, 1, 5}, "neighbor"), + std::make_tuple(std::vector{2, 3, 5}, "mid"), std::make_tuple(std::vector{1, 1, 5}, "self"), + std::make_tuple(std::vector{3, 0, 5}, "reverse"), std::make_tuple(std::vector{0, 3, 0}, "empty"), +}; + +const auto kTasks = + std::tuple_cat(ppc::util::AddFuncTask(kTests, PPC_SETTINGS_dergynov_s_hypercube), + ppc::util::AddFuncTask(kTests, PPC_SETTINGS_dergynov_s_hypercube)); + +const auto kValues = ppc::util::ExpandToValues(kTasks); + +INSTANTIATE_TEST_SUITE_P(HypercubeTests, DergynovSHypercubeFuncTests, kValues, + DergynovSHypercubeFuncTests::PrintFuncTestName); + +} // namespace + +} // namespace dergynov_s_hypercube diff --git a/tasks/dergynov_s_hypercube/my_task_backup/tests/performance/main.cpp b/tasks/dergynov_s_hypercube/my_task_backup/tests/performance/main.cpp new file mode 100644 index 0000000000..7a8b47bcaf --- /dev/null +++ b/tasks/dergynov_s_hypercube/my_task_backup/tests/performance/main.cpp @@ -0,0 +1,44 @@ +#include +#include + +#include "dergynov_s_hypercube/common/include/common.hpp" +#include "dergynov_s_hypercube/mpi/include/ops_mpi.hpp" +#include "dergynov_s_hypercube/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace dergynov_s_hypercube { + +class DergynovSHypercubePerfTests : public ppc::util::BaseRunPerfTests { + protected: + void SetUp() override { + int size = 0; + MPI_Comm_size(MPI_COMM_WORLD, &size); + input_ = {0, size - 1, kDataSize_}; + } + + bool CheckTestOutputData(OutType &out) final { + return out == kDataSize_; + } + + InType GetTestInputData() final { + return input_; + } + + private: + const int kDataSize_ = 400; + InType input_; +}; + +TEST_P(DergynovSHypercubePerfTests, RunPerf) { + ExecuteTest(GetParam()); +} + +const auto kAllTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_dergynov_s_hypercube); + +const auto kValues = ppc::util::TupleToGTestValues(kAllTasks); + +INSTANTIATE_TEST_SUITE_P(PerfTests, DergynovSHypercubePerfTests, kValues, + DergynovSHypercubePerfTests::CustomPerfTestName); + +} // namespace dergynov_s_hypercube diff --git a/tasks/dergynov_s_hypercube/report.md b/tasks/dergynov_s_hypercube/report.md new file mode 100644 index 0000000000..9295976383 --- /dev/null +++ b/tasks/dergynov_s_hypercube/report.md @@ -0,0 +1,276 @@ +# Топологии сетей передачи данных. Гиперкуб + +* Студент: Дергунов Сергей Антонович +* Группа: 3823Б1ПР4 +* Вариант: 10 +* Технологии: SEQ, MPI + +--- + +## 1. Введение + +Параллельное программирование широко используется при разработке приложений, требующих высокой производительности и интенсивного обмена данными между процессами. +В данной работе рассматривается практическая реализация передачи сообщений в среде MPI с использованием топологии гиперкуба. + +Цель данной работы - реализовать передачу данных между процессами по топологии гиперкуба без использования встроенных функций MPI для создания топологий (`MPI_Cart_create`, `MPI_Graph_create`), используя только базовые операции обмена (`MPI_Send`, `MPI_Recv`). + +Основные задачи: +1. Реализовать вычисление пути передачи данных между произвольными процессами. +2. Смоделировать передачу данных по гиперкубу. +3. Оценить корректность работы и производительность MPI-версии. + +--- + +## 2. Постановка задачи + +### Входные данные (InType): +```cpp +struct HypercubeMessage { + int source; // процесс-отправитель + int dest; // процесс-получатель + int data_size; // количество элементов для генерации и передачи +}; +``` + +### Выходные данные (OutType): +* `data` — сумма элементов вектора, полученного процессом-получателем. + +### Ограничения: +* Количество процессов `world_size` может не быть степенью двойки. +* Процессы `source` и `dest` должны быть в диапазоне `0 <= source < world_size`. + +Суммирование элементов используется как простая вычислительная операция, позволяющая проверить корректность передачи данных и целостность полученного сообщения. + +--- + +## 3. Базовый алгоритм (вычисление пути) + +### 3.1. Структура гиперкуба + +**Для 3-мерного гиперкуба:** + +```text + 110───────111 + ╱│ ╱│ + ╱ │ ╱ │ + 010─┼─────011 │ + │ 100─────│─101 + │ ╱ │ ╱ + 000───────001 +``` + +Каждый процесс соединён с соседями, отличающимися на один бит. + +### 3.2. Алгоритм передачи + +1. Определение размерности гиперкуба по числу процессов (`CalculateHypercubeDimension`). +2. Вычисление кратчайшего пути между `source` и `dest` с помощью XOR-битовой операции (`CalcPath`). +3. Последовательная передача данных по пути (`MPI_Send/MPI_Recv`). +4. Процесс-получатель рассылает результат всем процессам (`MPI_Bcast`). + +Такой подход позволяет на каждом шаге изменять ровно один бит номера процесса, что соответствует переходу к соседу в гиперкубе и гарантирует минимальную длину маршрута. + +**Пример функции расчёта пути:** + +```cpp +std::vector DergynovSHypercube::CalcPath(int source, int dest, int dimensions) { + std::vector path; + int current = source; + path.push_back(current); + int xor_val = source ^ dest; + for (int dim = 0; dim < dimensions; dim++) { + int mask = 1 << dim; + if ((xor_val & mask) != 0) { + current ^= mask; + path.push_back(current); + if (current == dest) break; + } + } + return path; +} +``` + +--- + +## 4. Схема распараллеливания + +**Роли процессов:** +* **Source** — генерирует данные и инициирует передачу. +* **Промежуточные узлы** — пересылают данные следующему процессу. +* **Dest** — получает данные, суммирует элементы, рассылает всем. +* **Остальные** — получают результат через `MPI_Bcast`. + +**Коммуникация:** +* Каждому процессу известен свой ранг и количество процессов. +* Передача строго по рассчитанному пути. +* Используются только `MPI_Send` и `MPI_Recv`. + +Описанная схема напрямую отражена в методе `RunImpl`, где для каждого процесса определяется его позиция в маршруте и соответствующая роль в передаче данных. + +```cpp +bool DergynovSHypercubeMPI::RunImpl() { + int rank = 0, size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + const auto &in = GetInput(); + int src = in[0]; + int dst = in[1]; + int data_size = in[2]; + + std::vector data; + if (rank == src) { + data.resize(data_size, 1); + } + + if (src == dst) { + MPI_Bcast(&data_size, 1, MPI_INT, dst, MPI_COMM_WORLD); + if (rank != dst) { + data.resize(data_size); + } + MPI_Bcast(data.data(), data_size, MPI_INT, dst, MPI_COMM_WORLD); + GetOutput() = std::accumulate(data.begin(), data.end(), 0); + return true; + } + + int dim = CalcDim(size); + auto path = BuildPath(src, dst, dim); + + int pos, next, prev; + FindPos(rank, path, pos, next, prev); + + if (pos != -1) { + if (rank == src) { + BusyWork(120000); + SendVec(data, next); + } else if (rank == dst) { + RecvVec(data, prev); + BusyWork(120000); + } else { + RecvVec(data, prev); + BusyWork(120000); + SendVec(data, next); + } + } + + int final_size = static_cast(data.size()); + MPI_Bcast(&final_size, 1, MPI_INT, dst, MPI_COMM_WORLD); + if (pos == -1) { + data.resize(final_size); + } + MPI_Bcast(data.data(), final_size, MPI_INT, dst, MPI_COMM_WORLD); + + GetOutput() = std::accumulate(data.begin(), data.end(), 0); + MPI_Barrier(MPI_COMM_WORLD); + return true; +} +``` + +--- + +## 5. Реализация + +```cpp +bool DergynovSHypercube::RunImpl() { + int rank, size; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + auto input = GetInput(); + int source = input[0], dest = input[1]; + std::vector data(input[2], 1); + + int dimensions = CalculateHypercubeDimension(size); + std::vector path = CalcPath(source, dest, dimensions); + + int my_pos = -1, prev_neighbor = -1, next_neighbor = -1; + CalcPositions(rank, path, my_pos, next_neighbor, prev_neighbor); + + if (my_pos != -1) { + if (rank == source) SendData(data, next_neighbor); + else if (rank == dest) { RecvData(data, prev_neighbor); PerformComputeLoad(150000); } + else { RecvData(data, prev_neighbor); PerformComputeLoad(150000); SendData(data, next_neighbor); } + } + + MPI_Bcast(data.data(), data.size(), MPI_INT, dest, MPI_COMM_WORLD); + GetOutput() = std::accumulate(data.begin(), data.end(), 0); + MPI_Barrier(MPI_COMM_WORLD); + return true; +} +``` + +В приведённом фрагменте показана основная логика работы MPI-версии: определение маршрута, приём и передача данных между соседними узлами, а также финальная рассылка результата всем процессам. +Вспомогательные функции инкапсулируют детали обмена и вычислительной нагрузки. +Для каждого узла выполняется `PerformComputeLoad(150000)`, чтобы оценить влияние вычислений на время передачи. + +--- + +## 6. Экспериментальная часть + +### Аппаратная конфигурация + +* Docker-контейнер (Linux) +* GCC +* OpenMPI +* Режим сборки: Release + +### Команды запуска + +``` +mpirun --allow-run-as-root -n 1 ./build/bin/ppc_perf_tests +mpirun --allow-run-as-root -n 2 ./build/bin/ppc_perf_tests +mpirun --allow-run-as-root -n 4 ./build/bin/ppc_perf_tests +``` + +--- + +## 7. Результаты + +### 7.1 Корректность + +Функциональные тесты пройдены для всех сценариев: +* соседние и удалённые процессы +* передача самому себе +* пустой массив +* обратная передача + +Все процессы получают корректную сумму элементов. + +### 7.2. Производительность + +| Процессы | Время выполнения (с) | Шаги передачи | +| -------- | -------------------- | ------------- | +| 2 | 0.0015 | 1 | +| 4 | 0.0016 | 2 | +| 8 | 0.0017 | 3 | +| 16 | 0.0017 | 4 | +| 32 | 0.0023 | 5 | + +Комментарий: +* Время растёт с числом процессов из-за увеличения длины пути. +* Основное время тратится на имитацию вычислительной нагрузки. +* MPI-версия эффективна, накладные расходы минимальны. + +Полученные результаты соответствуют теоретическим ожиданиям для гиперкуба: при увеличении числа процессов растёт размерность топологии и, как следствие, число шагов передачи. Это подтверждает корректность вычисления маршрута. + +--- + +## 8. Заключение + +* Реализована корректная передача данных по топологии гиперкуба. +* MPI-версия не использует встроенные функции топологий. +* Функциональные тесты подтверждают корректность работы. +* Производительность остаётся высокой даже при увеличении числа процессов. + +Дальнейшие улучшения: +* Увеличение объёма данных для более точной оценки масштабируемости. +* Тестирование на реальном кластере с большим числом узлов. + +--- + +## 9. Литература + +1. Gropp W., Lusk E., Skjellum A. Using MPI +2. MPI Standard Documentation +3. Документация GoogleTest +4. Сысоев А. В. Лекции по параллельному программированию diff --git a/tasks/dergynov_s_hypercube/seq/include/ops_seq.hpp b/tasks/dergynov_s_hypercube/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..1301320ad8 --- /dev/null +++ b/tasks/dergynov_s_hypercube/seq/include/ops_seq.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "dergynov_s_hypercube/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace dergynov_s_hypercube { + +class DergynovSHypercubeSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + + explicit DergynovSHypercubeSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace dergynov_s_hypercube diff --git a/tasks/dergynov_s_hypercube/seq/src/ops_seq.cpp b/tasks/dergynov_s_hypercube/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..500751a01f --- /dev/null +++ b/tasks/dergynov_s_hypercube/seq/src/ops_seq.cpp @@ -0,0 +1,30 @@ +#include "dergynov_s_hypercube/seq/include/ops_seq.hpp" + +#include "dergynov_s_hypercube/common/include/common.hpp" + +namespace dergynov_s_hypercube { + +DergynovSHypercubeSEQ::DergynovSHypercubeSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool DergynovSHypercubeSEQ::ValidationImpl() { + return true; +} + +bool DergynovSHypercubeSEQ::PreProcessingImpl() { + return true; +} + +bool DergynovSHypercubeSEQ::RunImpl() { + GetOutput() = GetInput()[2]; + return true; +} + +bool DergynovSHypercubeSEQ::PostProcessingImpl() { + return true; +} + +} // namespace dergynov_s_hypercube diff --git a/tasks/dergynov_s_hypercube/settings.json b/tasks/dergynov_s_hypercube/settings.json new file mode 100644 index 0000000000..7d2c35b298 --- /dev/null +++ b/tasks/dergynov_s_hypercube/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/dergynov_s_hypercube/tests/.clang-tidy b/tasks/dergynov_s_hypercube/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/dergynov_s_hypercube/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/dergynov_s_hypercube/tests/functional/main.cpp b/tasks/dergynov_s_hypercube/tests/functional/main.cpp new file mode 100644 index 0000000000..c334595b4f --- /dev/null +++ b/tasks/dergynov_s_hypercube/tests/functional/main.cpp @@ -0,0 +1,64 @@ +#include + +#include +#include +#include +#include +#include + +#include "dergynov_s_hypercube/common/include/common.hpp" +#include "dergynov_s_hypercube/mpi/include/ops_mpi.hpp" +#include "dergynov_s_hypercube/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace dergynov_s_hypercube { + +class DergynovSHypercubeFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::get<1>(test_param); + } + + protected: + void SetUp() override { + auto tt = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + input_ = std::get<0>(tt); + } + + bool CheckTestOutputData(OutType &out) final { + return out == input_[2]; + } + + InType GetTestInputData() final { + return input_; + } + + private: + InType input_; +}; + +namespace { + +TEST_P(DergynovSHypercubeFuncTests, Run) { + ExecuteTest(GetParam()); +} + +const std::array kTests = { + std::make_tuple(std::vector{0, 3, 5}, "far"), std::make_tuple(std::vector{0, 1, 5}, "neighbor"), + std::make_tuple(std::vector{2, 3, 5}, "mid"), std::make_tuple(std::vector{1, 1, 5}, "self"), + std::make_tuple(std::vector{3, 0, 5}, "reverse"), std::make_tuple(std::vector{0, 3, 0}, "empty"), +}; + +const auto kTasks = + std::tuple_cat(ppc::util::AddFuncTask(kTests, PPC_SETTINGS_dergynov_s_hypercube), + ppc::util::AddFuncTask(kTests, PPC_SETTINGS_dergynov_s_hypercube)); + +const auto kValues = ppc::util::ExpandToValues(kTasks); + +INSTANTIATE_TEST_SUITE_P(HypercubeTests, DergynovSHypercubeFuncTests, kValues, + DergynovSHypercubeFuncTests::PrintFuncTestName); + +} // namespace + +} // namespace dergynov_s_hypercube diff --git a/tasks/dergynov_s_hypercube/tests/performance/main.cpp b/tasks/dergynov_s_hypercube/tests/performance/main.cpp new file mode 100644 index 0000000000..7a8b47bcaf --- /dev/null +++ b/tasks/dergynov_s_hypercube/tests/performance/main.cpp @@ -0,0 +1,44 @@ +#include +#include + +#include "dergynov_s_hypercube/common/include/common.hpp" +#include "dergynov_s_hypercube/mpi/include/ops_mpi.hpp" +#include "dergynov_s_hypercube/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace dergynov_s_hypercube { + +class DergynovSHypercubePerfTests : public ppc::util::BaseRunPerfTests { + protected: + void SetUp() override { + int size = 0; + MPI_Comm_size(MPI_COMM_WORLD, &size); + input_ = {0, size - 1, kDataSize_}; + } + + bool CheckTestOutputData(OutType &out) final { + return out == kDataSize_; + } + + InType GetTestInputData() final { + return input_; + } + + private: + const int kDataSize_ = 400; + InType input_; +}; + +TEST_P(DergynovSHypercubePerfTests, RunPerf) { + ExecuteTest(GetParam()); +} + +const auto kAllTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_dergynov_s_hypercube); + +const auto kValues = ppc::util::TupleToGTestValues(kAllTasks); + +INSTANTIATE_TEST_SUITE_P(PerfTests, DergynovSHypercubePerfTests, kValues, + DergynovSHypercubePerfTests::CustomPerfTestName); + +} // namespace dergynov_s_hypercube