diff --git a/tasks/smyshlaev_a_str_order_check/common/include/common.hpp b/tasks/smyshlaev_a_str_order_check/common/include/common.hpp new file mode 100644 index 0000000000..3d8fa87c02 --- /dev/null +++ b/tasks/smyshlaev_a_str_order_check/common/include/common.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace smyshlaev_a_str_order_check { + +using InType = std::pair; +using OutType = int; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace smyshlaev_a_str_order_check diff --git a/tasks/smyshlaev_a_str_order_check/info.json b/tasks/smyshlaev_a_str_order_check/info.json new file mode 100644 index 0000000000..3478363909 --- /dev/null +++ b/tasks/smyshlaev_a_str_order_check/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Александр", + "last_name": "Смышляев", + "middle_name": "Павлович", + "group_number": "3823Б1ФИ2", + "task_number": "1" + } +} diff --git a/tasks/smyshlaev_a_str_order_check/mpi/include/ops_mpi.hpp b/tasks/smyshlaev_a_str_order_check/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..6de0bfc5ac --- /dev/null +++ b/tasks/smyshlaev_a_str_order_check/mpi/include/ops_mpi.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "smyshlaev_a_str_order_check/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace smyshlaev_a_str_order_check { + +class SmyshlaevAStrOrderCheckMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit SmyshlaevAStrOrderCheckMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + bool RunSequential(int min_len, int len1, int len2); +}; + +} // namespace smyshlaev_a_str_order_check diff --git a/tasks/smyshlaev_a_str_order_check/mpi/src/ops_mpi.cpp b/tasks/smyshlaev_a_str_order_check/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..706b4551c4 --- /dev/null +++ b/tasks/smyshlaev_a_str_order_check/mpi/src/ops_mpi.cpp @@ -0,0 +1,153 @@ +#include "smyshlaev_a_str_order_check/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include + +#include "smyshlaev_a_str_order_check/common/include/common.hpp" + +namespace smyshlaev_a_str_order_check { + +namespace { + +int CompareBuffers(const char *s1, const char *s2, int len) { + for (int i = 0; i < len; ++i) { + if (s1[i] < s2[i]) { + return -1; + } + if (s1[i] > s2[i]) { + return 1; + } + } + return 0; +} + +int ResolveResult(int diff_res, int len1, int len2) { + if (diff_res != 0) { + return diff_res; + } + if (len1 < len2) { + return -1; + } + if (len1 > len2) { + return 1; + } + return 0; +} + +void CalculateDistribution(int total_len, int proc_count, std::vector &counts, std::vector &offsets) { + const int chunk = total_len / proc_count; + const int remainder = total_len % proc_count; + int offset = 0; + for (int i = 0; i < proc_count; i++) { + counts[i] = chunk + (i < remainder ? 1 : 0); + offsets[i] = offset; + offset += counts[i]; + } +} + +} // namespace + +SmyshlaevAStrOrderCheckMPI::SmyshlaevAStrOrderCheckMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank == 0) { + GetInput() = in; + } + + GetOutput() = 0; +} + +bool SmyshlaevAStrOrderCheckMPI::ValidationImpl() { + return true; +} + +bool SmyshlaevAStrOrderCheckMPI::PreProcessingImpl() { + return true; +} + +bool SmyshlaevAStrOrderCheckMPI::RunSequential(int min_len, int len1, int len2) { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + int res = 0; + if (rank == 0) { + const auto &str1 = GetInput().first; + const auto &str2 = GetInput().second; + int cmp = CompareBuffers(str1.data(), str2.data(), min_len); + res = ResolveResult(cmp, len1, len2); + } + + MPI_Bcast(&res, 1, MPI_INT, 0, MPI_COMM_WORLD); + GetOutput() = res; + return true; +} + +bool SmyshlaevAStrOrderCheckMPI::RunImpl() { + int proc_count = 0; + int rank = 0; + MPI_Comm_size(MPI_COMM_WORLD, &proc_count); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + int len1 = 0; + int len2 = 0; + + if (rank == 0) { + const auto &input_data = GetInput(); + len1 = static_cast(input_data.first.length()); + len2 = static_cast(input_data.second.length()); + } + + MPI_Bcast(&len1, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&len2, 1, MPI_INT, 0, MPI_COMM_WORLD); + + const int min_len = static_cast(std::min(len1, len2)); + + if (proc_count > min_len) { + return RunSequential(min_len, len1, len2); + } + + std::vector sendcounts(proc_count); + std::vector offsets(proc_count); + CalculateDistribution(min_len, proc_count, sendcounts, offsets); + + int local_size = sendcounts[rank]; + std::vector local_str1(local_size); + std::vector local_str2(local_size); + + const char *s1_ptr = (rank == 0) ? GetInput().first.data() : nullptr; + const char *s2_ptr = (rank == 0) ? GetInput().second.data() : nullptr; + + MPI_Scatterv(s1_ptr, sendcounts.data(), offsets.data(), MPI_CHAR, local_str1.data(), local_size, MPI_CHAR, 0, + MPI_COMM_WORLD); + + MPI_Scatterv(s2_ptr, sendcounts.data(), offsets.data(), MPI_CHAR, local_str2.data(), local_size, MPI_CHAR, 0, + MPI_COMM_WORLD); + + int local_result = CompareBuffers(local_str1.data(), local_str2.data(), local_size); + + std::vector all_results(proc_count); + MPI_Allgather(&local_result, 1, MPI_INT, all_results.data(), 1, MPI_INT, MPI_COMM_WORLD); + + int global_result = 0; + for (int res : all_results) { + if (res != 0) { + global_result = res; + break; + } + } + + GetOutput() = ResolveResult(global_result, len1, len2); + + return true; +} + +bool SmyshlaevAStrOrderCheckMPI::PostProcessingImpl() { + return true; +} + +} // namespace smyshlaev_a_str_order_check diff --git a/tasks/smyshlaev_a_str_order_check/report.md b/tasks/smyshlaev_a_str_order_check/report.md new file mode 100644 index 0000000000..507660ccf3 --- /dev/null +++ b/tasks/smyshlaev_a_str_order_check/report.md @@ -0,0 +1,55 @@ +# <Проверка лексикографической упорядоченности двух строк> + +- Student: Смышляев Александр Павлович, group 3823Б1ФИ2 +- Technology: SEQ | MPI +- Variant: 26 + +## 1. Introduction +**Мотивация:** Исследовать эффективность распараллеливания задачи сравнения строк через MPI, которая, в отличие от простых арифметических операций, имеет переменную вычислительную сложность. + +**Проблема:** Производительность алгоритма сильно зависит от входных данных. В "лучшем случае" (различие в начале строк) затраты на коммуникацию могут превысить выгоду, в то время как в "худшем" (различие в конце) распараллеливание должно быть эффективным. + +**Ожидаемый результат:** MPI-версия покажет значительное ускорение по сравнению с последовательной в "худшем случае", но эффективность будет снижаться с ростом числа процессов из-за накладных расходов. +## 2. Problem Statement +На вход поступают две строки произвольного размера. Задача — определить их лексикографический порядок. + +## 3. Baseline Algorithm (Sequential) +Базовый (последовательный) алгоритм посимвольно сравнивает строки до первого различия. Если общая часть идентична (одна строка является префиксом другой), результат определяется сравнением длин строк. + +## 4. Parallelization Scheme +1. **Распределение работы:** 0-й процесс получает входные данные и рассылает длины строк всем процессам через MPI_Bcast. Затем вычисляются смещения (offsets) и размеры порций (counts). С помощью операции MPI_Scatterv части строк рассылаются по процессам. Таким образом, память расходуется экономно, и каждый процесс хранит только свою часть данных. +2. **Локальное сравнение:** Каждый процесс выполняет сравнение символов только в своем диапазоне. Если различие найдено, он сохраняет локальный результат (`-1` или `1`), иначе — `0`. +3. **Сбор результатов:** С помощью операции `MPI_Allgather` каждый процесс отправляет свой локальный результат всем остальным. В итоге каждый процесс получает полный массив результатов от всех участников. +4. **Финализация:** Каждый процесс анализирует полученный массив. Итоговый результат — это первый ненулевой элемент в этом массиве (так как массив упорядочен по рангам, это гарантирует нахождение самого первого различия в строках). Если все результаты нулевые, итоговый ответ определяется сравнением длин строк. + +## 5. Experimental Setup +- **Hardware/OS:** `Intel Core i7-1255U` (10 ядер, 12 потоков), `16GB RAM`, `Windows 11` +- **Toolchain:** `MSVC v19.38.33130 (Visual Studio 2022)`, `MS-MPI`, `Release` +- **Environment:** `PPC_NUM_PROC` +- **Data:** Две строки на 20'000'000 элементов, различающиеся в последнем символе ("худший случай" для вычислений). + +## 6. Results and Discussion + +### 6.1 Correctness +Корректность реализаций проверена функциональными тестами (GTest). Тесты покрывают все основные сценарии: равные строки, пустые строки, строки-префиксы и строки с различиями в разных позициях. + +### 6.2 Performance +Результаты исследования производительности для различного числа процессов: + +| Mode | Count | Time, s | Speedup | Efficiency | +|-------------|-------|---------|---------|------------| +| seq | 1 | 0.0243 | 1.00 | N/A | +| mpi | 2 | 0.0149 | 1.64 | 82.0% | +| mpi | 4 | 0.014 | 1.74 | 44.0% | +| mpi | 8 | 0.0153 | 1.60 | 20.0% | +Результаты показывают, что алгоритм эффективно масштабируется при переходе от 1 к 2 процессам (ускорение 1.64, эффективность 82%). + +Однако при дальнейшем увеличении числа процессов (N=4) прирост производительности становится незначительным (ускорение выросло лишь до 1.74), а эффективность резко падает. + +На 8 процессах наблюдается деградация производительности: время выполнения (0.01527 с) оказалось больше, чем на 4 процессах (0.01401 с). Это объясняется тем, что абсолютное время решения задачи очень мало (порядка 15-20 мс). В таких условиях накладные расходы на инициализацию коммуникаций, пересылку данных (MPI_Scatterv) и синхронизацию потоков начинают превышать выгоду от распараллеливания вычислений. +## 7. Conclusions +Реализация распределенного сравнения строк с использованием MPI показала свою высокую эффективность для сценариев с большой вычислительной нагрузкой. Использование MPI_Scatterv позволило выполнить эффективную декомпозицию данных, передав каждому процессу только необходимый фрагмент строки для независимой обработки. Однако, накладные расходы на пересылку данных и синхронизацию (Scatterv + Allgather) при большом числе процессов (8) начинают превышать выигрыш от параллельных вычислений, что видно по падению эффективности. В целом, MPI-подход является оправданным и эффективным решением для сравнения очень больших, преимущественно схожих строк. + +## 8. References +1. Лекции по параллельному программированию ННГУ +2. Стандарт MPI diff --git a/tasks/smyshlaev_a_str_order_check/seq/include/ops_seq.hpp b/tasks/smyshlaev_a_str_order_check/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..82f062182c --- /dev/null +++ b/tasks/smyshlaev_a_str_order_check/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "smyshlaev_a_str_order_check/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace smyshlaev_a_str_order_check { + +class SmyshlaevAStrOrderCheckSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit SmyshlaevAStrOrderCheckSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace smyshlaev_a_str_order_check diff --git a/tasks/smyshlaev_a_str_order_check/seq/src/ops_seq.cpp b/tasks/smyshlaev_a_str_order_check/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..496bced7b3 --- /dev/null +++ b/tasks/smyshlaev_a_str_order_check/seq/src/ops_seq.cpp @@ -0,0 +1,59 @@ +#include "smyshlaev_a_str_order_check/seq/include/ops_seq.hpp" + +#include +#include +#include + +#include "smyshlaev_a_str_order_check/common/include/common.hpp" + +namespace smyshlaev_a_str_order_check { + +SmyshlaevAStrOrderCheckSEQ::SmyshlaevAStrOrderCheckSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool SmyshlaevAStrOrderCheckSEQ::ValidationImpl() { + return true; +} + +bool SmyshlaevAStrOrderCheckSEQ::PreProcessingImpl() { + return true; +} + +bool SmyshlaevAStrOrderCheckSEQ::RunImpl() { + const auto &input_data = GetInput(); + + const std::string &str1 = input_data.first; + const std::string &str2 = input_data.second; + + int min_len = static_cast(std::min(str1.length(), str2.length())); + + for (int i = 0; i < min_len; ++i) { + if (str1[i] < str2[i]) { + GetOutput() = -1; + return true; + } + if (str1[i] > str2[i]) { + GetOutput() = 1; + return true; + } + } + + if (str1.length() < str2.length()) { + GetOutput() = -1; + } else if (str1.length() > str2.length()) { + GetOutput() = 1; + } else { + GetOutput() = 0; + } + + return true; +} + +bool SmyshlaevAStrOrderCheckSEQ::PostProcessingImpl() { + return true; +} + +} // namespace smyshlaev_a_str_order_check diff --git a/tasks/smyshlaev_a_str_order_check/settings.json b/tasks/smyshlaev_a_str_order_check/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/smyshlaev_a_str_order_check/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/smyshlaev_a_str_order_check/tests/.clang-tidy b/tasks/smyshlaev_a_str_order_check/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/smyshlaev_a_str_order_check/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/smyshlaev_a_str_order_check/tests/functional/main.cpp b/tasks/smyshlaev_a_str_order_check/tests/functional/main.cpp new file mode 100644 index 0000000000..fd625fd257 --- /dev/null +++ b/tasks/smyshlaev_a_str_order_check/tests/functional/main.cpp @@ -0,0 +1,78 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "smyshlaev_a_str_order_check/common/include/common.hpp" +#include "smyshlaev_a_str_order_check/mpi/include/ops_mpi.hpp" +#include "smyshlaev_a_str_order_check/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace smyshlaev_a_str_order_check { + +class SmyshlaevAStrOrderCheckRunFuncTestsProcesses : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::get<3>(test_param); + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + + input_data_ = std::make_pair(std::get<0>(params), std::get<1>(params)); + expected_output_ = std::get<2>(params); + } + + bool CheckTestOutputData(OutType &output_data) final { + return (expected_output_ == output_data); + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; + OutType expected_output_ = 0; +}; + +namespace { + +TEST_P(SmyshlaevAStrOrderCheckRunFuncTestsProcesses, StringOrderCheckTest) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + std::make_tuple(std::string("car"), std::string("car"), 0, std::string("equal_not_empty_strings")), + std::make_tuple(std::string("air"), std::string("bike"), -1, std::string("first_string_less_than_second")), + std::make_tuple(std::string("plane"), std::string("material"), 1, std::string("first_string_greater_than_second")), + std::make_tuple(std::string("abc"), std::string("abcdefg"), -1, std::string("first_string_prefix_of_second")), + std::make_tuple(std::string("gdefg"), std::string("gde"), 1, std::string("second_string_prefix_of_first")), + std::make_tuple(std::string("Abc"), std::string("abc"), -1, std::string("case_sensitive_comparison")), + std::make_tuple(std::string(""), std::string(""), 0, std::string("both_strings_empty")), + std::make_tuple(std::string("a"), std::string(""), 1, std::string("right_string_empty")), + std::make_tuple(std::string(""), std::string("a"), -1, std::string("left_string_empty")), + std::make_tuple(std::string("a"), std::string("a"), 0, std::string("characters_equal")), + std::make_tuple(std::string("a"), std::string("b"), -1, std::string("left_character_less"))}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_smyshlaev_a_str_order_check), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_smyshlaev_a_str_order_check)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = + SmyshlaevAStrOrderCheckRunFuncTestsProcesses::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(StringOrderCheckTests, SmyshlaevAStrOrderCheckRunFuncTestsProcesses, kGtestValues, + kPerfTestName); + +} // namespace + +} // namespace smyshlaev_a_str_order_check diff --git a/tasks/smyshlaev_a_str_order_check/tests/performance/main.cpp b/tasks/smyshlaev_a_str_order_check/tests/performance/main.cpp new file mode 100644 index 0000000000..67b4660ea9 --- /dev/null +++ b/tasks/smyshlaev_a_str_order_check/tests/performance/main.cpp @@ -0,0 +1,50 @@ +#include + +#include +#include +#include + +#include "smyshlaev_a_str_order_check/common/include/common.hpp" +#include "smyshlaev_a_str_order_check/mpi/include/ops_mpi.hpp" +#include "smyshlaev_a_str_order_check/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace smyshlaev_a_str_order_check { + +const size_t kStringLength = 20000000; + +class SmyshlaevAStrOrderCheckRunPerfTestsProcesses : public ppc::util::BaseRunPerfTests { + private: + const OutType kExpectedResult_ = -1; + InType input_data_; + + void SetUp() override { + std::string long_str_a(kStringLength, 'a'); + std::string long_str_b = long_str_a; + long_str_b.back() = 'b'; + input_data_ = std::make_pair(long_str_a, long_str_b); + } + + bool CheckTestOutputData(OutType &output_data) final { + return kExpectedResult_ == output_data; + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(SmyshlaevAStrOrderCheckRunPerfTestsProcesses, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_smyshlaev_a_str_order_check); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = SmyshlaevAStrOrderCheckRunPerfTestsProcesses::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, SmyshlaevAStrOrderCheckRunPerfTestsProcesses, kGtestValues, kPerfTestName); + +} // namespace smyshlaev_a_str_order_check