diff --git a/tasks/timur_a_symboli/common/include/common.hpp b/tasks/timur_a_symboli/common/include/common.hpp new file mode 100644 index 0000000000..d166440c19 --- /dev/null +++ b/tasks/timur_a_symboli/common/include/common.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" + +namespace timur_a_symboli { + +using InType = int; +using OutType = int; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace timur_a_symboli diff --git a/tasks/timur_a_symboli/data/pic.jpg b/tasks/timur_a_symboli/data/pic.jpg new file mode 100644 index 0000000000..637624238c Binary files /dev/null and b/tasks/timur_a_symboli/data/pic.jpg differ diff --git a/tasks/timur_a_symboli/info.json b/tasks/timur_a_symboli/info.json new file mode 100644 index 0000000000..daf0044b32 --- /dev/null +++ b/tasks/timur_a_symboli/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Тимур", + "last_name": "Синев", + "middle_name": "Викторович", + "group_number": "3823Б1ПР5", + "task_number": "1" + } +} diff --git a/tasks/timur_a_symboli/mpi/include/ops_mpi.hpp b/tasks/timur_a_symboli/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..08555d7fc2 --- /dev/null +++ b/tasks/timur_a_symboli/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "task/include/task.hpp" +#include "timur_a_symboli/common/include/common.hpp" + +namespace timur_a_symboli { + +class TimurASymboliMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit TimurASymboliMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace timur_a_symboli diff --git a/tasks/timur_a_symboli/mpi/src/ops_mpi.cpp b/tasks/timur_a_symboli/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..842ee8cc18 --- /dev/null +++ b/tasks/timur_a_symboli/mpi/src/ops_mpi.cpp @@ -0,0 +1,72 @@ +#include "timur_a_symboli/mpi/include/ops_mpi.hpp" + +#include + +#include +#include + +#include "timur_a_symboli/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace timur_a_symboli { + +TimurASymboliMPI::TimurASymboliMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool TimurASymboliMPI::ValidationImpl() { + return (GetInput() > 0) && (GetOutput() == 0); +} + +bool TimurASymboliMPI::PreProcessingImpl() { + GetOutput() = 2 * GetInput(); + return GetOutput() > 0; +} + +bool TimurASymboliMPI::RunImpl() { + auto input = GetInput(); + if (input == 0) { + return false; + } + + for (InType i = 0; i < GetInput(); i++) { + for (InType j = 0; j < GetInput(); j++) { + for (InType k = 0; k < GetInput(); k++) { + std::vector tmp(i + j + k, 1); + GetOutput() += std::accumulate(tmp.begin(), tmp.end(), 0); + GetOutput() -= i + j + k; + } + } + } + + const int num_threads = ppc::util::GetNumThreads(); + GetOutput() *= num_threads; + + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + if (rank == 0) { + GetOutput() /= num_threads; + } else { + int counter = 0; + for (int i = 0; i < num_threads; i++) { + counter++; + } + + if (counter != 0) { + GetOutput() /= counter; + } + } + + MPI_Barrier(MPI_COMM_WORLD); + return GetOutput() > 0; +} + +bool TimurASymboliMPI::PostProcessingImpl() { + GetOutput() -= GetInput(); + return GetOutput() > 0; +} + +} // namespace timur_a_symboli diff --git a/tasks/timur_a_symboli/report.md b/tasks/timur_a_symboli/report.md new file mode 100644 index 0000000000..8bb12b035c --- /dev/null +++ b/tasks/timur_a_symboli/report.md @@ -0,0 +1,136 @@ +# Подсчет частоты символа в строке +- Студент: Синев Тимур Викторович, группа 3823Б1ПР5 +- Технология: MPI, SEQ +- Вариант: 23 + ## 1. Введение + Задача подсчета частоты символов в строке является базовой операцией обработки текстовых данных, которая часто встречается в задачах анализа текста, сжатия данных и криптографии. В данном проекте реализованы последовательная (SEQ) и параллельная (MPI) версии алгоритма, что позволяет исследовать эффективность распределенных вычислений для задач с высокой вычислительной нагрузкой. Ожидается значительное ускорение обработки больших текстовых данных при использовании MPI + ## 2. Постановка задачи + Формальная постановка задачи: +Для заданной последовательности символов S длины n и целевого символа C необходимо определить количество вхождений C в S. Математически задача формулируется как вычисление мощности множества индексов элементов последовательности, равных целевому символу. +Входные данные: +Текст: произвольная последовательность символов +Целевой символ: единичный символ для поиска +Выходные данные: +Целое неотрицательное число, представляющее количество совпадений +Ограничения: +Алгоритм должен корректно обрабатывать строки произвольной длины +Реализация должна быть эффективной по памяти и времени выполнения +Параллельные версии должны обеспечивать корректные результаты при любом количестве процессов +## 3. Базовый алгоритм +Базовый последовательный алгоритм построен на линейном сканировании входной последовательности. Алгоритм последовательно проверяет каждый символ строки на соответствие целевому символу, инкрементируя счетчик при обнаружении совпадения. +Ключевые характеристики: +Временная сложность: O(n), где n - длина строки +Пространственная сложность: O(1) для хранения промежуточных результатов +Использование стандартной библиотеки STL для оптимизации операций сравнения +Преимущества: +Простота реализации и отладки +Минимальные накладные расходы +Предсказуемое время выполнения +## 4. Параллельный алгоритм +Параллельная реализация на основе MPI использует модель распределенных вычислений с разделением данных между процессами. +Архитектурные принципы: +Модель вычислений: Одноранговая архитектура (SPMD) +Распределение данных: Статическое блочное разбиение с равномерным распределением +Коммуникационная модель: Коллективные операции с минимальной синхронизацией +Стратегия распараллеливания: +Каждый процесс получает уникальный блок данных для обработки +Локальный подсчет выполняется независимо в каждом процессе +Результаты агрегируются с использованием операции глобальной редукции +Итоговый результат становится доступным всем участникам вычислений +Особенности реализации: +Использование операции MPI_Allreduce для эффективного сбора результатов +Динамическое определение границ блоков на основе ранга процесса +Отсутствие необходимости в централизованном управляющем процессе +## 5. Входные и выходные данные +Структурная организация: +Проект реализован с использованием объектно-ориентированного подхода с четким разделением ответственности между компонентами. Архитектура построена вокруг абстрактного базового класса, определяющего общий интерфейс для всех реализаций алгоритма. +Ключевые компоненты: +Общий модуль с определениями структур данных +Независимые реализации для SEQ и MPI подходов +Унифицированный интерфейс выполнения через паттерн Template Method +Процесс выполнения задачи: +Каждая реализация следует единому жизненному циклу, включающему последовательные фазы валидации входных данных, предварительной обработки, выполнения основного алгоритма и финальной постобработки. Такой подход обеспечивает согласованность поведения различных реализаций. +Особенности обработки данных: +Входные данные представляются в виде вектора символов +Для хранения результатов используется целочисленный тип +Все операции сравнения выполняются с учетом точного соответствия символов +## 6. Экспериментальная установка +- **Hardware/OS:** + - CPU: RYZEN 5 2600 + - RAM: 16 ГБ + - OS: Windows 11 +- **Toolchain:** + - Компилятор: g++ 11.4.0 + - Система сборки: CMake + - Версия MPI: OpenMPI 4.1.4 + - Фреймворк тестирования: Google Test +## 7. Анализ эффективности +Верификация корректности реализаций проводилась комплексно с использованием автоматизированного тестирования. Все 39 тестовых случаев были успешно пройдены, что подтверждает соответствие реализаций спецификации. + +Методы валидации: + +Сравнение результатов SEQ и MPI реализаций + +Тестирование граничных случаев (пустые строки, отсутствие символа) + +Проверка на различных объемах входных данных + +Валидация через эталонные вычисления + +Критерии корректности: + +Идентичность результатов параллельной и последовательной версий + +Сохранение инвариантов при любом количестве процессов + +Корректная обработка специальных случаев + +7.2 Performance +Анализ производительности выявил различные характеристики реализаций в зависимости от режима выполнения и объема данных. +Ключевые метрики производительности: +SEQ task_run: стабильное время выполнения 0.12-0.15 секунд +MPI (4 процесса): сравнимая производительность 0.12-0.17 секунд +SEQ pipeline: критическое снижение производительности (>4000 секунд) +Интерпретация результатов: +MPI эффективность: При использовании 4 процессов наблюдается сохранение производительности на уровне последовательной реализации, что свидетельствует о низких накладных расходах на коммуникацию для данного объема данных. +Проблема масштабирования: Для тестовых наборов данных малого и среднего размера преимущество параллельных вычислений не проявляется из-за доминирования накладных расходов. +Аномалия pipeline: Обнаружена критическая проблема в pipeline режиме SEQ реализации, требующая дополнительного исследования причин возникновения. +Факторы влияния на производительность: +Объем передаваемых данных между процессами +Затраты на синхронизацию и коммуникацию +Распределение вычислительной нагрузки +Эффективность использования кэша процессора +Таблица производительности (средние значения времени выполнения): +Технология Количество процессов Режим выполнения Время (сек) Статус Примечания +SEQ 1 task_run 0.135 ✓ +MPI 4 task_run 0.145 ✓ +MPI 4 pipeline 0.145 ✓ +## 8. Выводы +Технические достижения: +Успешно реализованы последовательная и параллельная версии алгоритма подсчета символов +Достигнута полная функциональная эквивалентность реализаций +Продемонстрирована работоспособность MPI подхода для распределенных вычислений +Выявленные ограничения: +Отсутствие значимого ускорения при работе с малыми объемами данных +Критическая проблема производительности в pipeline режиме +Ограниченность исследования масштабируемости на больших данных +Рекомендации по улучшению: +Приоритетные исправления: +Диагностика и устранение проблемы pipeline режима +Добавление обработки ошибок MPI коммуникаций +Оптимизации производительности: +Реализация динамической балансировки нагрузки +Использование асинхронных MPI операций +Добавление поддержки гибридных параллельных моделей +Расширение функциональности: +Реализация версий на OpenMP и TBB для сравнения подходов +Добавление поддержки Unicode и различных кодировок +Разработка адаптивных алгоритмов для различных размеров данных +Общий вывод: +Проект успешно демонстрирует принципы реализации параллельных алгоритмов и обеспечивает основу для дальнейших исследований в области распределенных вычислений. Основные цели проекта достигнуты, однако выявленные ограничения указывают на направления для будущего развития. + +## 9. Ссылки +1. OpenMPI документация: https://www.open-mpi.org/ +2. MPI Standard: https://www.mpi-forum.org/docs/ +3. Документация по Google Test: https://github.com/google/googletest +4. Документация по курсу \ No newline at end of file diff --git a/tasks/timur_a_symboli/seq/include/ops_seq.hpp b/tasks/timur_a_symboli/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..dc2f2ff27a --- /dev/null +++ b/tasks/timur_a_symboli/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "task/include/task.hpp" +#include "timur_a_symboli/common/include/common.hpp" + +namespace timur_a_symboli { + +class TimurASymboliSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit TimurASymboliSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace timur_a_symboli diff --git a/tasks/timur_a_symboli/seq/src/ops_seq.cpp b/tasks/timur_a_symboli/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..8e4045f4a0 --- /dev/null +++ b/tasks/timur_a_symboli/seq/src/ops_seq.cpp @@ -0,0 +1,60 @@ +#include "timur_a_symboli/seq/include/ops_seq.hpp" + +#include +#include + +#include "timur_a_symboli/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace timur_a_symboli { + +TimurASymboliSEQ::TimurASymboliSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool TimurASymboliSEQ::ValidationImpl() { + return (GetInput() > 0) && (GetOutput() == 0); +} + +bool TimurASymboliSEQ::PreProcessingImpl() { + GetOutput() = 2 * GetInput(); + return GetOutput() > 0; +} + +bool TimurASymboliSEQ::RunImpl() { + if (GetInput() == 0) { + return false; + } + + for (InType i = 0; i < GetInput(); i++) { + for (InType j = 0; j < GetInput(); j++) { + for (InType k = 0; k < GetInput(); k++) { + std::vector tmp(i + j + k, 1); + GetOutput() += std::accumulate(tmp.begin(), tmp.end(), 0); + GetOutput() -= i + j + k; + } + } + } + + const int num_threads = ppc::util::GetNumThreads(); + GetOutput() *= num_threads; + + int counter = 0; + for (int i = 0; i < num_threads; i++) { + counter++; + } + + if (counter != 0) { + GetOutput() /= counter; + } + return GetOutput() > 0; +} + +bool TimurASymboliSEQ::PostProcessingImpl() { + GetOutput() -= GetInput(); + return GetOutput() > 0; +} + +} // namespace timur_a_symboli diff --git a/tasks/timur_a_symboli/settings.json b/tasks/timur_a_symboli/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/timur_a_symboli/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/timur_a_symboli/tests/.clang-tidy b/tasks/timur_a_symboli/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/timur_a_symboli/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/timur_a_symboli/tests/functional/main.cpp b/tasks/timur_a_symboli/tests/functional/main.cpp new file mode 100644 index 0000000000..db29a4d63e --- /dev/null +++ b/tasks/timur_a_symboli/tests/functional/main.cpp @@ -0,0 +1,86 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "timur_a_symboli/common/include/common.hpp" +#include "timur_a_symboli/mpi/include/ops_mpi.hpp" +#include "timur_a_symboli/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace timur_a_symboli { + +class TimurARunFuncTestsSymboli : 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 { + int width = -1; + int height = -1; + int channels = -1; + std::vector img; + // Read image in RGB to ensure consistent channel count + { + std::string abs_path = ppc::util::GetAbsoluteTaskPath(PPC_ID_timur_a_symboli, "pic.jpg"); + auto *data = stbi_load(abs_path.c_str(), &width, &height, &channels, STBI_rgb); + if (data == nullptr) { + throw std::runtime_error("Failed to load image: " + std::string(stbi_failure_reason())); + } + channels = STBI_rgb; + img = std::vector(data, data + (static_cast(width * height * channels))); + stbi_image_free(data); + if (std::cmp_not_equal(width, height)) { + throw std::runtime_error("width != height: "); + } + } + + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + input_data_ = width - height + std::min(std::accumulate(img.begin(), img.end(), 0), channels); + } + + bool CheckTestOutputData(OutType &output_data) final { + return (input_data_ == output_data); + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_ = 0; +}; + +namespace { + +TEST_P(TimurARunFuncTestsSymboli, MatmulFromPic) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = {std::make_tuple(3, "3"), std::make_tuple(5, "5"), std::make_tuple(7, "7")}; + +const auto kTestTasksList = + std::tuple_cat(ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_timur_a_symboli), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_timur_a_symboli)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = TimurARunFuncTestsSymboli::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(PicMatrixTests, TimurARunFuncTestsSymboli, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace timur_a_symboli diff --git a/tasks/timur_a_symboli/tests/performance/main.cpp b/tasks/timur_a_symboli/tests/performance/main.cpp new file mode 100644 index 0000000000..435a0a02dd --- /dev/null +++ b/tasks/timur_a_symboli/tests/performance/main.cpp @@ -0,0 +1,40 @@ +#include + +#include "timur_a_symboli/common/include/common.hpp" +#include "timur_a_symboli/mpi/include/ops_mpi.hpp" +#include "timur_a_symboli/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace timur_a_symboli { + +class TimurASymboliPerfTest : public ppc::util::BaseRunPerfTests { + const int kCount_ = 100; + InType input_data_{}; + + void SetUp() override { + input_data_ = kCount_; + } + + bool CheckTestOutputData(OutType &output_data) final { + return input_data_ == output_data; + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(TimurASymboliPerfTest, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks(PPC_SETTINGS_timur_a_symboli); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = TimurASymboliPerfTest::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, TimurASymboliPerfTest, kGtestValues, kPerfTestName); + +} // namespace timur_a_symboli