diff --git a/tasks/sannikov_i_column_sum/common/include/common.hpp b/tasks/sannikov_i_column_sum/common/include/common.hpp new file mode 100644 index 0000000000..32f5d69a25 --- /dev/null +++ b/tasks/sannikov_i_column_sum/common/include/common.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace sannikov_i_column_sum { + +using InType = std::vector>; +using OutType = std::vector; +using TestType = std::tuple>, std::string>; +using BaseTask = ppc::task::Task; + +} // namespace sannikov_i_column_sum diff --git a/tasks/sannikov_i_column_sum/info.json b/tasks/sannikov_i_column_sum/info.json new file mode 100644 index 0000000000..00f793a5c5 --- /dev/null +++ b/tasks/sannikov_i_column_sum/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Иван", + "last_name": "Санников", + "middle_name": "Михайлович", + "group_number": "3823Б1ФИ2", + "task_number": "12" + } +} diff --git a/tasks/sannikov_i_column_sum/mpi/include/ops_mpi.hpp b/tasks/sannikov_i_column_sum/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..bdacc87fb6 --- /dev/null +++ b/tasks/sannikov_i_column_sum/mpi/include/ops_mpi.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include "sannikov_i_column_sum/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace sannikov_i_column_sum { + +class SannikovIColumnSumMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit SannikovIColumnSumMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static void PrepareSendBuffer(const InType &input_matrix, int rank, std::uint64_t rows, std::uint64_t columns, + std::vector &sendbuf); +}; + +} // namespace sannikov_i_column_sum diff --git a/tasks/sannikov_i_column_sum/mpi/src/ops_mpi.cpp b/tasks/sannikov_i_column_sum/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..f9ae5de652 --- /dev/null +++ b/tasks/sannikov_i_column_sum/mpi/src/ops_mpi.cpp @@ -0,0 +1,114 @@ +#include "sannikov_i_column_sum/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include + +#include "sannikov_i_column_sum/common/include/common.hpp" + +namespace sannikov_i_column_sum { + +SannikovIColumnSumMPI::SannikovIColumnSumMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + auto &input_buffer = GetInput(); + InType tmp(in); + input_buffer.swap(tmp); + GetOutput().clear(); +} + +bool SannikovIColumnSumMPI::ValidationImpl() { + const auto &input_matrix = GetInput(); + if (input_matrix.empty() || input_matrix.front().empty()) { + return false; + } + + const std::size_t columns = input_matrix.front().size(); + for (const auto &row : input_matrix) { + if (row.size() != columns) { + return false; + } + } + + return GetOutput().empty(); +} + +bool SannikovIColumnSumMPI::PreProcessingImpl() { + GetOutput().clear(); + return true; +} + +void SannikovIColumnSumMPI::PrepareSendBuffer(const InType &input_matrix, int rank, std::uint64_t rows, + std::uint64_t columns, std::vector &sendbuf) { + if (rank != 0) { + return; + } + if (rank == 0) { + const std::uint64_t base = rows * columns; + sendbuf.resize(static_cast(base)); + for (std::uint64_t i = 0; i < rows; i++) { + for (std::uint64_t j = 0; j < columns; j++) { + sendbuf[static_cast((i * columns) + (j))] = + input_matrix[static_cast(i)][static_cast(j)]; + } + } + } +} + +bool SannikovIColumnSumMPI::RunImpl() { + const auto &input_matrix = GetInput(); + + int rank = 0; + int size = 1; + std::uint64_t rows = 0; + std::uint64_t columns = 0; + + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + if (rank == 0) { + rows = static_cast(input_matrix.size()); + columns = static_cast(input_matrix.front().size()); + } + + MPI_Bcast(&rows, 1, MPI_UINT64_T, 0, MPI_COMM_WORLD); + MPI_Bcast(&columns, 1, MPI_UINT64_T, 0, MPI_COMM_WORLD); + + const std::uint64_t base = rows * columns; + if (columns > static_cast(std::numeric_limits::max()) || + (base > static_cast(std::numeric_limits::max()))) { + return false; + } + const int columns_int = static_cast(columns); + const int base_int = static_cast(base); + GetOutput().assign(static_cast(columns_int), 0); + + std::vector sendbuf; + PrepareSendBuffer(input_matrix, rank, rows, columns, sendbuf); + std::vector elem_for_proc(size); + std::vector id_elem(size); + int displacement = 0; + for (int i = 0; i < size; i++) { + elem_for_proc[i] = static_cast(base_int / size) + (i < (base_int % size) ? 1 : 0); + id_elem[i] = displacement; + displacement += elem_for_proc[i]; + } + const int mpi_displacement = id_elem[rank] % static_cast(columns_int); + std::vector buf(static_cast(elem_for_proc[rank]), 0); + MPI_Scatterv(rank == 0 ? sendbuf.data() : nullptr, elem_for_proc.data(), id_elem.data(), MPI_INT, buf.data(), + elem_for_proc[rank], MPI_INT, 0, MPI_COMM_WORLD); + std::vector sum(static_cast(columns_int), 0); + for (int i = 0; i < (elem_for_proc[rank]); i++) { + int new_col = (i + mpi_displacement) % columns_int; + sum[static_cast(new_col)] += buf[static_cast(i)]; + } + MPI_Allreduce(sum.data(), GetOutput().data(), columns_int, MPI_INT, MPI_SUM, MPI_COMM_WORLD); + return !GetOutput().empty(); +} + +bool SannikovIColumnSumMPI::PostProcessingImpl() { + return !GetOutput().empty(); +} + +} // namespace sannikov_i_column_sum diff --git a/tasks/sannikov_i_column_sum/report.md b/tasks/sannikov_i_column_sum/report.md new file mode 100644 index 0000000000..3b3b8831be --- /dev/null +++ b/tasks/sannikov_i_column_sum/report.md @@ -0,0 +1,151 @@ +# Сумма значений по столбцам матрицы + +- Студент: Санников Иван Михайлович, Группа: 3823Б1ФИ2 +- Технология: SEQ, MPI +- Вариант: 12 +## 1. Введение +Вычисление сумм по столбцам матрицы является одной из основных в математике и часто используется в прикладных задачах, как машинное обучение(ML) и статистика. Часто приходится работать с большим объемом данных, что делает задачу ресурсоемкой при использовании последовательных алгоритмов. Цель данной лабораторной работы - написание алгоритма на основе технологии MPI для распределение нагрузки между несколькоми процессами. + +## 2. Постановка задачи +Входные данные: std::vector> - вектор векторов типа данных int представляющий из себя матрицу. + +Для матрицы размера A*B, строица std::vector размера B, где для элемента i хранится сумма столбца i из входной матрицы. + +## 3. Базовый алгоритм(seq) +Последовательный алгорритм: +- Проходим по всем строкам +- В каждой i строке проходим по всем столбцам +- Складываем элемент i строки j столбца c элементом j вектора суммы. + +```cpp +bool SannikovIColumnSumSEQ::RunImpl() { + const auto &input_matrix = GetInput(); + for (const auto &row : input_matrix) { + std::size_t column = 0; + for (const auto &value : row) { + GetOutput()[column] += value; + column++; + } + } + return !GetOutput().empty(); +} +``` + +## 4. Описание параллельного алгоритма + +1. Обрабатываем входные данные: Превращаем std::vector> в последовательный std::vector, чтобы была возможность его разделить Scatterv. +2. Вычисляем сколько данных получит процесс: перемножаем количество столбцов на строки, делим на количество процессов и прибавляем 1, если номер вычисления меньше остатка (i &sendbuf) { + if (rank != 0) { + return; + } + if (rank == 0) { + const std::uint64_t base = rows * columns; + sendbuf.resize(static_cast(base)); + for (std::uint64_t i = 0; i < rows; i++) { + for (std::uint64_t j = 0; j < columns; j++) { + sendbuf[static_cast((i * columns) + (j))] = + input_matrix[static_cast(i)][static_cast(j)]; + } + } + } +} + + + +bool SannikovIColumnSumMPI::RunImpl() { + const auto &input_matrix = GetInput(); + + int rank = 0; + int size = 1; + std::uint64_t rows = 0; + std::uint64_t columns = 0; + + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + if (rank == 0) { + rows = static_cast(input_matrix.size()); + columns = static_cast(input_matrix.front().size()); + } + + MPI_Bcast(&rows, 1, MPI_UINT64_T, 0, MPI_COMM_WORLD); + MPI_Bcast(&columns, 1, MPI_UINT64_T, 0, MPI_COMM_WORLD); + + const std::uint64_t base = rows * columns; + if (columns > static_cast(std::numeric_limits::max())) { + return false; + } + if (base > static_cast(std::numeric_limits::max())) { + return false; + } + + const int columns_int = static_cast(columns); + const int base_int = static_cast(base); + GetOutput().assign(static_cast(columns_int), 0); + + std::vector sendbuf; + PrepareSendBuffer(input_matrix, rank, rows, columns, sendbuf); + std::vector elem_for_proc(size); + std::vector id_elem(size); + int displacement = 0; + for (int i = 0; i < size; i++) { + elem_for_proc[i] = static_cast(base_int / size) + (i < (base_int % size) ? 1 : 0); + id_elem[i] = displacement; + displacement += elem_for_proc[i]; + } + const int mpi_displacement = id_elem[rank] % static_cast(columns_int); + std::vector buf(static_cast(elem_for_proc[rank]), 0); + MPI_Scatterv(rank == 0 ? sendbuf.data() : nullptr, elem_for_proc.data(), id_elem.data(), MPI_INT, buf.data(), + elem_for_proc[rank], MPI_INT, 0, MPI_COMM_WORLD); + std::vector sum(static_cast(columns_int), 0); + for (int i = 0; i < (elem_for_proc[rank]); i++) { + int new_col = (i + mpi_displacement) % columns_int; + sum[static_cast(new_col)] += buf[static_cast(i)]; + } + MPI_Allreduce(sum.data(), GetOutput().data(), columns_int, MPI_INT, MPI_SUM, MPI_COMM_WORLD); + return !GetOutput().empty(); +} + + +``` \ No newline at end of file diff --git a/tasks/sannikov_i_column_sum/seq/include/ops_seq.hpp b/tasks/sannikov_i_column_sum/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..3eee0a4c57 --- /dev/null +++ b/tasks/sannikov_i_column_sum/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "sannikov_i_column_sum/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace sannikov_i_column_sum { + +class SannikovIColumnSumSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit SannikovIColumnSumSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace sannikov_i_column_sum diff --git a/tasks/sannikov_i_column_sum/seq/src/ops_seq.cpp b/tasks/sannikov_i_column_sum/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..e2abfe975c --- /dev/null +++ b/tasks/sannikov_i_column_sum/seq/src/ops_seq.cpp @@ -0,0 +1,57 @@ +#include "sannikov_i_column_sum/seq/include/ops_seq.hpp" + +#include +#include + +#include "sannikov_i_column_sum/common/include/common.hpp" +namespace sannikov_i_column_sum { + +SannikovIColumnSumSEQ::SannikovIColumnSumSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + auto &input_buffer = GetInput(); + InType tmp(in); + input_buffer.swap(tmp); + + GetOutput().clear(); +} + +bool SannikovIColumnSumSEQ::ValidationImpl() { + const auto &input_matrix = GetInput(); + if (input_matrix.empty() || input_matrix.front().empty()) { + return false; + } + + const std::size_t columns = input_matrix.front().size(); + for (const auto &row : input_matrix) { + if (row.size() != columns) { + return false; + } + } + + return GetOutput().empty(); +} + +bool SannikovIColumnSumSEQ::PreProcessingImpl() { + const auto &input_matrix = GetInput(); + GetOutput().clear(); + GetOutput().resize(input_matrix.front().size(), 0); + return !GetOutput().empty(); +} + +bool SannikovIColumnSumSEQ::RunImpl() { + const auto &input_matrix = GetInput(); + for (const auto &row : input_matrix) { + std::size_t column = 0; + for (const auto &value : row) { + GetOutput()[column] += value; + column++; + } + } + return !GetOutput().empty(); +} + +bool SannikovIColumnSumSEQ::PostProcessingImpl() { + return !GetOutput().empty(); +} + +} // namespace sannikov_i_column_sum diff --git a/tasks/sannikov_i_column_sum/settings.json b/tasks/sannikov_i_column_sum/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/sannikov_i_column_sum/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/sannikov_i_column_sum/tests/.clang-tidy b/tasks/sannikov_i_column_sum/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/sannikov_i_column_sum/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/sannikov_i_column_sum/tests/functional/main.cpp b/tasks/sannikov_i_column_sum/tests/functional/main.cpp new file mode 100644 index 0000000000..6e92cf24a6 --- /dev/null +++ b/tasks/sannikov_i_column_sum/tests/functional/main.cpp @@ -0,0 +1,88 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "sannikov_i_column_sum/common/include/common.hpp" +#include "sannikov_i_column_sum/mpi/include/ops_mpi.hpp" +#include "sannikov_i_column_sum/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace sannikov_i_column_sum { + +class SannikovIColumnSumFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::get<1>(test_param); + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + input_data_ = std::get<0>(params); + } + + bool CheckTestOutputData(OutType &output_data) final { + OutType sums_vec(input_data_.front().size(), 0); + for (std::size_t i = 0; i < (input_data_.size()); i++) { + for (std::size_t j = 0; j < (input_data_[i].size()); j++) { + sums_vec[j] += input_data_[i][j]; + } + } + return (sums_vec == output_data); + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +namespace { + +TEST_P(SannikovIColumnSumFuncTests, MatmulFromPic) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + std::make_tuple(std::vector>{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, "matrix3x3"), + std::make_tuple(std::vector>{{-1, -2, -3}, {-4, -5, -6}, {-7, -8, -9}}, "matrix3x3negative"), + std::make_tuple(std::vector>{{1, 2}, {1, 2}, {1, 2}, {1, 2}}, "matrix4x2"), + std::make_tuple(std::vector>{{0}}, "matrix1x1zero"), + std::make_tuple(std::vector>{{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, "matrix3x3zero"), + std::make_tuple(std::vector>{{1, 10, 15, 19, 90, 3}}, "matrix1x6"), + std::make_tuple(std::vector>{{1037, 2385, 8543}, {286, 629, 1094}, {8306, 6290, 375}}, + "matrix3x3big_nums"), + std::make_tuple(std::vector>{{1037, -2385, 8543}, {-286, 629, -1094}, {8306, 6290, -375}}, + "matrix3x3big_nums_with_negative"), + std::make_tuple(std::vector>{{-5, 0, 7, -3, 10}}, "matrix1x5_mix"), + std::make_tuple(std::vector>{{1}, {2}, {-3}, {4}, {-5}}, "matrix5x1_column"), + std::make_tuple(std::vector>{{0, -1, 2, -3}, {4, 0, -5, 6}}, "matrix2x4_mix"), + std::make_tuple(std::vector>{{1, 2, 3, 4, 5}, {-1, 0, 7, -2, 3}, {10, -5, 8, 0, -4}}, + "matrix3x5_mix"), + std::make_tuple(std::vector>{{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}, "matrix1x10_wide"), + std::make_tuple(std::vector>{{1}, {1}, {1}, {1}, {1}, {1}, {1}, {1}, {1}, {1}}, "matrix10x1_tall"), + std::make_tuple(std::vector>{{5, 5}, {5, 5}}, "matrix2x2_same_values"), + std::make_tuple(std::vector>{{100000, -50000, 30000}, {20000, -10000, 5000}, {1, 2, 3}}, + "matrix3x3_large_positive_mixed")}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_sannikov_i_column_sum), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_sannikov_i_column_sum)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = SannikovIColumnSumFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(PicMatrixTests, SannikovIColumnSumFuncTests, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace sannikov_i_column_sum diff --git a/tasks/sannikov_i_column_sum/tests/performance/main.cpp b/tasks/sannikov_i_column_sum/tests/performance/main.cpp new file mode 100644 index 0000000000..f19d9b9bef --- /dev/null +++ b/tasks/sannikov_i_column_sum/tests/performance/main.cpp @@ -0,0 +1,52 @@ +#include + +#include +#include + +#include "sannikov_i_column_sum/common/include/common.hpp" +#include "sannikov_i_column_sum/mpi/include/ops_mpi.hpp" +#include "sannikov_i_column_sum/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" +namespace sannikov_i_column_sum { + +class SannikovIColumnSumPerfTests : public ppc::util::BaseRunPerfTests { + InType input_data_; + + void SetUp() override { + input_data_ = InType(10000, std::vector(10000)); + for (int i = 0; i < 10000; i++) { + for (int j = 0; j < 10000; j++) { + input_data_[i][j] = (i * 14) + (j * 21); + } + } + } + + bool CheckTestOutputData(OutType &output_data) final { + OutType sums_vec(input_data_.front().size(), 0); + for (std::size_t i = 0; i < input_data_.size(); i++) { + for (std::size_t j = 0; j < input_data_[i].size(); j++) { + sums_vec[j] += input_data_[i][j]; + } + } + return sums_vec == output_data; + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(SannikovIColumnSumPerfTests, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_sannikov_i_column_sum); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = SannikovIColumnSumPerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, SannikovIColumnSumPerfTests, kGtestValues, kPerfTestName); + +} // namespace sannikov_i_column_sum