diff --git a/.hadolint.yaml b/.hadolint.yaml index b91b263f..e5cb49a3 100644 --- a/.hadolint.yaml +++ b/.hadolint.yaml @@ -1,3 +1,3 @@ ignored: - - DL3008 # Pin versions in apt-get install - - DL3018 # Pin versions in apk add + - DL3008 # Pin versions in apt-get install + - DL3018 # Pin versions in apk add diff --git a/tasks/klimov_m_torus/common/include/common.hpp b/tasks/klimov_m_torus/common/include/common.hpp new file mode 100644 index 00000000..989b4983 --- /dev/null +++ b/tasks/klimov_m_torus/common/include/common.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" + +namespace klimov_m_torus { + +struct TransferRequest { + int sender{}; + int receiver{}; + std::vector data; +}; + +struct TransferResult { + std::vector received_data; + std::vector route; +}; + +using InType = TransferRequest; +using OutType = TransferResult; +using TestParam = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace klimov_m_torus diff --git a/tasks/klimov_m_torus/info.json b/tasks/klimov_m_torus/info.json new file mode 100644 index 00000000..150dc390 --- /dev/null +++ b/tasks/klimov_m_torus/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Михаил", + "group_number": "3823Б1ПР2", + "last_name": "Климов", + "middle_name": "Дмитриевич", + "task_number": "2" + } +} diff --git a/tasks/klimov_m_torus/mpi/include/ops_mpi.hpp b/tasks/klimov_m_torus/mpi/include/ops_mpi.hpp new file mode 100644 index 00000000..5143d7a2 --- /dev/null +++ b/tasks/klimov_m_torus/mpi/include/ops_mpi.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include "klimov_m_torus/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace klimov_m_torus { + +class TorusMeshCommunicator : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + + explicit TorusMeshCommunicator(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static std::pair CalculateGridSize(int total_processes); + static int CombineCoordinates(int row, int col, int rows, int cols); + static std::pair SplitRank(int rank, int cols); + static std::vector BuildMessageRoute(int rows, int cols, int from, int to); + + void DistributeSenderReceiver(int &src, int &dst); + void DistributeDataLength(int src, int &len) const; + [[nodiscard]] std::vector AssembleSendBuffer(int src, int len) const; + void RelayMessage(int src, int dst, const std::vector &route, const std::vector &buffer, + std::vector &output) const; + void SaveFinalResult(int dst, const std::vector &output, const std::vector &route); + + InType local_request_{}; + OutType local_response_{}; + + int current_rank_{0}; + int total_ranks_{0}; + + int grid_rows_{1}; + int grid_cols_{1}; +}; + +} // namespace klimov_m_torus diff --git a/tasks/klimov_m_torus/mpi/src/ops_mpi.cpp b/tasks/klimov_m_torus/mpi/src/ops_mpi.cpp new file mode 100644 index 00000000..c61e073e --- /dev/null +++ b/tasks/klimov_m_torus/mpi/src/ops_mpi.cpp @@ -0,0 +1,237 @@ +#include "klimov_m_torus/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "klimov_m_torus/common/include/common.hpp" + +namespace klimov_m_torus { + +namespace { + +// Вспомогательные функции, вынесенные в анонимный namespace для снижения сложности RelayMessage + +void HandleSameNode(int current_rank, int src, const std::vector &buffer, std::vector &output) { + if (current_rank == src) { + output = buffer; + } +} + +void HandleSourceNode(int current_rank, int src, const std::vector &route, const std::vector &buffer, + std::vector &output) { + output = buffer; + if (current_rank == src && route.size() > 1) { + int next_hop = route[1]; + int send_len = static_cast(buffer.size()); + MPI_Send(&send_len, 1, MPI_INT, next_hop, 0, MPI_COMM_WORLD); + if (send_len > 0) { + MPI_Send(output.data(), send_len, MPI_INT, next_hop, 1, MPI_COMM_WORLD); + } + } +} + +void HandleIntermediateNode(int current_rank, int dst, const std::vector &route, int my_pos, + std::vector &output) { + int prev_hop = route[my_pos - 1]; + int recv_len = 0; + MPI_Recv(&recv_len, 1, MPI_INT, prev_hop, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + output.resize(recv_len); + if (recv_len > 0) { + MPI_Recv(output.data(), recv_len, MPI_INT, prev_hop, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } + + if (current_rank != dst && my_pos + 1 < static_cast(route.size())) { + int next_hop = route[my_pos + 1]; + MPI_Send(&recv_len, 1, MPI_INT, next_hop, 0, MPI_COMM_WORLD); + if (recv_len > 0) { + MPI_Send(output.data(), recv_len, MPI_INT, next_hop, 1, MPI_COMM_WORLD); + } + } +} + +} // namespace + +TorusMeshCommunicator::TorusMeshCommunicator(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = {}; +} + +std::pair TorusMeshCommunicator::CalculateGridSize(int total_processes) { + int rows = static_cast(std::sqrt(static_cast(total_processes))); + while (rows > 1 && (total_processes % rows != 0)) { + --rows; + } + if (rows <= 0) { + rows = 1; + } + int cols = total_processes / rows; + if (cols <= 0) { + cols = 1; + } + return {rows, cols}; +} + +int TorusMeshCommunicator::CombineCoordinates(int row, int col, int rows, int cols) { + int wrapped_row = ((row % rows) + rows) % rows; + int wrapped_col = ((col % cols) + cols) % cols; + return (wrapped_row * cols) + wrapped_col; +} + +std::pair TorusMeshCommunicator::SplitRank(int rank, int cols) { + int r = rank / cols; + int c = rank % cols; + return {r, c}; +} + +std::vector TorusMeshCommunicator::BuildMessageRoute(int rows, int cols, int from, int to) { + std::vector route; + if (rows <= 0 || cols <= 0) { + route.push_back(from); + return route; + } + + auto [src_row, src_col] = SplitRank(from, cols); + auto [dst_row, dst_col] = SplitRank(to, cols); + + int cur_row = src_row; + int cur_col = src_col; + route.push_back(from); + + int col_diff = dst_col - src_col; + int right_steps = (col_diff >= 0) ? col_diff : col_diff + cols; + int left_steps = (col_diff <= 0) ? -col_diff : cols - col_diff; + int col_step = (right_steps <= left_steps) ? 1 : -1; + int col_moves = (right_steps <= left_steps) ? right_steps : left_steps; + + for (int i = 0; i < col_moves; ++i) { + cur_col += col_step; + route.push_back(CombineCoordinates(cur_row, cur_col, rows, cols)); + } + + int row_diff = dst_row - src_row; + int down_steps = (row_diff >= 0) ? row_diff : row_diff + rows; + int up_steps = (row_diff <= 0) ? -row_diff : rows - row_diff; + int row_step = (down_steps <= up_steps) ? 1 : -1; + int row_moves = (down_steps <= up_steps) ? down_steps : up_steps; + + for (int i = 0; i < row_moves; ++i) { + cur_row += row_step; + route.push_back(CombineCoordinates(cur_row, cur_col, rows, cols)); + } + + return route; +} + +bool TorusMeshCommunicator::ValidationImpl() { + int initialized = 0; + MPI_Initialized(&initialized); + if (initialized == 0) { + return false; + } + + MPI_Comm_rank(MPI_COMM_WORLD, ¤t_rank_); + MPI_Comm_size(MPI_COMM_WORLD, &total_ranks_); + + int valid = 0; + if (current_rank_ == 0) { + const auto &req = GetInput(); + if (req.sender >= 0 && req.receiver >= 0 && req.sender < total_ranks_ && req.receiver < total_ranks_) { + valid = 1; + } + } + MPI_Bcast(&valid, 1, MPI_INT, 0, MPI_COMM_WORLD); + return valid != 0; +} + +bool TorusMeshCommunicator::PreProcessingImpl() { + MPI_Comm_rank(MPI_COMM_WORLD, ¤t_rank_); + MPI_Comm_size(MPI_COMM_WORLD, &total_ranks_); + + auto [r, c] = CalculateGridSize(total_ranks_); + grid_rows_ = r; + grid_cols_ = c; + + local_request_ = GetInput(); + local_response_ = OutType{}; + return true; +} + +bool TorusMeshCommunicator::RunImpl() { + int sender = 0; + int receiver = 0; + DistributeSenderReceiver(sender, receiver); + + int data_len = 0; + DistributeDataLength(sender, data_len); + + std::vector send_buffer = AssembleSendBuffer(sender, data_len); + std::vector message_route = BuildMessageRoute(grid_rows_, grid_cols_, sender, receiver); + + std::vector received_data; + RelayMessage(sender, receiver, message_route, send_buffer, received_data); + + SaveFinalResult(receiver, received_data, message_route); + return true; +} + +void TorusMeshCommunicator::DistributeSenderReceiver(int &src, int &dst) { + if (current_rank_ == 0) { + const auto &req = GetInput(); + src = req.sender; + dst = req.receiver; + } + MPI_Bcast(&src, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&dst, 1, MPI_INT, 0, MPI_COMM_WORLD); +} + +void TorusMeshCommunicator::DistributeDataLength(int src, int &len) const { + if (current_rank_ == src) { + len = static_cast(local_request_.data.size()); + } + MPI_Bcast(&len, 1, MPI_INT, src, MPI_COMM_WORLD); +} + +std::vector TorusMeshCommunicator::AssembleSendBuffer(int src, int len) const { + std::vector buffer(len); + if (current_rank_ == src && len > 0) { + std::ranges::copy(local_request_.data, buffer.begin()); + } + return buffer; +} + +void TorusMeshCommunicator::RelayMessage(int src, int dst, const std::vector &route, + const std::vector &buffer, std::vector &output) const { + auto it = std::ranges::find(route, current_rank_); + bool on_route = (it != route.end()); + int my_pos = on_route ? static_cast(std::distance(route.begin(), it)) : -1; + + if (src == dst) { + HandleSameNode(current_rank_, src, buffer, output); + } else if (current_rank_ == src) { + HandleSourceNode(current_rank_, src, route, buffer, output); + } else if (on_route) { + HandleIntermediateNode(current_rank_, dst, route, my_pos, output); + } +} + +void TorusMeshCommunicator::SaveFinalResult(int dst, const std::vector &output, const std::vector &route) { + if (current_rank_ == dst) { + local_response_.received_data = output; + local_response_.route = route; + GetOutput() = local_response_; + } else { + GetOutput() = OutType{}; + } +} + +bool TorusMeshCommunicator::PostProcessingImpl() { + return true; +} + +} // namespace klimov_m_torus diff --git a/tasks/klimov_m_torus/report.md b/tasks/klimov_m_torus/report.md new file mode 100644 index 00000000..237116dd --- /dev/null +++ b/tasks/klimov_m_torus/report.md @@ -0,0 +1,75 @@ +# Решетка-тор (Mesh-Torus) + +- **Студент**: Климов Михаил Дмитриевич, группа 3823Б1ПР2 +- **Технология**: MPI / SEQ +- **Вариант**: 9 + +## 1. Введение +Цель работы – реализовать виртуальную топологию «решётка-тор» средствами MPI, используя только точечные коммуникации (`MPI_Send`/`MPI_Recv`) без применения готовых функций создания топологий (`MPI_Cart_create`). Задача заключается в организации передачи данных от произвольного процесса-отправителя к произвольному процессу-получателю в рамках двумерной тороидальной решётки, а также в построении маршрута передачи. + +## 2. Постановка задачи +На вход подаются три параметра: номер исходного процесса `sender`, номер целевого процесса `receiver` и вектор целочисленных данных `data`. Требуется передать данные от `sender` к `receiver` по кратчайшему пути в топологии «решётка-тор» и зафиксировать пройденный маршрут (последовательность номеров процессов). Размеры решётки определяются автоматически исходя из общего числа MPI-процессов (квадратная или прямоугольная сетка, максимально близкая к квадрату). Выходные данные – полученный вектор `received_data` и вектор `route`, содержащий все промежуточные процессы (включая источник и цель). + +## 3. Описание последовательного алгоритма +Последовательная версия реализована в классе `TorusReferenceImpl`. Поскольку в последовательном случае передача данных не требуется, алгоритм тривиален: +1. Проверка корректности входных данных (`sender` и `receiver` неотрицательны). +2. Копирование входного вектора `data` в выходной `received_data`. +3. Формирование маршрута `route`: если `sender == receiver`, маршрут состоит из одного элемента; иначе – из двух элементов: `sender` и `receiver`. + +Данная реализация служит эталоном для проверки корректности MPI-версии на малом числе процессов. + +## 4. Схема распараллеливания (MPI) +Параллельная версия реализована в классе `TorusMeshCommunicator`. Основные этапы: + +1. **Определение размеров решётки**. На этапе предварительной обработки все процессы вычисляют размеры сетки `grid_rows_` и `grid_cols_` на основе общего числа процессов `total_ranks_`. Используется метод `CalculateGridSize`, который подбирает такие `rows` и `cols`, чтобы их произведение равнялось `total_ranks_`, а сетка была как можно более квадратной. + +2. **Преобразование «ранг ↔ координаты»**. Реализованы функции `CombineCoordinates` и `SplitRank`, позволяющие переходить от двумерных координат `(row, col)` к рангу MPI и обратно. Учёт тороидальности обеспечивается взятием по модулю. + +3. **Построение кратчайшего пути**. Метод `BuildMessageRoute` строит маршрут от `sender` до `receiver` в тороидальной сетке. Сначала происходит движение по столбцам (выбирается кратчайшее направление – вправо или влево с учётом зацикливания), затем – по строкам. Полученный путь – это последовательность рангов, через которые пройдёт сообщение. + +4. **Распространение данных**. Процесс с рангом 0 рассылает всем номера `sender` и `receiver`. Далее процесс-отправитель (`sender`) широковещательно передаёт размер своего вектора `data` (через `MPI_Bcast`), а затем все процессы, участвующие в пути, организуют цепочку передачи: + - Отправитель сохраняет свои данные в буфере и отправляет их следующему процессу в пути (если он не является получателем). + - Каждый промежуточный процесс принимает данные от предыдущего, сохраняет их у себя и, если он не является конечным получателем, отправляет дальше. + - Получатель принимает данные и сохраняет их в выходной структуре, также фиксируя полный путь. + +Взаимодействие между соседями по пути осуществляется только с помощью `MPI_Send` и `MPI_Recv`, что строго соответствует условию задачи (без использования коллективных операций для пересылки данных). + +## 5. Детали реализации +- Класс `TorusMeshCommunicator` содержит вспомогательные методы: `CalculateGridSize`, `CombineCoordinates`, `SplitRank`, `BuildMessageRoute`, `DistributeSenderReceiver`, `DistributeDataLength`, `AssembleSendBuffer`, `RelayMessage`, `SaveFinalResult`. +- Все обмены данными между процессами выполняются по принципу «каждый передаёт только следующему»; гарантируется, что сообщение не зациклится и дойдёт до адресата. +- Валидация входных данных включает проверку на то, что `sender` и `receiver` находятся в допустимых пределах и что MPI инициализирован. +- Последовательная версия `TorusReferenceImpl` максимально упрощена и служит только для функционального тестирования. +- Тесты параметризованы размером данных (`1, 4, 8, 16, 32` элемента) и проверяют, что: + - Данные доходят до получателя без искажений. + - Маршрут содержит корректные начальную и конечную точки. + - Промежуточные процессы (кроме получателя) не сохраняют результат. + +## 6. Экспериментальная установка +- **Аппаратное обеспечение**: виртуальная среда (контейнер Docker) с выделением 4 ядер и 8 ГБ ОЗУ. +- **ОС**: Ubuntu 24.04 LTS (внутри контейнера). +- **Компилятор**: g++-14. +- **Сборка**: CMake с типом `RelWithDebInfo`, санитайзеры не использовались при финальных тестах. +- **Среда выполнения**: MPI-тесты запускались с `PPC_NUM_PROC=2`, `4`, `8` (для проверки при разных размерах сетки) через скрипт `scripts/run_tests.py`. Количество процессов подбиралось так, чтобы можно было построить прямоугольную сетку (например, 2→1x2, 4→2x2, 8→2x4 или 4x2). + +## 7. Результаты и обсуждение + +### 7.1 Корректность +Функциональные тесты покрывают следующие сценарии: +- Передача данных от процесса 0 к процессу `total_ranks_-1` (наиболее удалённому). +- Различные размеры полезной нагрузки (от 1 до 32 целых чисел). +- Проверка, что только процесс-получатель сохраняет данные, а остальные (включая промежуточные) возвращают пустой результат. +- Проверка, что маршрут начинается с `sender` и заканчивается `receiver`. + +Все тесты успешно проходятся как для последовательной, так и для MPI-версии при любом допустимом количестве процессов. Это подтверждает правильность построения пути и передачи данных в тороидальной топологии. + +### 7.2 Особенности реализации +- Использованный метод построения пути (сначала по столбцам, затем по строкам) всегда даёт кратчайший маршрут в метрике Манхэттена на торе, так как движения по разным осям независимы. +- Благодаря модульным вычислениям координат (`(x % N + N) % N`) обеспечивается корректная работа на тороидальных границах. +- При `sender == receiver` данные не передаются по сети, и маршрут состоит из одного процесса. + +## 8. Выводы +Разработана MPI-программа, реализующая передачу данных в топологии «решётка-тор» с использованием только `MPI_Send/Recv`. Программа автоматически определяет размеры сетки, строит кратчайший путь и выполняет доставку сообщения по цепочке процессов. Функциональное тестирование подтверждает корректность работы для различных конфигураций процессов и размеров данных. Реализация полностью соответствует условиям задачи и может служить основой для дальнейших экспериментов с маршрутизацией в тороидальных сетях. + +## 9. Источники +1. Microsoft MPI : документация // Microsoft Learn. – URL: https://learn.microsoft.com/ru-ru/message-passing-interface/microsoft-mpi +2. Сысоев А. В. Курс лекций по параллельному программированию \ No newline at end of file diff --git a/tasks/klimov_m_torus/seq/include/ops_seq.hpp b/tasks/klimov_m_torus/seq/include/ops_seq.hpp new file mode 100644 index 00000000..5d203e3c --- /dev/null +++ b/tasks/klimov_m_torus/seq/include/ops_seq.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "klimov_m_torus/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace klimov_m_torus { + +class TorusReferenceImpl : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit TorusReferenceImpl(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + InType local_request_{}; + OutType local_response_{}; +}; + +} // namespace klimov_m_torus diff --git a/tasks/klimov_m_torus/seq/src/ops_seq.cpp b/tasks/klimov_m_torus/seq/src/ops_seq.cpp new file mode 100644 index 00000000..6f88d5fd --- /dev/null +++ b/tasks/klimov_m_torus/seq/src/ops_seq.cpp @@ -0,0 +1,43 @@ +#include "klimov_m_torus/seq/include/ops_seq.hpp" + +#include + +#include "klimov_m_torus/common/include/common.hpp" + +namespace klimov_m_torus { + +TorusReferenceImpl::TorusReferenceImpl(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = {}; +} + +bool TorusReferenceImpl::ValidationImpl() { + const auto &req = GetInput(); + return req.sender >= 0 && req.receiver >= 0; +} + +bool TorusReferenceImpl::PreProcessingImpl() { + return true; +} + +bool TorusReferenceImpl::RunImpl() { + const auto &req = GetInput(); + auto &out = GetOutput(); + + out.received_data = req.data; + + out.route.clear(); + out.route.push_back(req.sender); + if (req.sender != req.receiver) { + out.route.push_back(req.receiver); + } + + return true; +} + +bool TorusReferenceImpl::PostProcessingImpl() { + return true; +} + +} // namespace klimov_m_torus diff --git a/tasks/klimov_m_torus/settings.json b/tasks/klimov_m_torus/settings.json new file mode 100644 index 00000000..16f25e42 --- /dev/null +++ b/tasks/klimov_m_torus/settings.json @@ -0,0 +1,7 @@ +{ + "tasks": { + "mpi": "enabled", + "seq": "enabled" + }, + "tasks_type": "processes" +} diff --git a/tasks/klimov_m_torus/tests/functional/main.cpp b/tasks/klimov_m_torus/tests/functional/main.cpp new file mode 100644 index 00000000..7881a860 --- /dev/null +++ b/tasks/klimov_m_torus/tests/functional/main.cpp @@ -0,0 +1,125 @@ +#include +#include + +#include +#include +#include +#include + +#include "klimov_m_torus/common/include/common.hpp" +#include "klimov_m_torus/mpi/include/ops_mpi.hpp" +#include "klimov_m_torus/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" + +namespace klimov_m_torus { + +class TorusFunctionalTest : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestParam ¶m) { + int size = std::get<0>(param); + return "size" + std::to_string(size); + } + + protected: + void SetUp() override { + TestParam params = std::get<2>(GetParam()); + int data_size = std::get<0>(params); + + int mpi_initialized = 0; + MPI_Initialized(&mpi_initialized); + if (mpi_initialized != 0) { + MPI_Comm_size(MPI_COMM_WORLD, &world_size_); + MPI_Comm_rank(MPI_COMM_WORLD, &rank_); + } else { + world_size_ = 1; + rank_ = 0; + } + + source_ = 0; + dest_ = (world_size_ > 1) ? (world_size_ - 1) : 0; + + request_.sender = source_; + request_.receiver = dest_; + request_.data.clear(); + for (int i = 0; i < data_size; ++i) { + request_.data.push_back(i + 1); + } + expected_payload_ = request_.data; + } + + InType GetTestInputData() override { + return request_; + } + + bool CheckTestOutputData(OutType &out) override { + const std::string &task_name = std::get<1>(GetParam()); + const bool is_seq = (task_name.find("seq") != std::string::npos); + + return is_seq ? CheckSequential(out) : CheckParallel(out); + } + + private: + [[nodiscard]] bool CheckSequential(const OutType &out) const { + if (out.received_data != expected_payload_) { + return false; + } + if (out.route.empty()) { + return false; + } + if (out.route.front() != source_) { + return false; + } + if (out.route.back() != dest_) { + return false; + } + return true; + } + + [[nodiscard]] bool CheckParallel(const OutType &out) const { + if (rank_ != dest_) { + return out.received_data.empty() && out.route.empty(); + } + if (out.received_data != expected_payload_) { + return false; + } + if (out.route.empty()) { + return false; + } + if (out.route.front() != source_) { + return false; + } + if (out.route.back() != dest_) { + return false; + } + return true; + } + + InType request_{}; + std::vector expected_payload_; + int world_size_{1}; + int rank_{0}; + int source_{0}; + int dest_{0}; +}; + +namespace { + +const std::array kTestParams = { + std::make_tuple(1), std::make_tuple(4), std::make_tuple(8), std::make_tuple(16), std::make_tuple(32), +}; + +const auto kTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParams, "tasks/klimov_m_torus/settings.json"), + ppc::util::AddFuncTask(kTestParams, "tasks/klimov_m_torus/settings.json")); + +const auto kValues = ppc::util::ExpandToValues(kTasksList); +const auto kNamePrinter = TorusFunctionalTest::PrintFuncTestName; + +TEST_P(TorusFunctionalTest, TorusDataTransfer) { + ExecuteTest(GetParam()); +} + +INSTANTIATE_TEST_SUITE_P(TorusFunctionalTests, TorusFunctionalTest, kValues, kNamePrinter); + +} // namespace +} // namespace klimov_m_torus diff --git a/tasks/klimov_m_torus/tests/performance/main.cpp b/tasks/klimov_m_torus/tests/performance/main.cpp new file mode 100644 index 00000000..17138009 --- /dev/null +++ b/tasks/klimov_m_torus/tests/performance/main.cpp @@ -0,0 +1,106 @@ +#include +#include + +#include + +#include "klimov_m_torus/common/include/common.hpp" +#include "klimov_m_torus/mpi/include/ops_mpi.hpp" +#include "klimov_m_torus/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace klimov_m_torus { + +class TorusPerformanceTest : public ppc::util::BaseRunPerfTests { + protected: + InType test_data; + bool data_ready = false; + int world_size = 1; + int rank = 0; + bool is_seq_mode = false; + + void SetUp() override { + std::string task_name = std::get<1>(GetParam()); + is_seq_mode = (task_name.find("seq") != std::string::npos); + + int mpi_initialized = 0; + MPI_Initialized(&mpi_initialized); + if (mpi_initialized != 0) { + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + } + + PrepareTestData(); + } + + void PrepareTestData() { + if (data_ready) { + return; + } + + const int data_size = 10000000; + + test_data.sender = 0; + if (is_seq_mode) { + test_data.receiver = 0; + } else { + test_data.receiver = (world_size > 1) ? (world_size - 1) : 0; + } + + test_data.data.resize(data_size); + for (int i = 0; i < data_size; ++i) { + test_data.data[i] = i + 1; + } + + data_ready = true; + } + + InType GetTestInputData() override { + return test_data; + } + + bool CheckTestOutputData(OutType &out) override { + std::string task_name = std::get<1>(GetParam()); + bool is_mpi = (task_name.find("mpi") != std::string::npos); + + if (is_mpi) { + if (rank != test_data.receiver) { + return out.received_data.empty() && out.route.empty(); + } + if (out.received_data.size() != test_data.data.size()) { + return false; + } + if (out.received_data.empty()) { + return true; + } + return (out.received_data.front() == test_data.data.front() && out.received_data.back() == test_data.data.back()); + } + + if (rank == 0) { + if (out.received_data.size() != test_data.data.size()) { + return false; + } + if (out.received_data.empty()) { + return true; + } + return (out.received_data.front() == test_data.data.front() && out.received_data.back() == test_data.data.back()); + } + return true; + } +}; + +namespace { + +const auto kPerfTasksTuples = ppc::util::MakeAllPerfTasks( + "tasks/klimov_m_torus/settings.json"); + +const auto kPerfValues = ppc::util::TupleToGTestValues(kPerfTasksTuples); +const auto kPerfNamePrinter = TorusPerformanceTest::CustomPerfTestName; + +TEST_P(TorusPerformanceTest, TorusPerformance) { + ExecuteTest(GetParam()); +} + +INSTANTIATE_TEST_SUITE_P(TorusPerformanceTests, TorusPerformanceTest, kPerfValues, kPerfNamePrinter); + +} // namespace +} // namespace klimov_m_torus