diff --git a/tasks/Nazarova_K_char_count/common/include/common.hpp b/tasks/Nazarova_K_char_count/common/include/common.hpp new file mode 100644 index 0000000000..b444e7419f --- /dev/null +++ b/tasks/Nazarova_K_char_count/common/include/common.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace nazarova_k_char_count_processes { + +struct Input { + std::vector text; + char target = '\0'; +}; + +using InType = Input; +using OutType = int; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace nazarova_k_char_count_processes diff --git a/tasks/Nazarova_K_char_count/info.json b/tasks/Nazarova_K_char_count/info.json new file mode 100644 index 0000000000..d57c9801d9 --- /dev/null +++ b/tasks/Nazarova_K_char_count/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/Nazarova_K_char_count/mpi/include/ops_mpi.hpp b/tasks/Nazarova_K_char_count/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..f22659b951 --- /dev/null +++ b/tasks/Nazarova_K_char_count/mpi/include/ops_mpi.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "Nazarova_K_char_count/common/include/common.hpp" + +namespace nazarova_k_char_count_processes { + +class NazarovaKCharCountMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit NazarovaKCharCountMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace nazarova_k_char_count_processes diff --git a/tasks/Nazarova_K_char_count/mpi/src/ops_mpi.cpp b/tasks/Nazarova_K_char_count/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..764a47db31 --- /dev/null +++ b/tasks/Nazarova_K_char_count/mpi/src/ops_mpi.cpp @@ -0,0 +1,53 @@ +#include "Nazarova_K_char_count/mpi/include/ops_mpi.hpp" + +#include + +#include +#include + +#include "Nazarova_K_char_count/common/include/common.hpp" + +namespace nazarova_k_char_count_processes { + +NazarovaKCharCountMPI::NazarovaKCharCountMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool NazarovaKCharCountMPI::ValidationImpl() { + return GetOutput() == 0; +} + +bool NazarovaKCharCountMPI::PreProcessingImpl() { + GetOutput() = 0; + return true; +} + +bool NazarovaKCharCountMPI::RunImpl() { + int rank = 0; + int size = 1; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + const auto &input = GetInput(); + const std::size_t n = input.text.size(); + + const std::size_t start = (static_cast(rank) * n) / static_cast(size); + const std::size_t end = (static_cast(rank + 1) * n) / static_cast(size); + + const int local_count = + static_cast(std::count(input.text.begin() + static_cast(start), + input.text.begin() + static_cast(end), input.target)); + int global_count = 0; + MPI_Allreduce(&local_count, &global_count, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD); + + GetOutput() = global_count; + return true; +} + +bool NazarovaKCharCountMPI::PostProcessingImpl() { + return true; +} + +} // namespace nazarova_k_char_count_processes diff --git a/tasks/Nazarova_K_char_count/report.md b/tasks/Nazarova_K_char_count/report.md new file mode 100644 index 0000000000..1753fbdff4 --- /dev/null +++ b/tasks/Nazarova_K_char_count/report.md @@ -0,0 +1,197 @@ +# Подсчёт вхождений символа в тексте + +- Студент: Назарова К, группа 3823Б1ПР3 +- Технология: SEQ | MPI +- Вариант: 23 + +## Введение + +Цель работы — реализовать последовательную (SEQ) и параллельную (MPI) версии алгоритма подсчёта количества вхождений заданного символа в массиве символов, а также проверить корректность и измерить производительность. + +## Постановка задачи + +Дан текст (массив символов) и целевой символ `target`. Требуется вычислить количество индексов i, для которых `text[i] == target`. + +Тип входных данных: + +```cpp +struct Input { + std::vector text; + char target; +}; +``` + +Тип выходных данных: + +```cpp +using OutType = int; +``` + +Ограничения: + +- допускается пустой текст n = 0; +- значение `target` может быть любым, включая 0. + +## Базовый алгоритм (Sequential) + +Последовательная версия последовательно проходит по всем элементам массива и увеличивает счётчик, если текущий символ равен `target`: + +```cpp +int cnt = 0; +for (char c : text) { + if (c == target) ++cnt; +} +``` + +Сложность: + +- O(n) по времени; +- O(1) по дополнительной памяти (кроме хранения входа). + +## Схема распараллеливания (MPI) + +Распараллеливание выполняется по элементам текста: каждый процесс обрабатывает свой непрерывный диапазон индексов. + +Далее: + +- каждый процесс вычисляет `local_count` на своём фрагменте; +- итоговый ответ получается коллективной операцией `MPI_Allreduce(local_count, SUM)`, чтобы результат был доступен на каждом процессе. + +Коммуникация: один коллектив (`MPI_Allreduce`) после локального вычисления. + +## Детали реализации + +Структура задачи: + +``` +tasks/Nazarova_K_char_count/ +├── common +│ └── include +│ └── common.hpp +├── mpi +│ ├── include +│ │ └── ops_mpi.hpp +│ └── src +│ └── ops_mpi.cpp +├── seq +│ ├── include +│ │ └── ops_seq.hpp +│ └── src +│ └── ops_seq.cpp +└── tests + ├── functional + │ └── main.cpp + └── performance + └── main.cpp +``` + +Ключевые моменты реализации: + +- Общие типы: `tasks/Nazarova_K_char_count/common/include/common.hpp` + - `Input { std::vector text; char target; }` + - `OutType = int` +- SEQ-версия: `tasks/Nazarova_K_char_count/seq/src/ops_seq.cpp` + - подсчёт выполнен через `std::count(text.begin(), text.end(), target)`. +- MPI-версия: `tasks/Nazarova_K_char_count/mpi/src/ops_mpi.cpp` + - локальный подсчёт через `std::count` на своём диапазоне; + - объединение результата через `MPI_Allreduce`. + +Пограничные случаи: + +- пустой текст: каждый процесс получает `0`; +- при p > n часть процессов обрабатывает пустые диапазоны, что корректно учитывается в `MPI_Allreduce`. + +## Экспериментальная среда + +| Компонент | Значение | +|---|---| +| ОС | Linux 6.6.87.2-microsoft-standard-WSL2 (WSL2) | +| CPU | 12th Gen Intel(R) Core(TM) i5-1235U, 6 cores / 12 threads (x86_64) | +| CMake | сборка `Release` | +| Компилятор | gcc-14 / g++-14 (из `build/CMakeCache.txt`) | +| MPI | Open MPI 4.1.6 (`mpirun --version`) | + +Переменные окружения для локальных запусков: + +- `PPC_NUM_THREADS=4` +- `PPC_NUM_PROC=2` (для MPI) +- `PPC_ASAN_RUN=1` + +Примечание: в devcontainer MPI запускается под `root`, поэтому для OpenMPI требуется `--allow-run-as-root` или переменные `OMPI_ALLOW_RUN_AS_ROOT=1` и `OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1`. + +## Как воспроизвести + +Сборка из корня репозитория: + +```bash +git submodule update --init --recursive + +cmake -S . -B build -D USE_FUNC_TESTS=ON -D USE_PERF_TESTS=ON -D CMAKE_BUILD_TYPE=Release +cmake --build build --config Release --parallel +``` + +Запуск тестов через скрипт курса: + +```bash +export PPC_NUM_THREADS=4 +export PPC_NUM_PROC=2 +export PPC_ASAN_RUN=1 + +python3 scripts/run_tests.py --running-type threads --counts 1 2 4 +python3 scripts/run_tests.py --running-type processes --counts 2 4 +python3 scripts/run_tests.py --running-type performance +``` + +Дополнительно: прямой запуск MPI-тестов именно для этой задачи: + +```bash +export OMPI_ALLOW_RUN_AS_ROOT=1 +export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 +export PPC_NUM_THREADS=4 +export OMP_NUM_THREADS=4 + +mpirun -np 2 -x PPC_NUM_THREADS -x OMP_NUM_THREADS ./build/bin/ppc_func_tests \ + --gtest_filter='*nazarova_k_char_count_processes_mpi_enabled*' + +./build/bin/ppc_perf_tests --gtest_filter='*nazarova_k_char_count_processes_seq_enabled*' +mpirun -np 2 -x PPC_NUM_THREADS -x OMP_NUM_THREADS ./build/bin/ppc_perf_tests \ + --gtest_filter='*nazarova_k_char_count_processes_mpi_enabled*' +``` + +## Результаты и обсуждение + +### Корректность + +Корректность проверяется функциональными тестами в `tasks/Nazarova_K_char_count/tests/functional/main.cpp`. + +В тестах проверяются случаи: + +- пустой текст; +- отсутствие вхождений `target`; +- детерминированная расстановка `target` в тексте для проверки ожидаемого количества. + +Корректность MPI-версии дополнительно подтверждается запуском под `mpirun -np 2`. + +### Производительность + +В performance-тесте создаётся вектор длины 10^6, выбирается `target='a'`, и целевой символ размещается на каждом 7-м индексе. Результат — суммарное число совпадений. + +Времена (режим Perf `task_run`): + +| Mode | Count | Time, s | Speedup | Efficiency | +|------|------:|----------:|--------:|-----------:| +| seq | 1 | 0.0002117 | 1.00 | N/A | +| mpi | 2 | 0.0001194 | 1.77 | 88.7% | + +Замечания: + +- операция очень лёгкая (сравнение символов), поэтому накладные расходы MPI заметны; +- при увеличении числа процессов ускорение может ограничиваться стоимостью `MPI_Allreduce` и пропускной способностью памяти. + +## Заключение + +Реализованы SEQ и MPI версии подсчёта вхождений символа в тексте. + +- SEQ-алгоритм имеет сложность O(n) и служит базовой реализацией. +- MPI-версия делит вход по индексам и использует `MPI_Allreduce`, обеспечивая корректный итог на каждом процессе. +- На тестовом размере 10^6 при 2 процессах получено ускорение 1.77. diff --git a/tasks/Nazarova_K_char_count/seq/include/ops_seq.hpp b/tasks/Nazarova_K_char_count/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..f36c61401f --- /dev/null +++ b/tasks/Nazarova_K_char_count/seq/include/ops_seq.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "Nazarova_K_char_count/common/include/common.hpp" + +namespace nazarova_k_char_count_processes { + +class NazarovaKCharCountSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit NazarovaKCharCountSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace nazarova_k_char_count_processes diff --git a/tasks/Nazarova_K_char_count/seq/src/ops_seq.cpp b/tasks/Nazarova_K_char_count/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..1388ff61bb --- /dev/null +++ b/tasks/Nazarova_K_char_count/seq/src/ops_seq.cpp @@ -0,0 +1,34 @@ +#include "Nazarova_K_char_count/seq/include/ops_seq.hpp" + +#include + +#include "Nazarova_K_char_count/common/include/common.hpp" + +namespace nazarova_k_char_count_processes { + +NazarovaKCharCountSEQ::NazarovaKCharCountSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool NazarovaKCharCountSEQ::ValidationImpl() { + return GetOutput() == 0; +} + +bool NazarovaKCharCountSEQ::PreProcessingImpl() { + GetOutput() = 0; + return true; +} + +bool NazarovaKCharCountSEQ::RunImpl() { + const auto &input = GetInput(); + GetOutput() = static_cast(std::count(input.text.begin(), input.text.end(), input.target)); + return true; +} + +bool NazarovaKCharCountSEQ::PostProcessingImpl() { + return true; +} + +} // namespace nazarova_k_char_count_processes diff --git a/tasks/Nazarova_K_char_count/settings.json b/tasks/Nazarova_K_char_count/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/Nazarova_K_char_count/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/Nazarova_K_char_count/tests/.clang-tidy b/tasks/Nazarova_K_char_count/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/Nazarova_K_char_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/Nazarova_K_char_count/tests/functional/main.cpp b/tasks/Nazarova_K_char_count/tests/functional/main.cpp new file mode 100644 index 0000000000..e1ff392edb --- /dev/null +++ b/tasks/Nazarova_K_char_count/tests/functional/main.cpp @@ -0,0 +1,83 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "Nazarova_K_char_count/common/include/common.hpp" +#include "Nazarova_K_char_count/mpi/include/ops_mpi.hpp" +#include "Nazarova_K_char_count/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace nazarova_k_char_count_processes { + +class NazarovaKCharCountRunFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + const int n = std::get<0>(test_param); + const char target = std::get<1>(test_param); + const bool has_target = std::get<2>(test_param); + return std::to_string(n) + "_" + std::string(1, target) + "_" + (has_target ? "has" : "no"); + } + + protected: + void SetUp() override { + const TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + const int n = std::get<0>(params); + const char target = std::get<1>(params); + const bool has_target = std::get<2>(params); + + input_data_.target = target; + input_data_.text.assign(static_cast(std::max(0, n)), 'q'); + + expected_ = 0; + if (has_target) { + // Put target at every 7th position (including 0) for deterministic expected result. + for (std::size_t i = 0; i < input_data_.text.size(); i++) { + if (i % 7 == 0) { + input_data_.text[i] = target; + expected_++; + } + } + } + } + + bool CheckTestOutputData(OutType &output_data) final { + return output_data == expected_; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_{}; + OutType expected_ = 0; +}; + +namespace { + +TEST_P(NazarovaKCharCountRunFuncTests, CountChar) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = {std::make_tuple(0, 'a', true), std::make_tuple(100, 'x', false), + std::make_tuple(10000, 'z', true)}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_Nazarova_K_char_count), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_Nazarova_K_char_count)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = NazarovaKCharCountRunFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(CharCountTests, NazarovaKCharCountRunFuncTests, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace nazarova_k_char_count_processes diff --git a/tasks/Nazarova_K_char_count/tests/performance/main.cpp b/tasks/Nazarova_K_char_count/tests/performance/main.cpp new file mode 100644 index 0000000000..a1451c4a50 --- /dev/null +++ b/tasks/Nazarova_K_char_count/tests/performance/main.cpp @@ -0,0 +1,51 @@ +#include + +#include + +#include "Nazarova_K_char_count/common/include/common.hpp" +#include "Nazarova_K_char_count/mpi/include/ops_mpi.hpp" +#include "Nazarova_K_char_count/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace nazarova_k_char_count_processes { + +class NazarovaKCharCountRunPerfTests : public ppc::util::BaseRunPerfTests { + static constexpr std::size_t kCount = 1'000'000; + InType input_data_{}; + OutType expected_{}; + + void SetUp() override { + input_data_.target = 'a'; + input_data_.text.assign(kCount, 'q'); + expected_ = 0; + for (std::size_t i = 0; i < input_data_.text.size(); i++) { + if (i % 7 == 0) { + input_data_.text[i] = input_data_.target; + expected_++; + } + } + } + + bool CheckTestOutputData(OutType &output_data) final { + return output_data == expected_; + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(NazarovaKCharCountRunPerfTests, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_Nazarova_K_char_count); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = NazarovaKCharCountRunPerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, NazarovaKCharCountRunPerfTests, kGtestValues, kPerfTestName); + +} // namespace nazarova_k_char_count_processes