diff --git a/tasks/ovsyannikov_n_num_mistm_in_two_str/common/include/common.hpp b/tasks/ovsyannikov_n_num_mistm_in_two_str/common/include/common.hpp new file mode 100644 index 000000000..d3f945714 --- /dev/null +++ b/tasks/ovsyannikov_n_num_mistm_in_two_str/common/include/common.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include +#include + +#include "task/include/task.hpp" + +namespace ovsyannikov_n_num_mistm_in_two_str { +using InType = std::pair; // Входные данные: пара строк +using OutType = size_t; // Выходные данные: количество несовпадений +using BaseTask = ppc::task::Task; +using TestType = std::tuple; +} // namespace ovsyannikov_n_num_mistm_in_two_str diff --git a/tasks/ovsyannikov_n_num_mistm_in_two_str/info.json b/tasks/ovsyannikov_n_num_mistm_in_two_str/info.json new file mode 100644 index 000000000..3fafc91c2 --- /dev/null +++ b/tasks/ovsyannikov_n_num_mistm_in_two_str/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Никита", + "last_name": "Овсянников", + "middle_name": "Владимирович", + "group_number": "3823Б1ФИ2", + "task_number": "27" + } +} diff --git a/tasks/ovsyannikov_n_num_mistm_in_two_str/mpi/include/ops_mpi.hpp b/tasks/ovsyannikov_n_num_mistm_in_two_str/mpi/include/ops_mpi.hpp new file mode 100644 index 000000000..626aaa6ac --- /dev/null +++ b/tasks/ovsyannikov_n_num_mistm_in_two_str/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "ovsyannikov_n_num_mistm_in_two_str/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace ovsyannikov_n_num_mistm_in_two_str { + +class OvsyannikovNNumMistmInTwoStrMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit OvsyannikovNNumMistmInTwoStrMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace ovsyannikov_n_num_mistm_in_two_str diff --git a/tasks/ovsyannikov_n_num_mistm_in_two_str/mpi/src/ops_mpi.cpp b/tasks/ovsyannikov_n_num_mistm_in_two_str/mpi/src/ops_mpi.cpp new file mode 100644 index 000000000..5cc1c7c27 --- /dev/null +++ b/tasks/ovsyannikov_n_num_mistm_in_two_str/mpi/src/ops_mpi.cpp @@ -0,0 +1,95 @@ +#include "ovsyannikov_n_num_mistm_in_two_str/mpi/include/ops_mpi.hpp" + +#include + +#include + +#include "ovsyannikov_n_num_mistm_in_two_str/common/include/common.hpp" + +namespace ovsyannikov_n_num_mistm_in_two_str { + +OvsyannikovNNumMistmInTwoStrMPI::OvsyannikovNNumMistmInTwoStrMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool OvsyannikovNNumMistmInTwoStrMPI::ValidationImpl() { + int proc_rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &proc_rank); + if (proc_rank == 0) { + return GetInput().first.size() == GetInput().second.size(); + } + return true; +} + +bool OvsyannikovNNumMistmInTwoStrMPI::PreProcessingImpl() { + GetOutput() = 0; + return true; +} + +bool OvsyannikovNNumMistmInTwoStrMPI::RunImpl() { + int proc_rank = 0; + int proc_num = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &proc_rank); + MPI_Comm_size(MPI_COMM_WORLD, &proc_num); + + int total_len = 0; + if (proc_rank == 0) { + total_len = static_cast(GetInput().first.size()); + } + + MPI_Bcast(&total_len, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (total_len == 0) { + return true; + } + + std::vector counts(proc_num); + std::vector displs(proc_num); + + int tail = total_len % proc_num; + int accum = 0; + for (int i = 0; i < proc_num; i++) { + counts[i] = (total_len / proc_num) + (i < tail ? 1 : 0); + displs[i] = accum; + accum += counts[i]; + } + + int my_count = counts[proc_rank]; + std::vector local_str_1(my_count); + std::vector local_str_2(my_count); + + const char *send_buf_1 = nullptr; + const char *send_buf_2 = nullptr; + + if (proc_rank == 0) { + send_buf_1 = GetInput().first.data(); + send_buf_2 = GetInput().second.data(); + } + + MPI_Scatterv(send_buf_1, counts.data(), displs.data(), MPI_CHAR, local_str_1.data(), my_count, MPI_CHAR, 0, + MPI_COMM_WORLD); + + MPI_Scatterv(send_buf_2, counts.data(), displs.data(), MPI_CHAR, local_str_2.data(), my_count, MPI_CHAR, 0, + MPI_COMM_WORLD); + + int priv_err_cnt = 0; + for (int i = 0; i < my_count; ++i) { + if (local_str_1[i] != local_str_2[i]) { + priv_err_cnt++; + } + } + + int total_err_cnt = 0; + MPI_Allreduce(&priv_err_cnt, &total_err_cnt, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD); + GetOutput() = total_err_cnt; + + return true; +} + +bool OvsyannikovNNumMistmInTwoStrMPI::PostProcessingImpl() { + return true; +} + +} // namespace ovsyannikov_n_num_mistm_in_two_str diff --git a/tasks/ovsyannikov_n_num_mistm_in_two_str/report.md b/tasks/ovsyannikov_n_num_mistm_in_two_str/report.md new file mode 100644 index 000000000..d4a3be85e --- /dev/null +++ b/tasks/ovsyannikov_n_num_mistm_in_two_str/report.md @@ -0,0 +1,87 @@ +# Количество несовпадений символов двух строк + +- Студент: Овсянников Никита Владимирович, группа 3823Б1ФИ2 +- Технология: SEQ, MPI +- Вариант: 27 + +## 1. Introduction + +Цель данной работы — научиться применять технологию MPI для распараллеливания задач. В качестве примера выбрана задача сравнения двух строк. Хотя сама по себе задача простая, на ней удобно разбирать, как делить данные между процессами и собирать общий результат. + +## 2. Problem Statement + +У нас есть две строки одинаковой длины. Нужно пройтись по ним и посчитать, сколько раз символы на одной и той же позиции не совпадают. + +**Входные данные:** Две строки (`std::string`). +**Выходные данные:** Целое число — количество несовпадений. + +## 3. Baseline Algorithm (Sequential) + +В последовательной версии мы просто бежим циклом от начала до конца строки. + +```cpp +int mismatch_count = 0; +size_t n = seq_one.size(); +for (size_t i = 0; i < n; ++i) { + if (seq_one[i] != seq_two[i]) { + mismatch_count++; + } +} +``` +Сложность линейная - O(N). + +## 4. Parallelization Scheme +Процесс с рангом 0 определяет размер данных. Исходные строки делятся на части по количеству процессов. Ранг 0 рассылает каждому процессу его куски двух строк (используется Scatterv). Каждый процесс независимо сравнивает символы в полученных частях. Локальные счетчики несовпадений суммируются в общий результат (Allreduce). + +## 5. Implementation Details +- common: Определяет общие типы данных. +- seq: Последовательная реализация (обычный цикл). +- mpi: Параллельная реализация. Использует векторы для упаковки данных перед отправкой. +- tests: Тесты на корректность (разные случаи несовпадений) и на скорость. + +## 6. Experimental Setup +- Аппаратное обеспечение: 13th Gen Intel Core i5-13500H (12 ядер: 4P + 8E, 2.60 GHz) +- ОЗУ: 16 ГБ (4266 MT/s) +- Операционная система: Windows 11 +- Компилятор: MSVC +- Тип сборки: Release + +## 7. Results and Discussion + +### 7.1 Correctness +Корректность алгоритма подтверждена функциональными тестами. Результаты MPI-версии идентичны результатам последовательной версии на всех тестовых наборах данных (пустые строки, полное совпадение, частичное совпадение). + +### 7.2 Performance + +Тестирование проводилось на строках длиной 100 000 000 символов. +За базовое время ($T_{seq}$) взято время работы последовательной реализации: **0.1734 s**. + +| Mode | Processes | Time (s) | Speedup | Efficiency | +|:----:|:---------:|:--------:|:-------:|:----------:| +| seq | 1 | 0.1734 | 1.00 | 100% | +| mpi | 2 | 0.3410 | 0.51 | 25.5% | +| mpi | 4 | 0.3304 | 0.52 | 13.0% | +| mpi | 8 | 0.2650 | 0.65 | 8.1% | +| mpi | 12 | 0.3064 | 0.56 | 4.7% | +| mpi | 24 | 0.8192 | 0.21 | 0.9% | + +## 8. Conclusions + +Анализ результатов экспериментов позволяет сделать следующие выводы: + +1. **Отрицательное ускорение (Slowdown):** + Параллельная версия (MPI) во всех случаях работает медленнее последовательной (Speedup < 1). Это объясняется природой задачи: + * **Низкая вычислительная сложность:** Операция сравнения двух символов (`char == char`) выполняется процессором за один такт и экстремально быстра. + * **Доминирование накладных расходов:** Время, затрачиваемое на передачу данных через `MPI_Scatterv` и инициализацию процессов, значительно превышает время полезных вычислений. Даже оптимизация с прямым доступом к памяти (без лишних буферов) не позволяет перекрыть латентность межпроцессного взаимодействия. + +2. **Динамика масштабируемости:** + * На 2-4 процессах накладные расходы высоки, ускорение составляет около 0.5. + * Наилучший результат достигнут на **8 процессах** (0.2650 s), где накладные расходы на коммуникацию лучше всего балансируются с уменьшением объема работы на каждом ядре. Однако даже этот результат уступает последовательной версии. + +3. **Эффект переподписки (Oversubscription):** + При запуске на **24 процессах** наблюдается резкое падение производительности (время выросло до 0.8192 s). Это связано с тем, что количество MPI-процессов превышает количество физических потоков процессора (Intel Core i5-13500H имеет 12 ядер/16 потоков). Планировщик ОС вынужден постоянно переключать контекст (Context Switching) между процессами, что убивает производительность и кэш-локальность. + +**Итог:** Технология MPI плохо подходит для задач с тривиальными вычислениями и большим объемом передаваемых данных на системах с общей памятью. Для таких задач эффективнее использовать многопоточность (OpenMP/TBB) или векторизацию (SIMD), так как они не требуют дорогостоящей пересылки данных между адресными пространствами. + +## 9. References +1. Лекции и практики курса "Параллельное программирование". \ No newline at end of file diff --git a/tasks/ovsyannikov_n_num_mistm_in_two_str/seq/include/ops_seq.hpp b/tasks/ovsyannikov_n_num_mistm_in_two_str/seq/include/ops_seq.hpp new file mode 100644 index 000000000..f7cea27e3 --- /dev/null +++ b/tasks/ovsyannikov_n_num_mistm_in_two_str/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "ovsyannikov_n_num_mistm_in_two_str/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace ovsyannikov_n_num_mistm_in_two_str { + +class OvsyannikovNNumMistmInTwoStrSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit OvsyannikovNNumMistmInTwoStrSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace ovsyannikov_n_num_mistm_in_two_str diff --git a/tasks/ovsyannikov_n_num_mistm_in_two_str/seq/src/ops_seq.cpp b/tasks/ovsyannikov_n_num_mistm_in_two_str/seq/src/ops_seq.cpp new file mode 100644 index 000000000..641190f1d --- /dev/null +++ b/tasks/ovsyannikov_n_num_mistm_in_two_str/seq/src/ops_seq.cpp @@ -0,0 +1,45 @@ +#include "ovsyannikov_n_num_mistm_in_two_str/seq/include/ops_seq.hpp" + +#include + +// Clang-Tidy требует явного подключения файла, где определен InType +#include "ovsyannikov_n_num_mistm_in_two_str/common/include/common.hpp" + +namespace ovsyannikov_n_num_mistm_in_two_str { + +OvsyannikovNNumMistmInTwoStrSEQ::OvsyannikovNNumMistmInTwoStrSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool OvsyannikovNNumMistmInTwoStrSEQ::ValidationImpl() { + return GetInput().first.size() == GetInput().second.size(); +} + +bool OvsyannikovNNumMistmInTwoStrSEQ::PreProcessingImpl() { + GetOutput() = 0; + return true; +} + +bool OvsyannikovNNumMistmInTwoStrSEQ::RunImpl() { + const auto &seq_one = GetInput().first; + const auto &seq_two = GetInput().second; + int diff_cnt = 0; + + size_t length = seq_one.size(); + for (size_t i = 0; i < length; ++i) { + if (seq_one[i] != seq_two[i]) { + diff_cnt++; + } + } + + GetOutput() = diff_cnt; + return true; +} + +bool OvsyannikovNNumMistmInTwoStrSEQ::PostProcessingImpl() { + return true; +} + +} // namespace ovsyannikov_n_num_mistm_in_two_str diff --git a/tasks/ovsyannikov_n_num_mistm_in_two_str/settings.json b/tasks/ovsyannikov_n_num_mistm_in_two_str/settings.json new file mode 100644 index 000000000..b1a0d5257 --- /dev/null +++ b/tasks/ovsyannikov_n_num_mistm_in_two_str/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/ovsyannikov_n_num_mistm_in_two_str/tests/.clang-tidy b/tasks/ovsyannikov_n_num_mistm_in_two_str/tests/.clang-tidy new file mode 100644 index 000000000..ef43b7aa8 --- /dev/null +++ b/tasks/ovsyannikov_n_num_mistm_in_two_str/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/ovsyannikov_n_num_mistm_in_two_str/tests/functional/main.cpp b/tasks/ovsyannikov_n_num_mistm_in_two_str/tests/functional/main.cpp new file mode 100644 index 000000000..dff1c4edd --- /dev/null +++ b/tasks/ovsyannikov_n_num_mistm_in_two_str/tests/functional/main.cpp @@ -0,0 +1,65 @@ +#include + +#include +#include +#include +#include + +#include "ovsyannikov_n_num_mistm_in_two_str/common/include/common.hpp" +#include "ovsyannikov_n_num_mistm_in_two_str/mpi/include/ops_mpi.hpp" +#include "ovsyannikov_n_num_mistm_in_two_str/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" + +namespace ovsyannikov_n_num_mistm_in_two_str { + +class OvsyannikovNRunFuncTestsProcesses : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::get<2>(test_param); + } + + protected: + void SetUp() override { + TestType params = std::get(GetParam()); + input_data_ = std::get<0>(params); + expected_ = std::get<1>(params); + } + + bool CheckTestOutputData(OutType &actual_res) final { + return actual_res == expected_; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; + OutType expected_ = 0; +}; + +TEST_P(OvsyannikovNRunFuncTestsProcesses, CalculateMismatches) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + {std::make_tuple(std::make_pair("", ""), 0, "EmptyStrings"), + std::make_tuple(std::make_pair("a", "a"), 0, "SingleCharMatch"), + std::make_tuple(std::make_pair("a", "b"), 1, "SingleCharMismatch"), + std::make_tuple(std::make_pair("hello", "hello"), 0, "IdenticalStrings"), + std::make_tuple(std::make_pair("abc", "def"), 3, "AllMismatch"), + std::make_tuple(std::make_pair("apple", "apply"), 1, "LastCharMismatch"), + std::make_tuple(std::make_pair("Case", "case"), 1, "CaseSensitivity"), + std::make_tuple(std::make_pair("12345", "12355"), 1, "NumbersMismatch"), + std::make_tuple(std::make_pair("two words", "two_words"), 1, "SpaceVsUnderscore"), + std::make_tuple(std::make_pair("longstringtest", "longstringbest"), 1, "LongStringOneMismatch")}}; + +const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_ovsyannikov_n_num_mistm_in_two_str), + ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_ovsyannikov_n_num_mistm_in_two_str)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); +const auto kPerfTestName = OvsyannikovNRunFuncTestsProcesses::PrintFuncTestName; +INSTANTIATE_TEST_SUITE_P(NumMistmInTwoStr, OvsyannikovNRunFuncTestsProcesses, kGtestValues, kPerfTestName); +} // namespace ovsyannikov_n_num_mistm_in_two_str diff --git a/tasks/ovsyannikov_n_num_mistm_in_two_str/tests/performance/main.cpp b/tasks/ovsyannikov_n_num_mistm_in_two_str/tests/performance/main.cpp new file mode 100644 index 000000000..f014b5cac --- /dev/null +++ b/tasks/ovsyannikov_n_num_mistm_in_two_str/tests/performance/main.cpp @@ -0,0 +1,61 @@ +#include + +#include +#include + +#include "ovsyannikov_n_num_mistm_in_two_str/common/include/common.hpp" +#include "ovsyannikov_n_num_mistm_in_two_str/mpi/include/ops_mpi.hpp" +#include "ovsyannikov_n_num_mistm_in_two_str/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace ovsyannikov_n_num_mistm_in_two_str { + +class OvsyannikovNRunPerfTestProcesses : public ppc::util::BaseRunPerfTests { + protected: + void SetUp() override { + // Размер 100 миллионов символов + const int benchmark_size = 100'000'000; + + std::string sample_a(benchmark_size, 'a'); + std::string sample_b(benchmark_size, 'a'); + + expected_output_ = 0; + for (int i = 0; i < benchmark_size; ++i) { + if (i % 2 == 0) { + sample_b[i] = 'b'; + expected_output_++; + } + } + + input_data_ = std::make_pair(sample_a, sample_b); + } + + bool CheckTestOutputData(OutType &calculated_res) final { + return calculated_res == expected_output_; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; + OutType expected_output_ = 0; +}; +; + +TEST_P(OvsyannikovNRunPerfTestProcesses, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_ovsyannikov_n_num_mistm_in_two_str); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = OvsyannikovNRunPerfTestProcesses::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, OvsyannikovNRunPerfTestProcesses, kGtestValues, kPerfTestName); + +} // namespace ovsyannikov_n_num_mistm_in_two_str