diff --git a/tasks/zavyalov_a_scalar_product/common/include/common.hpp b/tasks/zavyalov_a_scalar_product/common/include/common.hpp new file mode 100644 index 0000000000..2633d09b7d --- /dev/null +++ b/tasks/zavyalov_a_scalar_product/common/include/common.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" + +namespace zavyalov_a_scalar_product { + +using InType = std::tuple, std::vector>; +using OutType = double; +using TestType = unsigned int; // size of vectors +using BaseTask = ppc::task::Task; + +} // namespace zavyalov_a_scalar_product diff --git a/tasks/zavyalov_a_scalar_product/info.json b/tasks/zavyalov_a_scalar_product/info.json new file mode 100644 index 0000000000..8dde370f88 --- /dev/null +++ b/tasks/zavyalov_a_scalar_product/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/zavyalov_a_scalar_product/mpi/include/ops_mpi.hpp b/tasks/zavyalov_a_scalar_product/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..054e42f609 --- /dev/null +++ b/tasks/zavyalov_a_scalar_product/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "task/include/task.hpp" +#include "zavyalov_a_scalar_product/common/include/common.hpp" + +namespace zavyalov_a_scalar_product { + +class ZavyalovAScalarProductMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit ZavyalovAScalarProductMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace zavyalov_a_scalar_product diff --git a/tasks/zavyalov_a_scalar_product/mpi/src/ops_mpi.cpp b/tasks/zavyalov_a_scalar_product/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..581603ae20 --- /dev/null +++ b/tasks/zavyalov_a_scalar_product/mpi/src/ops_mpi.cpp @@ -0,0 +1,97 @@ +#include "zavyalov_a_scalar_product/mpi/include/ops_mpi.hpp" + +#include + +#include + +#include "zavyalov_a_scalar_product/common/include/common.hpp" + +namespace zavyalov_a_scalar_product { + +ZavyalovAScalarProductMPI::ZavyalovAScalarProductMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank == 0) { + GetInput() = in; + } + GetOutput() = 0.0; +} + +bool ZavyalovAScalarProductMPI::ValidationImpl() { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank != 0) { + return true; + } + return (!std::get<0>(GetInput()).empty()) && (std::get<0>(GetInput()).size() == std::get<1>(GetInput()).size()); +} + +bool ZavyalovAScalarProductMPI::PreProcessingImpl() { + return true; +} +bool ZavyalovAScalarProductMPI::RunImpl() { + const double *left_data = nullptr; + const double *right_data = nullptr; + + int world_size = 0; + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + int vector_size = 0; + + if (rank == 0) { + GetOutput() = 0.0; + const auto &input = GetInput(); + if (!std::get<0>(input).empty()) { // it does not compile in ubuntu without this line + left_data = std::get<0>(input).data(); + } + if (!std::get<1>(input).empty()) { // it does not compile in ubuntu without this line + right_data = std::get<1>(input).data(); + } + vector_size = static_cast(std::get<0>(input).size()); + } + + MPI_Bcast(&vector_size, 1, MPI_INT, 0, MPI_COMM_WORLD); + + std::vector sendcounts(world_size); + std::vector displs(world_size); + + int blocksize = vector_size / world_size; + int elements_left = vector_size - (world_size * blocksize); + int elements_processed = 0; + + for (int i = 0; i < world_size; i++) { + sendcounts[i] = blocksize + (i < elements_left ? 1 : 0); + displs[i] = elements_processed; + elements_processed += sendcounts[i]; + } + + int elements_count = sendcounts[rank]; + std::vector local_left(elements_count); + std::vector local_right(elements_count); + + MPI_Scatterv(left_data, sendcounts.data(), displs.data(), MPI_DOUBLE, local_left.data(), elements_count, MPI_DOUBLE, + 0, MPI_COMM_WORLD); + MPI_Scatterv(right_data, sendcounts.data(), displs.data(), MPI_DOUBLE, local_right.data(), elements_count, MPI_DOUBLE, + 0, MPI_COMM_WORLD); + + double cur_res = 0.0; + for (int i = 0; i < elements_count; i++) { + cur_res += local_left[i] * local_right[i]; + } + + double glob_res = 0.0; + MPI_Allreduce(&cur_res, &glob_res, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD); + + GetOutput() = glob_res; + + return true; +} + +bool ZavyalovAScalarProductMPI::PostProcessingImpl() { + return true; +} + +} // namespace zavyalov_a_scalar_product diff --git a/tasks/zavyalov_a_scalar_product/report.md b/tasks/zavyalov_a_scalar_product/report.md new file mode 100644 index 0000000000..4bb0761326 --- /dev/null +++ b/tasks/zavyalov_a_scalar_product/report.md @@ -0,0 +1,66 @@ +# Скалярное произведение векторов + +- Student: Завьялов Алексей Алексеевич, group 3823Б1ФИ3 +- Technology: SEQ | MPI +- Variant: 9 + +## 1. Introduction + +Скалярное произведение имеет широкую область применения в современном мире, в связи с чем возникает необходимость оптимизировать процесс его вычисления. + +Ожидается небольшое ускорение mpi версии программы относительно последовательной версии. + +## 2. Problem Statement +Скалярное произведение двух векторов одной размерности - это число, равное сумме произведений соответствующих координат этих векторов. +Требуется найти скалярное произведение двух заданных векторов. + +```math +(a, b) = \sum_{i=0}^{n-1} a_i b_i. +``` + +### Входные данные: +Два математических вектора произвольной размерности, состоящих из действительных чисел. + +### Выходные данные: +Одно действительное число - скалярное произведение заданных векторов. + +## 3. Baseline Algorithm (Sequential) +Инициализируем результат значением 0. Затем последовательно проходим по векторам, добавляя к результату произведение соответствующих координат. + +## 4. Parallelization Scheme +Делим размер вектора нацело на `n`, где `n` - число процессов, получаем размер блока. + +Корневой процесс рассылает остальным соответствующие части обоих векторов. В случае, когда размер вектора не кратен n, то есть когда при делении остаётся остаток `remainder`, не равный 0, первым `remainder` процессам отсылается на 1 элемент каждого вектора больше. Этот этап реализуется с помощью функции `MPI_Scatterv()` + +Далее каждый процесс вычисляет свою часть слагаемых, составляющих скалярное произведение. + +В конце работы результаты всех процессов складываются и записываются в ответ с помощью функции `MPI_Allreduce()`. + +## 5. Experimental Setup +- Hardware/OS: AMD Ryzen 5 7520U, 4 ядра, 16 GB RAM, Windows 10 x64 +- Toolchain: + - Cmake 3.28.3 + - Компилятор: g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 + - Использовался Docker-контейнер. + - Режим сборки: Release. +- Data: Для замера производительности использовались векторы размером 40.000.000: + - Первый вектор состоит из натуральных чисел от 0 до 39.999.999 включительно. + - Второй вектор состоит из чётных натуральных чисел от 0 до 79.999.998 включительно. + +## 6. Results and Discussion + +### 6.1 Correctness +Корректность работы проверена с помощью тестов Google Test на парах векторов размером от 1 до 10 включительно. + +### 6.2 Performance +| Mode | Count | Time, s | Speedup | Efficiency | +|-------------|-------|---------|---------|------------| +| seq | 1 | 0.070 | 1.00 | N/A | +| mpi | 2 | 0.737 | 0.09 | 4.5% | +| mpi | 4 | 0.599 | 0.11 | 2.75% | + +## 7. Conclusions +Накладные расходы на работу с процессами и рассылка данных занимают большую часть времени работы программы, из-за чего эффективность mpi версии значительно меньше 100%. + +## 8. References +1. Курс лекций ННГУ "Параллельное программирование для кластерных систем" \ No newline at end of file diff --git a/tasks/zavyalov_a_scalar_product/seq/include/ops_seq.hpp b/tasks/zavyalov_a_scalar_product/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..308979777e --- /dev/null +++ b/tasks/zavyalov_a_scalar_product/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "task/include/task.hpp" +#include "zavyalov_a_scalar_product/common/include/common.hpp" + +namespace zavyalov_a_scalar_product { + +class ZavyalovAScalarProductSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit ZavyalovAScalarProductSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace zavyalov_a_scalar_product diff --git a/tasks/zavyalov_a_scalar_product/seq/src/ops_seq.cpp b/tasks/zavyalov_a_scalar_product/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..4e1528b0c5 --- /dev/null +++ b/tasks/zavyalov_a_scalar_product/seq/src/ops_seq.cpp @@ -0,0 +1,41 @@ +#include "zavyalov_a_scalar_product/seq/include/ops_seq.hpp" + +#include +#include + +#include "zavyalov_a_scalar_product/common/include/common.hpp" + +namespace zavyalov_a_scalar_product { + +ZavyalovAScalarProductSEQ::ZavyalovAScalarProductSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0.0; +} + +bool ZavyalovAScalarProductSEQ::ValidationImpl() { + return (!std::get<0>(GetInput()).empty()) && (std::get<0>(GetInput()).size() == std::get<1>(GetInput()).size()); +} + +bool ZavyalovAScalarProductSEQ::PreProcessingImpl() { + return true; +} + +bool ZavyalovAScalarProductSEQ::RunImpl() { + const auto &input = GetInput(); + const std::vector &left = std::get<0>(input); + const std::vector &right = std::get<1>(input); + + double res = 0.0; + for (size_t i = 0; i < left.size(); i++) { + res += left[i] * right[i]; + } + GetOutput() = res; + return true; +} + +bool ZavyalovAScalarProductSEQ::PostProcessingImpl() { + return true; +} + +} // namespace zavyalov_a_scalar_product diff --git a/tasks/zavyalov_a_scalar_product/settings.json b/tasks/zavyalov_a_scalar_product/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/zavyalov_a_scalar_product/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/zavyalov_a_scalar_product/tests/.clang-tidy b/tasks/zavyalov_a_scalar_product/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/zavyalov_a_scalar_product/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/zavyalov_a_scalar_product/tests/functional/main.cpp b/tasks/zavyalov_a_scalar_product/tests/functional/main.cpp new file mode 100644 index 0000000000..88ca31dff3 --- /dev/null +++ b/tasks/zavyalov_a_scalar_product/tests/functional/main.cpp @@ -0,0 +1,81 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" +#include "zavyalov_a_scalar_product/common/include/common.hpp" +#include "zavyalov_a_scalar_product/mpi/include/ops_mpi.hpp" +#include "zavyalov_a_scalar_product/seq/include/ops_seq.hpp" + +namespace zavyalov_a_scalar_product { + +class ZavyalovAScalarProductFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::to_string(test_param); + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + std::vector left_vec(params); + std::vector right_vec(params); + + double minus = -1.0; + for (unsigned int i = 0; i < params; i++) { + left_vec[i] = (i * 0.5) + 0.1; + right_vec[i] = static_cast(i) + 1.0; + right_vec[i] *= minus; + minus *= -1.0; + } + + input_data_ = std::make_tuple(left_vec, right_vec); + } + + bool CheckTestOutputData(OutType &output_data) final { + double res = 0.0; + for (size_t i = 0; i < std::get<0>(input_data_).size(); i++) { + res += std::get<0>(input_data_)[i] * std::get<1>(input_data_)[i]; + } + double diff = fabs(res - output_data); + double epsilon = 1e-9 * (1 + std::max(fabs(res), fabs(output_data))); + return diff < epsilon; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +namespace { + +TEST_P(ZavyalovAScalarProductFuncTests, MatmulFromPic) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = {1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_zavyalov_a_scalar_product), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_zavyalov_a_scalar_product)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = ZavyalovAScalarProductFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(PicMatrixTests, ZavyalovAScalarProductFuncTests, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace zavyalov_a_scalar_product diff --git a/tasks/zavyalov_a_scalar_product/tests/performance/main.cpp b/tasks/zavyalov_a_scalar_product/tests/performance/main.cpp new file mode 100644 index 0000000000..5206c1b6e9 --- /dev/null +++ b/tasks/zavyalov_a_scalar_product/tests/performance/main.cpp @@ -0,0 +1,60 @@ +#include + +#include +#include +#include +#include +#include + +#include "util/include/perf_test_util.hpp" +#include "zavyalov_a_scalar_product/common/include/common.hpp" +#include "zavyalov_a_scalar_product/mpi/include/ops_mpi.hpp" +#include "zavyalov_a_scalar_product/seq/include/ops_seq.hpp" + +namespace zavyalov_a_scalar_product { + +class ZavyalovAScalarProductPerfTestProcesses : public ppc::util::BaseRunPerfTests { + const uint64_t kCount_ = 100000000ULL; + InType input_data_; + + void SetUp() override { + std::vector left_vec(kCount_); + std::vector right_vec(kCount_); + + for (uint64_t i = 0; i < kCount_; i++) { + left_vec[i] = static_cast(i); + right_vec[i] = static_cast(i * 2ULL); + } + + input_data_ = std::make_tuple(left_vec, right_vec); + } + + bool CheckTestOutputData(OutType &output_data) final { + double res = 0.0; + for (uint64_t i = 0; i < kCount_; i++) { + res += std::get<0>(input_data_)[i] * std::get<1>(input_data_)[i]; + } + double diff = fabs(res - output_data); + double epsilon = 1e-9 * (1 + std::max(fabs(res), fabs(output_data))); + return diff < epsilon; + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(ZavyalovAScalarProductPerfTestProcesses, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_zavyalov_a_scalar_product); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = ZavyalovAScalarProductPerfTestProcesses::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, ZavyalovAScalarProductPerfTestProcesses, kGtestValues, kPerfTestName); + +} // namespace zavyalov_a_scalar_product