diff --git a/tasks/kichanova_k_count_letters_in_str/common/include/common.hpp b/tasks/kichanova_k_count_letters_in_str/common/include/common.hpp new file mode 100644 index 0000000000..c9a49c4f2f --- /dev/null +++ b/tasks/kichanova_k_count_letters_in_str/common/include/common.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" + +namespace kichanova_k_count_letters_in_str { + +using InType = std::string; +using OutType = int; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace kichanova_k_count_letters_in_str diff --git a/tasks/kichanova_k_count_letters_in_str/data/pic.jpg b/tasks/kichanova_k_count_letters_in_str/data/pic.jpg new file mode 100644 index 0000000000..637624238c Binary files /dev/null and b/tasks/kichanova_k_count_letters_in_str/data/pic.jpg differ diff --git a/tasks/kichanova_k_count_letters_in_str/info.json b/tasks/kichanova_k_count_letters_in_str/info.json new file mode 100644 index 0000000000..09e4730b92 --- /dev/null +++ b/tasks/kichanova_k_count_letters_in_str/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/kichanova_k_count_letters_in_str/mpi/include/ops_mpi.hpp b/tasks/kichanova_k_count_letters_in_str/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..21a495c737 --- /dev/null +++ b/tasks/kichanova_k_count_letters_in_str/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "kichanova_k_count_letters_in_str/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace kichanova_k_count_letters_in_str { + +class KichanovaKCountLettersInStrMPI : public BaseTask { +public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit KichanovaKCountLettersInStrMPI(const InType &in); + +private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace kichanova_k_count_letters_in_str diff --git a/tasks/kichanova_k_count_letters_in_str/mpi/src/ops_mpi.cpp b/tasks/kichanova_k_count_letters_in_str/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..ed656097b2 --- /dev/null +++ b/tasks/kichanova_k_count_letters_in_str/mpi/src/ops_mpi.cpp @@ -0,0 +1,67 @@ +#include "kichanova_k_count_letters_in_str/mpi/include/ops_mpi.hpp" + +#include + +#include +#include + +#include "kichanova_k_count_letters_in_str/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace kichanova_k_count_letters_in_str { + +KichanovaKCountLettersInStrMPI::KichanovaKCountLettersInStrMPI( + const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool KichanovaKCountLettersInStrMPI::ValidationImpl() { + return !GetInput().empty(); +} + +bool KichanovaKCountLettersInStrMPI::PreProcessingImpl() { + GetOutput() = 0; + return true; +} + +bool KichanovaKCountLettersInStrMPI::RunImpl() { + auto input_str = GetInput(); + if (input_str.empty()) { + return false; + } + + int rank, size; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + int total_length = input_str.length(); + int chunk_size = total_length / size; + + int start_index = rank * chunk_size; + int end_index = (rank == size - 1) ? total_length : start_index + chunk_size; + + int local_count = 0; + for (int i = start_index; i < end_index; i++) { + if (std::isalpha(static_cast(input_str[i]))) { + local_count++; + } + } + + int global_count = 0; + MPI_Reduce(&local_count, &global_count, 1, MPI_INT, MPI_SUM, 0, + MPI_COMM_WORLD); + + MPI_Bcast(&global_count, 1, MPI_INT, 0, MPI_COMM_WORLD); + + GetOutput() = global_count; + + return true; +} + +bool KichanovaKCountLettersInStrMPI::PostProcessingImpl() { + return GetOutput() >= 0; +} + +} // namespace kichanova_k_count_letters_in_str diff --git a/tasks/kichanova_k_count_letters_in_str/report.md b/tasks/kichanova_k_count_letters_in_str/report.md new file mode 100644 index 0000000000..b1c84098fb --- /dev/null +++ b/tasks/kichanova_k_count_letters_in_str/report.md @@ -0,0 +1,115 @@ +# Отчёт по лабораторной работе +# Подсчёт числа буквенных символов в строке + +- Student: Кичанова Ксения Константиновна, group 3823Б1ФИ3 +- Technology: SEQ | MPI +- Variant: 22 + +## 1. Introduction +Подсчет буквенных символов в строке — классическая задача обработки текстовых данных. В контексте параллельного программирования данная задача представляет интерес благодаря возможности распараллеливания операции подсчета символов в больших текстовых данных. Ожидается, что при использовании технолоогии mpi произойдёт ускорение по сравнению с последовательной версией. + +## 2. Problem Statement +Нужно найти количество буквенных символов в строке input_str. +Input: Строка (std::string) +Output: Целое число (int) - количество буквенных символов в строке + +## 3. Baseline Algorithm (Sequential) +Базовый последовательный алгоритм реализует однопроходное сканирование входной строки. Алгоритм последовательно обрабатывает каждый символ строки, проверяя его принадлежность к множеству буквенных символов с помощью функции std::isalpha. Для каждого символа, удовлетворяющего условию, увеличивается счетчик. +Вычислительная сложность: O(N) + +## 4. Parallelization Scheme +Каждый процесс MPI получает для обработки сегмент исходной строки. Размер сегмента вычисляется как целочисленное деление общей длины строки на количество процессов. Процесс с наибольшим рангом получает оставшуюся часть строки. Каждый процесс независимо выполняет подсчет буквенных символов в своем сегменте, проверяя каждый символ с помощью функции std::isalpha. После завершения локальных вычислений процессы синхронизируются: операция MPI_Reduce с операцией суммирования MPI_SUM собирает все локальные счетчики на процессе с рангом 0. Затем итоговый результат рассылается всем процессам с помощью MPI_Bcast для обеспечения идентичности выходных данных во всех процессах. + +## 5. Implementation Details +- common.hpp - общие типы данных и константы +- ops_seq - последовательная реализация алгоритма +- ops_mpi - параллельная MPI-реализация алгоритма +- tests - functional для проверки корректности и performance для замера скорости. + +## 6. Experimental Setup +- Аппаратное обеспечение: AMD Ryzen 5 5500U (6 ядер, 12 логических процессоров, базовая скорость 2,10 ГГц) +- ОЗУ — 8 ГБ +- Операционная система: Windows 11 +- Компилятор: g++ +- Использовался Docker-контейнер. +- Тип сборки: Release + +## 7. Results and Discussion + +### 7.1 Correctness +Корректность реализации проверялась тестами, включающие пустые строки, цифры, знаки и длинные строки. + +### 7.2 Performance + +pipeline: + +| Mode | Count | Time, s | Speedup | Efficiency | +|-------------|-------|-----------|---------|------------| +| seq | 1 | 0.01322 | 1.00 | N/A | +| omp | 2 | 0.01044 | 1.27 | 63.5% | +| omp | 4 | 0.00748 | 1.77 | 44.3% | + +task_run: + +| Mode | Count | Time, s | Speedup | Efficiency | +|-------------|-------|-----------|---------|------------| +| seq | 1 | 0.01419 | 1.00 | N/A | +| omp | 2 | 0.00865 | 1.64 | 82.0% | +| omp | 4 | 0.00569 | 2.49 | 62.3% | + + +## 8. Conclusions +MPI реализация демонстрирует положительное ускорение на всех конфигурациях. Максимальное ускорение 2.49 достигнуто в режиме task_run на 4 процессах. Эффективность параллелизации снижается с ростом числа процессов из-за накладных расходов MPI. + +## 9. References +1. Лекции и практики курса "Параллельное программирование для кластерных систем" + + +## Appendix +ops_seq.cpp: +bool KichanovaKCountLettersInStrSEQ::RunImpl() { +const std::string& input_str = GetInput(); + + for (char c : input_str) { + if (std::isalpha(static_cast(c))) { + GetOutput()++; + } + } + + return GetOutput() >= 0; +} + +ops_mpi.cpp: +bool KichanovaKCountLettersInStrMPI::RunImpl() { + auto input_str = GetInput(); + if (input_str.empty()) { + return false; + } + + int rank, size; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + int total_length = input_str.length(); + int chunk_size = total_length / size; + + int start_index = rank * chunk_size; + int end_index = (rank == size - 1) ? total_length : start_index + chunk_size; + + int local_count = 0; + for (int i = start_index; i < end_index; i++) { + if (std::isalpha(static_cast(input_str[i]))) { + local_count++; + } + } + + int global_count = 0; + MPI_Reduce(&local_count, &global_count, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); + + MPI_Bcast(&global_count, 1, MPI_INT, 0, MPI_COMM_WORLD); + + GetOutput() = global_count; + + return true; +} +``` \ No newline at end of file diff --git a/tasks/kichanova_k_count_letters_in_str/seq/include/ops_seq.hpp b/tasks/kichanova_k_count_letters_in_str/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..8a321ea862 --- /dev/null +++ b/tasks/kichanova_k_count_letters_in_str/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "kichanova_k_count_letters_in_str/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace kichanova_k_count_letters_in_str { + +class KichanovaKCountLettersInStrSEQ : public BaseTask { +public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit KichanovaKCountLettersInStrSEQ(const InType &in); + +private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace kichanova_k_count_letters_in_str diff --git a/tasks/kichanova_k_count_letters_in_str/seq/src/ops_seq.cpp b/tasks/kichanova_k_count_letters_in_str/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..b8d621c475 --- /dev/null +++ b/tasks/kichanova_k_count_letters_in_str/seq/src/ops_seq.cpp @@ -0,0 +1,43 @@ +#include "kichanova_k_count_letters_in_str/seq/include/ops_seq.hpp" + +#include +#include + +#include "kichanova_k_count_letters_in_str/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace kichanova_k_count_letters_in_str { + +KichanovaKCountLettersInStrSEQ::KichanovaKCountLettersInStrSEQ( + const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool KichanovaKCountLettersInStrSEQ::ValidationImpl() { + return !GetInput().empty() && (GetOutput() == 0); +} + +bool KichanovaKCountLettersInStrSEQ::PreProcessingImpl() { + GetOutput() = 0; + return true; +} + +bool KichanovaKCountLettersInStrSEQ::RunImpl() { + const std::string &input_str = GetInput(); + + for (char c : input_str) { + if (std::isalpha(static_cast(c))) { + GetOutput()++; + } + } + + return GetOutput() >= 0; +} + +bool KichanovaKCountLettersInStrSEQ::PostProcessingImpl() { + return GetOutput() >= 0; +} + +} // namespace kichanova_k_count_letters_in_str diff --git a/tasks/kichanova_k_count_letters_in_str/settings.json b/tasks/kichanova_k_count_letters_in_str/settings.json new file mode 100644 index 0000000000..7d2c35b298 --- /dev/null +++ b/tasks/kichanova_k_count_letters_in_str/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/kichanova_k_count_letters_in_str/tests/.clang-tidy b/tasks/kichanova_k_count_letters_in_str/tests/.clang-tidy new file mode 100644 index 0000000000..d68523c24e --- /dev/null +++ b/tasks/kichanova_k_count_letters_in_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/kichanova_k_count_letters_in_str/tests/functional/main.cpp b/tasks/kichanova_k_count_letters_in_str/tests/functional/main.cpp new file mode 100644 index 0000000000..111b4e87b8 --- /dev/null +++ b/tasks/kichanova_k_count_letters_in_str/tests/functional/main.cpp @@ -0,0 +1,104 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kichanova_k_count_letters_in_str/common/include/common.hpp" +#include "kichanova_k_count_letters_in_str/mpi/include/ops_mpi.hpp" +#include "kichanova_k_count_letters_in_str/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace kichanova_k_count_letters_in_str { + +class KichanovaKCountLettersInStrFuncTests + : public ppc::util::BaseRunFuncTests { +public: + static std::string PrintTestParam(const TestType &test_param) { + std::string input_str = std::get<1>(test_param); + std::string safe_name; + + for (char c : input_str) { + if (std::isalnum(static_cast(c))) { + safe_name += c; + } else { + safe_name += '_'; + } + } + + return std::to_string(std::get<0>(test_param)) + "_" + safe_name; + } + +protected: + void SetUp() override { + TestType params = std::get( + ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + input_string_ = std::get<1>(params); + expected_output_ = CalculateExpectedCount(input_string_); + } + + bool CheckTestOutputData(OutType &output_data) final { + return (expected_output_ == output_data); + } + + InType GetTestInputData() final { return input_string_; } + +private: + int CalculateExpectedCount(const std::string &str) { + int count = 0; + for (char c : str) { + if (std::isalpha(static_cast(c))) { + count++; + } + } + return count; + } + + std::string input_string_; + int expected_output_; +}; + +namespace { + +TEST_P(KichanovaKCountLettersInStrFuncTests, CountLettersInString) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + std::make_tuple(5, "Hello"), + std::make_tuple(10, "Hello World"), + std::make_tuple(8, "Test123!"), + std::make_tuple(0, "123!@#"), + std::make_tuple(0, " "), + std::make_tuple(4, "a.b,c!d"), + std::make_tuple(50, std::string(100, 'a') + std::string(100, '1')), + std::make_tuple(1000, std::string(1000, 'x')), +}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_example_processes), + ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_example_processes)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = + KichanovaKCountLettersInStrFuncTests::PrintFuncTestName< + KichanovaKCountLettersInStrFuncTests>; + +INSTANTIATE_TEST_SUITE_P(StringTests, KichanovaKCountLettersInStrFuncTests, + kGtestValues, kPerfTestName); + +} // namespace + +} // namespace kichanova_k_count_letters_in_str diff --git a/tasks/kichanova_k_count_letters_in_str/tests/performance/main.cpp b/tasks/kichanova_k_count_letters_in_str/tests/performance/main.cpp new file mode 100644 index 0000000000..b3849553a6 --- /dev/null +++ b/tasks/kichanova_k_count_letters_in_str/tests/performance/main.cpp @@ -0,0 +1,57 @@ +#include + +#include "kichanova_k_count_letters_in_str/common/include/common.hpp" +#include "kichanova_k_count_letters_in_str/mpi/include/ops_mpi.hpp" +#include "kichanova_k_count_letters_in_str/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace kichanova_k_count_letters_in_str { + +class KichanovaKCountLettersInStrPerfTest + : public ppc::util::BaseRunPerfTests { + InType input_data_{}; + int expected_output_{}; + + void SetUp() override { + const size_t str_size = 5000000; + + std::string generated_string; + generated_string.reserve(str_size); + + for (size_t i = 0; i < str_size; ++i) { + if (i % 3 == 0) { + generated_string += (i % 2 == 0) ? 'a' + (i % 26) : 'A' + (i % 26); + expected_output_++; + } else { + generated_string += (i % 2 == 0) ? '0' + (i % 10) : '!' + (i % 15); + } + } + + input_data_ = generated_string; + } + + bool CheckTestOutputData(OutType &output_data) final { + return expected_output_ == output_data; + } + + InType GetTestInputData() final { return input_data_; } +}; + +TEST_P(KichanovaKCountLettersInStrPerfTest, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_example_processes); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = + KichanovaKCountLettersInStrPerfTest::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, KichanovaKCountLettersInStrPerfTest, + kGtestValues, kPerfTestName); + +} // namespace kichanova_k_count_letters_in_str