diff --git a/tasks/luzan_e_matrix_rows_sum/common/include/common.hpp b/tasks/luzan_e_matrix_rows_sum/common/include/common.hpp new file mode 100644 index 0000000000..93163bba16 --- /dev/null +++ b/tasks/luzan_e_matrix_rows_sum/common/include/common.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" + +namespace luzan_e_matrix_rows_sum { + +using InType = std::tuple, int, int>; // matrix, height, width +using OutType = std::vector; // vec of sums, size = height +using TestType = std::tuple; // height & width +using BaseTask = ppc::task::Task; + +} // namespace luzan_e_matrix_rows_sum diff --git a/tasks/luzan_e_matrix_rows_sum/info.json b/tasks/luzan_e_matrix_rows_sum/info.json new file mode 100644 index 0000000000..2e00aba5f2 --- /dev/null +++ b/tasks/luzan_e_matrix_rows_sum/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Егор", + "last_name": "Лузан", + "middle_name": "Андреевич", + "group_number": "3823Б1ФИ3", + "task_number": "1" + } +} diff --git a/tasks/luzan_e_matrix_rows_sum/mpi/include/ops_mpi.hpp b/tasks/luzan_e_matrix_rows_sum/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..ba0449922d --- /dev/null +++ b/tasks/luzan_e_matrix_rows_sum/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "luzan_e_matrix_rows_sum/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace luzan_e_matrix_rows_sum { + +class LuzanEMatrixRowsSumMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit LuzanEMatrixRowsSumMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace luzan_e_matrix_rows_sum diff --git a/tasks/luzan_e_matrix_rows_sum/mpi/src/ops_mpi.cpp b/tasks/luzan_e_matrix_rows_sum/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..fcc40624f0 --- /dev/null +++ b/tasks/luzan_e_matrix_rows_sum/mpi/src/ops_mpi.cpp @@ -0,0 +1,130 @@ +#include "luzan_e_matrix_rows_sum/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include + +#include "luzan_e_matrix_rows_sum/common/include/common.hpp" + +namespace luzan_e_matrix_rows_sum { + +LuzanEMatrixRowsSumMPI::LuzanEMatrixRowsSumMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetOutput() = {}; + + // saving matrix only if it's rank=0 + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + if (rank == 0) { + GetInput() = in; + } else { + GetInput() = {}; + } +} + +bool LuzanEMatrixRowsSumMPI::ValidationImpl() { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank != 0) { + return true; + } + + int height = std::get<1>(GetInput()); + int width = std::get<2>(GetInput()); + return std::get<0>(GetInput()).size() == static_cast(height) * static_cast(width) && height > 0 && + width > 0; +} + +bool LuzanEMatrixRowsSumMPI::PreProcessingImpl() { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank != 0) { + return true; + } + + int height = std::get<1>(GetInput()); + GetOutput().resize(height); + for (int row = 0; row < height; row++) { + GetOutput()[row] = 0; + } + return true; +} + +bool LuzanEMatrixRowsSumMPI::RunImpl() { + // mpi things + int height = 0; + int width = 0; + std::tuple_element_t<0, InType> mat; + + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + // getting input matrix on rank=0 + // getting & sharing matrix sizes + std::vector dim(2, 0); + if (rank == 0) { + mat = std::get<0>(GetInput()); + height = std::get<1>(GetInput()); + width = std::get<2>(GetInput()); + } + MPI_Bcast(&height, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&width, 1, MPI_INT, 0, MPI_COMM_WORLD); + + // calcilating shifts & rows_per_proc (only about rows rigth now) + int rest = height % size; + std::vector shift(size, 0); + std::vector per_proc(size, height / size); // rows per proc + + int accumulator = 0; + for (int i = 0; i < size; i++) { + if (rest > 0) { + per_proc[i]++; + rest--; + } + shift[i] = accumulator; + accumulator = per_proc[i] + shift[i]; + } + + // preparing to recieve data + std::vector recv(static_cast(per_proc[rank] * width)); + + for (int i = 0; i < size; i++) { + per_proc[i] *= width; // now it's about elements + shift[i] *= width; + } + MPI_Scatterv(mat.data(), per_proc.data(), shift.data(), MPI_INT, recv.data(), per_proc[rank], MPI_INT, 0, + MPI_COMM_WORLD); + mat.clear(); // no need anymore + + // calculating + std::vector rows_sum(static_cast(per_proc[rank] / width)); // sums + int rows_to_calc = static_cast(per_proc[rank] / width); + for (int row = 0; row < rows_to_calc; row++) { + for (int col = 0; col < width; col++) { + rows_sum[row] += recv[(row * width) + col]; + } + } + + for (int i = 0; i < size; i++) { + per_proc[i] /= width; // back to rows + shift[i] /= width; + } + + std::vector fin_sum(height); + MPI_Gatherv(rows_sum.data(), rows_to_calc, MPI_INT, fin_sum.data(), per_proc.data(), shift.data(), MPI_INT, 0, + MPI_COMM_WORLD); + MPI_Bcast(fin_sum.data(), height, MPI_INT, 0, MPI_COMM_WORLD); + GetOutput() = fin_sum; + return true; +} + +bool LuzanEMatrixRowsSumMPI::PostProcessingImpl() { + return true; +} + +} // namespace luzan_e_matrix_rows_sum diff --git a/tasks/luzan_e_matrix_rows_sum/report.md b/tasks/luzan_e_matrix_rows_sum/report.md new file mode 100644 index 0000000000..20c0c40323 --- /dev/null +++ b/tasks/luzan_e_matrix_rows_sum/report.md @@ -0,0 +1,87 @@ +# <Сумма значений по строкам матрицы> + +- Student: Лузан Егор Андреевич, group 3823Б1ФИ3 +- Technology: SEQ | MPI +- Variant: 11 + +## 1. Introduction +Цель работы — реализовать и сравнить две версии программы, вычисляющей сумму элементов в каждой строке матрицы: +1. последовательную, +2. параллельную, использующую библиотеку **MPI**. + +Подобная задача встречается при обработке больших таблиц данных, изображений, численных расчётах. + +Ожидается ускорение mpi версии относительно последовательной версии. + +## 2. Problem Statement +Дана матрица A размера `height × width`. +Требуется вычислить вектор $sum$ размера `width`, где: +$$sum[i] = \sum_{j=0}^{width-1} A_{i\_j}$$ + +**Входные данные:** +- Матрица в форме вектора `(std::vector)` размера `height × width` состоящая из целых чисел. + +**Выходные данные**: +- Вектор `(std::vector)` размера `height` состоящий из целых чисел, каждое из которых является суммой строки матрицы. + +**Ограничения:** +- элементы матрицы — целые числа. +- элементы матрицы лежат в диапазоне -`[0; 42000]`. + +## 3. Baseline Algorithm (Sequential) +- Инициализация всех элементов выходного вектора нулями. +- Последовательный обход всех строк исходной матрицы, производя суммирование элементов в них и записывая результат в соответствующий элемент выходного вектора. + +## 4. Parallelization Scheme +**Распределение данных:** +- Для каждого процесса: + 1. Делим высоту матрицы нацело на количество процессов, получая `rows_per_proc` - минимальное количество строк для обработки каждый процессом. + 2. Получаем остаток `rest` от предыдущего деления. + 3. Раскладываем оставшиеся `rest` строк по процессам по одной, начиная с `rank=0`. Также вычисляем `shift` - массив смещений, который будет использован при рассылке данных с корневого процесса. + 4. Таким образом первые `rest` процессов обработают `rows_per_proc` + 1 строку, остальные `rows_per_proc`. + +**Роли рангов:** +- Root-процесс получает матрицу и её размеры. +- Root-процесс рассылает остальным процессам части матрицы для вычислений. +- Во время вычисления сумм процессы всех рангов имеют одинаковые задачи. +- После вычисления итоговый результат будет записан в процессе `rank = 0` с помощью `MPI_Reduce()`. + +## 5. Implementation Details +- Для тестов производительности матрица генерируется внутри программы. Результат заранее предсказуем. +- Загрузка матрицы производиться только на root-процесс для экономии памяти. +- Работа программы проверена на матрицах на матрицах состоящих только из одного столбца/строки, на матрицы из одного элемента. + +## 6. Experimental Setup +- Hardware/OS: + - CPU: Intel Core i7-13620H; P-cores-6, E-cores-4. + - RAM: 16 GB RAM. + - OS: Windows 11, x64. +- Toolchain: + - Cmake 3.28.3. + - Компилятор: gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0. + - Использовался Docker-контейнер с Ubuntu 24.04.2 LTS. + - Режим сборки: Release. + +- Data: Для замера производительности использовалась матрица размером 14 000 × 14 000, элементы которой представляют собой целые последовательные числа от 0 до 42 000. + +## 7. Results and Discussion + +### 7.1 Correctness +Корректность работы проверена с помощью GoogleTest на матрицах размерностями 3×3, 2×5, 10×70, 2000×5, 5×2000, 1×1. + +### 7.2 Performance + +| Mode | Count | Time, s | Speedup | Efficiency | +| ---- | ----- | ------------------ | ------- | ---------- | +| seq | 1 | 0,06262 | 1.00 | N/A | +| mpi | 2 | 0,71973 | 0.08 | 4% | +| mpi | 4 | 0,60037 | 0.10 | 2% | + +## 8. Conclusions + +Разработана программа, выполняющая поиск сумм строк матрицы. Также разработана её параллельная версия с использованием MPI. +Эффективность параллельного очень низкая из-за высоких затрат на пересылку данных и создание процессов. + +## 9. References +1. Курс лекций ННГУ "Параллельное программирование для кластерных систем". +2. Стандарт MPI. \ No newline at end of file diff --git a/tasks/luzan_e_matrix_rows_sum/seq/include/ops_seq.hpp b/tasks/luzan_e_matrix_rows_sum/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..b7d19c931b --- /dev/null +++ b/tasks/luzan_e_matrix_rows_sum/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "luzan_e_matrix_rows_sum/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace luzan_e_matrix_rows_sum { + +class LuzanEMatrixRowsSumSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit LuzanEMatrixRowsSumSEQ(const InType &in); + + private: + bool ValidationImpl() override; //= input data check + bool PreProcessingImpl() override; //= pre proc + bool RunImpl() override; //= PARALLEL + bool PostProcessingImpl() override; //= back to reality +}; + +} // namespace luzan_e_matrix_rows_sum diff --git a/tasks/luzan_e_matrix_rows_sum/seq/src/ops_seq.cpp b/tasks/luzan_e_matrix_rows_sum/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..f2af5b681d --- /dev/null +++ b/tasks/luzan_e_matrix_rows_sum/seq/src/ops_seq.cpp @@ -0,0 +1,51 @@ +#include "luzan_e_matrix_rows_sum/seq/include/ops_seq.hpp" + +#include +#include +#include + +#include "luzan_e_matrix_rows_sum/common/include/common.hpp" + +namespace luzan_e_matrix_rows_sum { + +LuzanEMatrixRowsSumSEQ::LuzanEMatrixRowsSumSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = {}; +} + +bool LuzanEMatrixRowsSumSEQ::ValidationImpl() { + int height = std::get<1>(GetInput()); + int width = std::get<2>(GetInput()); + + return std::get<0>(GetInput()).size() == static_cast(height) * static_cast(width) && height > 0 && + width > 0; +} + +bool LuzanEMatrixRowsSumSEQ::PreProcessingImpl() { + int height = std::get<1>(GetInput()); + GetOutput().resize(height); + for (int row = 0; row < height; row++) { + GetOutput()[row] = 0; + } + return true; +} + +bool LuzanEMatrixRowsSumSEQ::RunImpl() { + int height = std::get<1>(GetInput()); + int width = std::get<2>(GetInput()); + const std::tuple_element_t<0, InType> &mat = std::get<0>(GetInput()); + + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + GetOutput()[row] += mat[(width * row) + col]; + } + } + return true; +} + +bool LuzanEMatrixRowsSumSEQ::PostProcessingImpl() { + return true; +} + +} // namespace luzan_e_matrix_rows_sum diff --git a/tasks/luzan_e_matrix_rows_sum/settings.json b/tasks/luzan_e_matrix_rows_sum/settings.json new file mode 100644 index 0000000000..7d2c35b298 --- /dev/null +++ b/tasks/luzan_e_matrix_rows_sum/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/luzan_e_matrix_rows_sum/tests/.clang-tidy b/tasks/luzan_e_matrix_rows_sum/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/luzan_e_matrix_rows_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/luzan_e_matrix_rows_sum/tests/functional/main.cpp b/tasks/luzan_e_matrix_rows_sum/tests/functional/main.cpp new file mode 100644 index 0000000000..178caa494e --- /dev/null +++ b/tasks/luzan_e_matrix_rows_sum/tests/functional/main.cpp @@ -0,0 +1,84 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "luzan_e_matrix_rows_sum/common/include/common.hpp" +#include "luzan_e_matrix_rows_sum/mpi/include/ops_mpi.hpp" +#include "luzan_e_matrix_rows_sum/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace luzan_e_matrix_rows_sum { + +class LuzanEMatrixRowsSumFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::to_string(std::get<0>(test_param)) + "_" + std::to_string(std::get<1>(test_param)); + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + int height = std::get<0>(params); + int width = std::get<1>(params); + std::tuple_element_t<0, InType> mat(static_cast(height) * static_cast(width)); + + for (int elem = 0; elem < height * width; elem++) { + mat[elem] = (elem * 2) - 42; + } + + input_data_ = std::make_tuple(mat, height, width); + } + + bool CheckTestOutputData(OutType &output_data) final { + int height = std::get<1>(input_data_); + int width = std::get<2>(input_data_); + std::vector sum(height, 0); + std::tuple_element_t<0, InType> mat = std::get<0>(input_data_); + + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + sum[row] += mat[(width * row) + col]; + } + } + + return (output_data == sum); + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +namespace { + +TEST_P(LuzanEMatrixRowsSumFuncTests, MatmulFromPic) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + std::make_tuple(3, 3), std::make_tuple(2, 5), std::make_tuple(10, 70), + std::make_tuple(2000, 5), std::make_tuple(5, 2000), std::make_tuple(1, 1), + std::make_tuple(1, 100), std::make_tuple(100, 1), std::make_tuple(1000, 1000)}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_luzan_e_matrix_rows_sum), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_luzan_e_matrix_rows_sum)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = LuzanEMatrixRowsSumFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(PicMatrixTests, LuzanEMatrixRowsSumFuncTests, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace luzan_e_matrix_rows_sum diff --git a/tasks/luzan_e_matrix_rows_sum/tests/performance/main.cpp b/tasks/luzan_e_matrix_rows_sum/tests/performance/main.cpp new file mode 100644 index 0000000000..e7529440c6 --- /dev/null +++ b/tasks/luzan_e_matrix_rows_sum/tests/performance/main.cpp @@ -0,0 +1,59 @@ +#include + +#include +#include +#include + +#include "luzan_e_matrix_rows_sum/common/include/common.hpp" +#include "luzan_e_matrix_rows_sum/mpi/include/ops_mpi.hpp" +#include "luzan_e_matrix_rows_sum/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace luzan_e_matrix_rows_sum { + +class LuzanEMatrixRowsSumpERFTests : public ppc::util::BaseRunPerfTests { + const int height_ = 12500; + const int width_ = 12500; + InType input_data_; + + void SetUp() override { + std::tuple_element_t<0, InType> mat(static_cast(height_) * static_cast(width_)); + for (int elem = 0; elem < height_ * width_; elem++) { + mat[elem] = elem % 42000; + } + + input_data_ = std::make_tuple(mat, height_, width_); + } + + bool CheckTestOutputData(OutType &output_data) final { + std::vector sum(height_); + std::tuple_element_t<0, InType> mat = std::get<0>(input_data_); + + for (int row = 0; row < height_; row++) { + for (int col = 0; col < width_; col++) { + sum[row] += mat[(width_ * row) + col]; + } + } + + return (output_data == sum); + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(LuzanEMatrixRowsSumpERFTests, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_luzan_e_matrix_rows_sum); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = LuzanEMatrixRowsSumpERFTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, LuzanEMatrixRowsSumpERFTests, kGtestValues, kPerfTestName); + +} // namespace luzan_e_matrix_rows_sum