diff --git a/tasks/akimov_i_words_string_count/common/include/common.hpp b/tasks/akimov_i_words_string_count/common/include/common.hpp new file mode 100644 index 0000000000..96160bdc92 --- /dev/null +++ b/tasks/akimov_i_words_string_count/common/include/common.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace akimov_i_words_string_count { + +using InType = std::vector; +using OutType = int; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace akimov_i_words_string_count diff --git a/tasks/akimov_i_words_string_count/data/pic.jpg b/tasks/akimov_i_words_string_count/data/pic.jpg new file mode 100644 index 0000000000..3445802349 Binary files /dev/null and b/tasks/akimov_i_words_string_count/data/pic.jpg differ diff --git a/tasks/akimov_i_words_string_count/data/words.txt b/tasks/akimov_i_words_string_count/data/words.txt new file mode 100644 index 0000000000..0dec264e32 --- /dev/null +++ b/tasks/akimov_i_words_string_count/data/words.txt @@ -0,0 +1,11 @@ + This is a sample line with leading and trailing spaces. +Multiple spaces here + Tabs and spaces mixed +Newlines +are +also +handled +Punctuation! Isn't it counted? Yes. +Русские слова тоже считаются + +End. \ No newline at end of file diff --git a/tasks/akimov_i_words_string_count/info.json b/tasks/akimov_i_words_string_count/info.json new file mode 100644 index 0000000000..d6b3da5c58 --- /dev/null +++ b/tasks/akimov_i_words_string_count/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/akimov_i_words_string_count/mpi/include/ops_mpi.hpp b/tasks/akimov_i_words_string_count/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..d2a5cde3fd --- /dev/null +++ b/tasks/akimov_i_words_string_count/mpi/include/ops_mpi.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "akimov_i_words_string_count/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace akimov_i_words_string_count { + +class AkimovIWordsStringCountMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit AkimovIWordsStringCountMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + InType input_buffer_; + InType local_buffer_; + int local_space_count_ = 0; + int global_space_count_ = 0; + int word_count_ = 0; +}; +} // namespace akimov_i_words_string_count diff --git a/tasks/akimov_i_words_string_count/mpi/src/ops_mpi.cpp b/tasks/akimov_i_words_string_count/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..cf722a8812 --- /dev/null +++ b/tasks/akimov_i_words_string_count/mpi/src/ops_mpi.cpp @@ -0,0 +1,124 @@ +#include "akimov_i_words_string_count/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include + +#include "akimov_i_words_string_count/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace akimov_i_words_string_count { + +AkimovIWordsStringCountMPI::AkimovIWordsStringCountMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool AkimovIWordsStringCountMPI::ValidationImpl() { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank == 0) { + return (!GetInput().empty()) && (GetOutput() == 0); + } + return true; +} + +bool AkimovIWordsStringCountMPI::PreProcessingImpl() { + int rank = 0; + int size = 1; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + local_space_count_ = 0; + global_space_count_ = 0; + word_count_ = 0; + local_buffer_.clear(); + input_buffer_.clear(); + + std::size_t total = 0; + if (rank == 0) { + input_buffer_ = GetInput(); + total = input_buffer_.size(); + } + + int base = 0; + int remainder = 0; + if (rank == 0) { + base = static_cast(total / static_cast(size)); + remainder = static_cast(total % static_cast(size)); + } + MPI_Bcast(&base, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&remainder, 1, MPI_INT, 0, MPI_COMM_WORLD); + + int my_count = base + ((rank < remainder) ? 1 : 0); + local_buffer_.resize(my_count); + + if (rank == 0) { + std::size_t offset = 0; + for (int proc = 0; proc < size; ++proc) { + int count = base + ((proc < remainder) ? 1 : 0); + if (count == 0) { + } else if (proc == 0) { + std::memcpy(local_buffer_.data(), input_buffer_.data() + offset, static_cast(count)); + } else { + MPI_Send(input_buffer_.data() + offset, count, MPI_CHAR, proc, 0, MPI_COMM_WORLD); + } + offset += static_cast(count); + } + } else { + if (my_count > 0) { + MPI_Recv(local_buffer_.data(), my_count, MPI_CHAR, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } else { + } + } + + return true; +} + +bool AkimovIWordsStringCountMPI::RunImpl() { + local_space_count_ = 0; + for (char c : local_buffer_) { + if (c == ' ') { + ++local_space_count_; + } + } + + MPI_Reduce(&local_space_count_, &global_space_count_, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); + + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank == 0) { + if (GetInput().empty()) { + word_count_ = 0; + } else { + const InType &buf = GetInput(); + bool in_word = false; + word_count_ = 0; + for (char c : buf) { + if (c != ' ' && !in_word) { + in_word = true; + ++word_count_; + } else if (c == ' ' && in_word) { + in_word = false; + } + } + } + } + + return true; +} + +bool AkimovIWordsStringCountMPI::PostProcessingImpl() { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank == 0) { + GetOutput() = word_count_; + } + return true; +} + +} // namespace akimov_i_words_string_count diff --git a/tasks/akimov_i_words_string_count/report.md b/tasks/akimov_i_words_string_count/report.md new file mode 100644 index 0000000000..3475785e73 --- /dev/null +++ b/tasks/akimov_i_words_string_count/report.md @@ -0,0 +1,50 @@ +### Отчёт по задаче: "Подсчёт числа слов в строке" +**Вариант:** №24 +**Выполнил студент группы 3823Б1ФИ2:** Акимов Илья Александрович +**Работу проверили:** преподаватель Нестеров Александр Юрьевич и преподаватель Оболенский Арсений Андреевич + +## Введение +В данной работе реализован алгоритм подсчёта количества слов в строке. Алгоритм представлен в двух вариантах: последовательная реализация (SEQ) и параллельная реализация с использованием технологии MPI. Цель работы — корректно подсчитать количество слов в строке и ускорить вычисления для больших объемов данных за счёт параллельной обработки. + +## Постановка задачи и формальное описание +Необходимо определить количество слов в строке. Слово — последовательность непробельных символов, разделённая пробелами, табуляциями или переводами строк. + +Формально: пусть `S` — входная строка длины `n`. Необходимо вычислить: +`word_count = количество таких последовательностей символов, которые отделены пробелами и другими разделителями` + +## Пример +Строка: `" This is an example "` +Слова: `"This"`, `"is"`, `"an"`, `"example"` +Количество слов: 4 + +## Реализация разделена на модули +- `ops_seq.hpp` и `ops_seq.cpp` — последовательная реализация подсчёта слов +- `ops_mpi.hpp` и `ops_mpi.cpp` — параллельная реализация с использованием MPI +- `common.hpp` — определения типов данных и базовый класс задачи +- `functional/main.cpp` — функциональные тесты +- `performance/main.cpp` — тесты производительности + +## Описание последовательной версии +Последовательная версия (`AkimovIWordsStringCountSEQ`) сохраняет строку во внутренний буфер и проходит по всем символам. Алгоритм подсчёта слов работает следующим образом: + +1. Для каждого символа проверяется, является ли он пробелом. +2. Если текущий символ — начало слова (не пробел, предыдущий был пробел или начало строки), счётчик слов увеличивается. +3. Итоговый результат сохраняется в `GetOutput()`. + +## Описание параллельной версии +Параллельная версия (`AkimovIWordsStringCountMPI`) распределяет входную строку между процессами MPI: + +1. Процесс с рангом 0 делит строку на равные блоки для всех процессов. +2. Каждый процесс получает свой блок и подсчитывает количество пробелов в нём. +3. Частичные результаты объединяются с помощью `MPI_Reduce`. +4. На процессе с рангом 0 выполняется финальный подсчёт слов по всей строке, корректно учитывая границы блоков. + +Такой подход позволяет эффективно обрабатывать очень большие строки, распределяя работу между процессами. + +## Тестирования + +### Функциональное тестирование +Функциональные тесты проверяют корректность работы алгоритмов на заранее известных данных. Тесты считывают строки из файла `words.txt`, заменяя табуляции и переводы строк на пробелы. Для каждой позиции строки алгоритм определяет, начинается ли слово, и увеличивает счётчик. + +### Тестирование производительности +Для тестирования производительности формируется строка из 1 миллиона слов `"word"`, разделённых пробелами. Это гарантирует предсказуемый результат (1 000 000 слов), позволяя точно проверять корректность работы как последовательной, так и параллельной версий. diff --git a/tasks/akimov_i_words_string_count/seq/include/ops_seq.hpp b/tasks/akimov_i_words_string_count/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..d064d826d2 --- /dev/null +++ b/tasks/akimov_i_words_string_count/seq/include/ops_seq.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "akimov_i_words_string_count/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace akimov_i_words_string_count { + +class AkimovIWordsStringCountSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit AkimovIWordsStringCountSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + InType input_buffer_; + int word_count_ = 0; + int space_count_ = 0; +}; + +} // namespace akimov_i_words_string_count diff --git a/tasks/akimov_i_words_string_count/seq/src/ops_seq.cpp b/tasks/akimov_i_words_string_count/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..e5fd22f563 --- /dev/null +++ b/tasks/akimov_i_words_string_count/seq/src/ops_seq.cpp @@ -0,0 +1,58 @@ +#include "akimov_i_words_string_count/seq/include/ops_seq.hpp" + +#include +#include + +#include "akimov_i_words_string_count/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace akimov_i_words_string_count { + +AkimovIWordsStringCountSEQ::AkimovIWordsStringCountSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool AkimovIWordsStringCountSEQ::ValidationImpl() { + return (!GetInput().empty()) && (GetOutput() == 0); +} + +bool AkimovIWordsStringCountSEQ::PreProcessingImpl() { + input_buffer_ = GetInput(); + word_count_ = 0; + space_count_ = 0; + return true; +} + +bool AkimovIWordsStringCountSEQ::RunImpl() { + if (input_buffer_.empty()) { + return false; + } + + for (char c : input_buffer_) { + if (c == ' ') { + ++space_count_; + } + } + + bool in_word = false; + word_count_ = 0; + for (char c : input_buffer_) { + if (c != ' ' && !in_word) { + in_word = true; + ++word_count_; + } else if (c == ' ' && in_word) { + in_word = false; + } + } + + return true; +} + +bool AkimovIWordsStringCountSEQ::PostProcessingImpl() { + GetOutput() = word_count_; + return true; +} + +} // namespace akimov_i_words_string_count diff --git a/tasks/akimov_i_words_string_count/settings.json b/tasks/akimov_i_words_string_count/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/akimov_i_words_string_count/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/akimov_i_words_string_count/tests/.clang-tidy b/tasks/akimov_i_words_string_count/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/akimov_i_words_string_count/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/akimov_i_words_string_count/tests/functional/main.cpp b/tasks/akimov_i_words_string_count/tests/functional/main.cpp new file mode 100644 index 0000000000..3c544202d9 --- /dev/null +++ b/tasks/akimov_i_words_string_count/tests/functional/main.cpp @@ -0,0 +1,109 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "akimov_i_words_string_count/common/include/common.hpp" +#include "akimov_i_words_string_count/mpi/include/ops_mpi.hpp" +#include "akimov_i_words_string_count/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace akimov_i_words_string_count { + +class AkimovIWordsStringCountFromFileFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::to_string(std::get<0>(test_param)) + "_" + std::get<1>(test_param); + } + + protected: + void SetUp() override { + std::string abs_path = ppc::util::GetAbsoluteTaskPath(PPC_ID_akimov_i_words_string_count, "words.txt"); + + std::ifstream file(abs_path, std::ios::in | std::ios::binary); + if (!file.is_open()) { + throw std::runtime_error("Cannot open words.txt at path: " + abs_path); + } + + std::string content; + { + std::ostringstream ss; + ss << file.rdbuf(); + content = ss.str(); + } + file.close(); + + for (char &c : content) { + if (c == '\n' || c == '\r' || c == '\t') { + c = ' '; + } + } + + input_data_ = InType(content.begin(), content.end()); + + is_valid_ = !content.empty(); + + expected_result_ = 0; + if (is_valid_) { + bool in_word = false; + for (char c : content) { + if (c != ' ' && !in_word) { + in_word = true; + ++expected_result_; + } else if (c == ' ' && in_word) { + in_word = false; + } + } + } + + (void)std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + } + + InType GetTestInputData() final { + return input_data_; + } + + bool CheckTestOutputData(OutType &output_data) final { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (!is_valid_ || rank != 0) { + return true; + } + return output_data == expected_result_; + } + + private: + InType input_data_; + OutType expected_result_ = 0; + bool is_valid_ = true; +}; + +namespace { + +TEST_P(AkimovIWordsStringCountFromFileFuncTests, CountWordsFromFile) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = {std::make_tuple(0, std::string("default"))}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_akimov_i_words_string_count), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_akimov_i_words_string_count)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kFuncTestName = AkimovIWordsStringCountFromFileFuncTests::PrintTestParam; + +INSTANTIATE_TEST_SUITE_P(CountFromFile, AkimovIWordsStringCountFromFileFuncTests, kGtestValues); + +} // namespace + +} // namespace akimov_i_words_string_count diff --git a/tasks/akimov_i_words_string_count/tests/performance/main.cpp b/tasks/akimov_i_words_string_count/tests/performance/main.cpp new file mode 100644 index 0000000000..bbc200b39a --- /dev/null +++ b/tasks/akimov_i_words_string_count/tests/performance/main.cpp @@ -0,0 +1,56 @@ +#include + +#include +#include +#include + +#include "akimov_i_words_string_count/common/include/common.hpp" +#include "akimov_i_words_string_count/mpi/include/ops_mpi.hpp" +#include "akimov_i_words_string_count/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace akimov_i_words_string_count { + +class AkimovIWordsStringCountPerfTests : public ppc::util::BaseRunPerfTests { + protected: + void SetUp() override { + const std::size_t words = 1'000'000; + std::string s; + s.reserve(words * 5); + for (std::size_t i = 0; i < words; ++i) { + s += "word"; + if (i + 1 != words) { + s += ' '; + } + } + input_data_ = InType(s.begin(), s.end()); + expected_result_ = static_cast(words); + } + + InType GetTestInputData() final { + return input_data_; + } + + bool CheckTestOutputData(OutType &output_data) final { + return output_data == expected_result_; + } + + private: + InType input_data_; + OutType expected_result_ = 0; +}; + +TEST_P(AkimovIWordsStringCountPerfTests, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_akimov_i_words_string_count); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = AkimovIWordsStringCountPerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, AkimovIWordsStringCountPerfTests, kGtestValues, kPerfTestName); + +} // namespace akimov_i_words_string_count