diff --git a/tasks/maslova_u_char_frequency_count/common/include/common.hpp b/tasks/maslova_u_char_frequency_count/common/include/common.hpp new file mode 100644 index 0000000000..41747cab5e --- /dev/null +++ b/tasks/maslova_u_char_frequency_count/common/include/common.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include +#include + +#include "task/include/task.hpp" + +namespace maslova_u_char_frequency_count { + +using InType = std::pair; +using OutType = size_t; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace maslova_u_char_frequency_count diff --git a/tasks/maslova_u_char_frequency_count/data/pic.jpg b/tasks/maslova_u_char_frequency_count/data/pic.jpg new file mode 100644 index 0000000000..637624238c Binary files /dev/null and b/tasks/maslova_u_char_frequency_count/data/pic.jpg differ diff --git a/tasks/maslova_u_char_frequency_count/info.json b/tasks/maslova_u_char_frequency_count/info.json new file mode 100644 index 0000000000..68d706d661 --- /dev/null +++ b/tasks/maslova_u_char_frequency_count/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Ульяна", + "last_name": "Маслова", + "middle_name": "Александровна", + "group_number": "3823Б1ФИ2", + "task_number": "23" + } +} diff --git a/tasks/maslova_u_char_frequency_count/mpi/include/ops_mpi.hpp b/tasks/maslova_u_char_frequency_count/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..6971da96ae --- /dev/null +++ b/tasks/maslova_u_char_frequency_count/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "maslova_u_char_frequency_count/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace maslova_u_char_frequency_count { + +class MaslovaUCharFrequencyCountMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit MaslovaUCharFrequencyCountMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace maslova_u_char_frequency_count diff --git a/tasks/maslova_u_char_frequency_count/mpi/src/ops_mpi.cpp b/tasks/maslova_u_char_frequency_count/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..f0470c2adb --- /dev/null +++ b/tasks/maslova_u_char_frequency_count/mpi/src/ops_mpi.cpp @@ -0,0 +1,109 @@ +#include "maslova_u_char_frequency_count/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "maslova_u_char_frequency_count/common/include/common.hpp" + +namespace maslova_u_char_frequency_count { + +MaslovaUCharFrequencyCountMPI::MaslovaUCharFrequencyCountMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool MaslovaUCharFrequencyCountMPI::ValidationImpl() { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + int flag = 0; // 0 - всё ок, 1 - ошибка + if (rank == 0) { + if (GetInput().first.size() > static_cast(INT_MAX)) { + flag = 1; + } + } + MPI_Bcast(&flag, 1, MPI_INT, 0, MPI_COMM_WORLD); + return (flag == 0); +} + +bool MaslovaUCharFrequencyCountMPI::PreProcessingImpl() { + return true; +} + +bool MaslovaUCharFrequencyCountMPI::RunImpl() { + int rank = 0; + int proc_size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); // id процесса + MPI_Comm_size(MPI_COMM_WORLD, &proc_size); // количество процессов + + std::string input_string; + char input_char = 0; + size_t input_str_size = 0; + + if (rank == 0) { + input_string = GetInput().first; + input_char = GetInput().second; + input_str_size = input_string.size(); // получили данные + } + + uint64_t size_for_mpi = 0; + if (rank == 0) { + size_for_mpi = static_cast(input_str_size); // явное приведение перед передачей + } + + MPI_Bcast(&size_for_mpi, 1, MPI_UINT64_T, 0, MPI_COMM_WORLD); // отправляем размер строки + + if (rank != 0) { + input_str_size = static_cast(size_for_mpi); // возращаем обратно для удобного использования в дальнейшем + } + + if (input_str_size == 0) { + GetOutput() = 0; // ставим для всех процессов + return true; + } + + MPI_Bcast(&input_char, 1, MPI_CHAR, 0, MPI_COMM_WORLD); // отправляем нужный символ + + std::vector send_counts(proc_size); // здесь размеры всех порций + std::vector displs(proc_size); // смещения + if (rank == 0) { + size_t part = input_str_size / proc_size; + size_t rem = input_str_size % proc_size; + for (size_t i = 0; std::cmp_less(i, proc_size); ++i) { + send_counts[i] = static_cast(part + (i < rem ? 1 : 0)); // общий размер, включающий остаток, если он входит + } + displs[0] = 0; + for (size_t i = 1; std::cmp_less(i, proc_size); ++i) { + displs[i] = displs[i - 1] + send_counts[i - 1]; + } + } + + MPI_Bcast(send_counts.data(), proc_size, MPI_INT, 0, MPI_COMM_WORLD); // отправляем размеры порций + std::vector local_str(send_counts[rank]); + MPI_Scatterv((rank == 0) ? input_string.data() : nullptr, send_counts.data(), displs.data(), MPI_CHAR, + local_str.data(), static_cast(local_str.size()), MPI_CHAR, 0, MPI_COMM_WORLD // распределяем данные + ); + + size_t local_count = std::count(local_str.begin(), local_str.end(), input_char); + auto local_count_for_mpi = static_cast(local_count); + uint64_t global_count = 0; + MPI_Allreduce(&local_count_for_mpi, &global_count, 1, MPI_UINT64_T, MPI_SUM, + MPI_COMM_WORLD); // собрали данные со всех процессов + + GetOutput() = static_cast(global_count); // вывели результат, при этом приведя его к нужному нам типу + + return true; +} + +bool MaslovaUCharFrequencyCountMPI::PostProcessingImpl() { + return true; +} + +} // namespace maslova_u_char_frequency_count diff --git a/tasks/maslova_u_char_frequency_count/report.md b/tasks/maslova_u_char_frequency_count/report.md new file mode 100644 index 0000000000..d924a38ae4 --- /dev/null +++ b/tasks/maslova_u_char_frequency_count/report.md @@ -0,0 +1,129 @@ +# Подсчет частоты символа в строке + +- Student: Маслова Ульяна Александровна, group 3823Б1ФИ2 +- Technology: SEQ | MPI +- Variant: 23 + +## 1. Introduction +Проблема: Последовательный подсчет частоты символов в строках большого размера является медленным. +Задача: Ускорить этот процесс с помощью параллельных вычислений на MPI. +Ожидаемый результат: Значительное сокращение времени выполнения по сравнению с последовательной версией. + +## 2. Problem Statement +Нужно найти число вхождений символа input_char в строке input_str. +- InPut: Пара (std::string, char). +- OutPut: Целое число (size_t). + +## 3. Baseline Algorithm (Sequential) +Проход по строке в цикле с увеличением счетчика при нахождении искомого символа. Алгоритм имеет линейную временную сложность O(N), где N — длина строки. + +## 4. Parallelization Scheme +Процесс с рангом 0 делит исходную строку на P (число процессов) частей. Далее ранг 0 рассылает каждому процессу его фрагмент строки. Каждый процесс независимо считает символы в своей части. Локальные счетчики суммируются на ранге 0. + +## 5. Implementation Details +- common: Определяет общие типы данных (InType, OutType). +- seq: Содержит простую последовательную реализацию алгоритма. +- mpi: Содержит параллельную MPI-реализацию алгоритма. +- tests: Включает два набора тестов: functional для проверки корректности и performance для замера скорости. +Максимальная длинна строки, которая может быть обработана программой - 231 - 1, что составляет 2 147 483 647 символов. + +## 6. Experimental Setup +- Аппаратное обеспечение: AMD Ryzen 7 7840HS (8 ядер, 16 логических процессоров, базовая частота 3,80 ГГц) +- ОЗУ — 16 ГБ +- Операционная система: Windows 11 +- Компилятор: g++ +- Тип сборки: Release + +## 7. Results and Discussion + +### 7.1 Correctness +Корректность проверялась на строках различной длинны, а также различного типа (пустые, из одного слова, только из букв, смешанные и т.д.) + +### 7.2 Performance + +Тест на данных, состоящих из 100 000 000 символов: + +| Mode | Count | Time, s | Speedup | Efficiency | +|------|-------|-----------|---------|------------| +| seq | 1 | 0.07242 | 1.00 | N/A | +| mpi | 2 | 0.04439 | 1.63 | 81.5% | +| mpi | 4 | 0.03100 | 2.34 | 58.5% | +| mpi | 8 | 0.03050 | 2.37 | 29.6% | + +## 8. Conclusions +Мы видим значительное повышение производительности. С увеличением числа процессов время выполнения сокращается, в связи с этим мы имеем ускорение 2.34 на 4 процессах. В свою очередь эффективность падает с ростом числа процессов, так как накладные расходы на коммуникацию MPI на 8 процессах начинают перевешивать выгоду от параллелизма. + +## 9. References +1. Лекции и практики курса "Параллельное программирование" + +## Appendix (Optional) +```cpp +bool MaslovaUCharFrequencyCountMPI::RunImpl() { + int rank = 0; + int proc_size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); // id процесса + MPI_Comm_size(MPI_COMM_WORLD, &proc_size); // количество процессов + + std::string input_string; + char input_char = 0; + size_t input_str_size = 0; + + if (rank == 0) { + input_string = GetInput().first; + input_char = GetInput().second; + input_str_size = input_string.size(); // получили данные + } + + uint64_t size_for_mpi = 0; + if (rank == 0) { + size_for_mpi = static_cast(input_str_size); // явное приведение перед передачей + } + + MPI_Bcast(&size_for_mpi, 1, MPI_UINT64_T, 0, MPI_COMM_WORLD); // отправляем размер строки + + if (rank != 0) { + input_str_size = static_cast(size_for_mpi); // возращаем обратно для удобного использования в дальнейшем + } + + if (input_str_size == 0) { + GetOutput() = 0; // ставим для всех процессов + return true; + } + + MPI_Bcast(&input_char, 1, MPI_CHAR, 0, MPI_COMM_WORLD); // отправляем нужный символ + + std::vector send_counts(proc_size); // здесь размеры всех порций + std::vector displs(proc_size); // смещения + if (rank == 0) { + size_t part = input_str_size / proc_size; + size_t rem = input_str_size % proc_size; + for (size_t i = 0; std::cmp_less(i, proc_size); ++i) { + send_counts[i] = static_cast(part + (i < rem ? 1 : 0)); // общий размер, включающий остаток, если он входит + } + displs[0] = 0; + for (size_t i = 1; std::cmp_less(i, proc_size); ++i) { + displs[i] = displs[i - 1] + send_counts[i - 1]; + } + } + + MPI_Bcast(send_counts.data(), proc_size, MPI_INT, 0, MPI_COMM_WORLD); // отправляем размеры порций + std::vector local_str(send_counts[rank]); + MPI_Scatterv((rank == 0) ? input_string.data() : nullptr, send_counts.data(), displs.data(), MPI_CHAR, + local_str.data(), static_cast(local_str.size()), MPI_CHAR, 0, MPI_COMM_WORLD // распределяем данные + ); + + size_t local_count = std::count(local_str.begin(), local_str.end(), input_char); + auto local_count_for_mpi = static_cast(local_count); + uint64_t global_count = 0; + MPI_Allreduce(&local_count_for_mpi, &global_count, 1, MPI_UINT64_T, MPI_SUM, + MPI_COMM_WORLD); // собрали данные со всех процессов + + GetOutput() = static_cast(global_count); // вывели результат, при этом приведя его к нужному нам типу + + return true; +} + +bool MaslovaUCharFrequencyCountMPI::PostProcessingImpl() { + return true; +} +``` \ No newline at end of file diff --git a/tasks/maslova_u_char_frequency_count/seq/include/ops_seq.hpp b/tasks/maslova_u_char_frequency_count/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..cb8cfca419 --- /dev/null +++ b/tasks/maslova_u_char_frequency_count/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "maslova_u_char_frequency_count/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace maslova_u_char_frequency_count { + +class MaslovaUCharFrequencyCountSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit MaslovaUCharFrequencyCountSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace maslova_u_char_frequency_count diff --git a/tasks/maslova_u_char_frequency_count/seq/src/ops_seq.cpp b/tasks/maslova_u_char_frequency_count/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..2dbae1dcae --- /dev/null +++ b/tasks/maslova_u_char_frequency_count/seq/src/ops_seq.cpp @@ -0,0 +1,44 @@ +#include "maslova_u_char_frequency_count/seq/include/ops_seq.hpp" + +#include +#include +#include + +#include "maslova_u_char_frequency_count/common/include/common.hpp" + +namespace maslova_u_char_frequency_count { + +MaslovaUCharFrequencyCountSEQ::MaslovaUCharFrequencyCountSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool MaslovaUCharFrequencyCountSEQ::ValidationImpl() { + return GetInput().first.size() <= static_cast(INT_MAX); +} + +bool MaslovaUCharFrequencyCountSEQ::PreProcessingImpl() { + return true; +} + +bool MaslovaUCharFrequencyCountSEQ::RunImpl() { + std::string &input_string = GetInput().first; + char input_char = GetInput().second; // получили данные + size_t frequency_count = 0; + + for (const char c : input_string) { + if (c == input_char) { + frequency_count++; + } + } + + GetOutput() = frequency_count; // отправили данные + return true; +} + +bool MaslovaUCharFrequencyCountSEQ::PostProcessingImpl() { + return true; +} + +} // namespace maslova_u_char_frequency_count diff --git a/tasks/maslova_u_char_frequency_count/settings.json b/tasks/maslova_u_char_frequency_count/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/maslova_u_char_frequency_count/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/maslova_u_char_frequency_count/tests/.clang-tidy b/tasks/maslova_u_char_frequency_count/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/maslova_u_char_frequency_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/maslova_u_char_frequency_count/tests/functional/main.cpp b/tasks/maslova_u_char_frequency_count/tests/functional/main.cpp new file mode 100644 index 0000000000..1970dd48b8 --- /dev/null +++ b/tasks/maslova_u_char_frequency_count/tests/functional/main.cpp @@ -0,0 +1,74 @@ +#include + +#include +#include +#include +#include + +#include "maslova_u_char_frequency_count/common/include/common.hpp" +#include "maslova_u_char_frequency_count/mpi/include/ops_mpi.hpp" +#include "maslova_u_char_frequency_count/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace maslova_u_char_frequency_count { + +class MaslovaURunFuncTestsProcesses : public ppc::util::BaseRunFuncTests { + public: + MaslovaURunFuncTestsProcesses() = default; + + static std::string PrintTestParam(const TestType &test_param) { + return std::get<2>(test_param); + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + input_data_ = std::get<0>(params); + expected_ = std::get<1>(params); + } + + bool CheckTestOutputData(OutType &output_data) final { + return expected_ == output_data; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; + OutType expected_ = 0; +}; + +namespace { + +TEST_P(MaslovaURunFuncTestsProcesses, charFrequencyCount) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + {std::make_tuple(InType("", 'a'), 0, "empty_string"), std::make_tuple(InType("a", 'a'), 1, "single_char_match"), + std::make_tuple(InType("b", 'x'), 0, "single_char_no_match"), + std::make_tuple(InType("maslova", 'a'), 2, "normal_string_az"), + std::make_tuple(InType("zzzzzzzz", 'u'), 0, "all_other_chars"), + std::make_tuple(InType("aaaaaaaa", 'a'), 8, "all_input_chars"), + std::make_tuple(InType("Hello World!", 'l'), 3, "mixed_symbols"), + std::make_tuple(InType("1234567890", '1'), 1, "string_with_numbers"), + std::make_tuple(InType("123456789abcdlfn", 'a'), 1, "aligned_string_16"), + std::make_tuple(InType("123456789abcd", 'a'), 1, "unaligned_string_13"), + std::make_tuple(InType("+++--!", '+'), 3, "string_with_oper"), + std::make_tuple(InType("1234567890sexdcrfvgbhjnkml", '1'), 1, "string_with_lettes_and_numbers"), + std::make_tuple(InType(std::string(100, 'x') + std::string(50, 'a'), 'x'), 100, "long_string")}}; + +const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_maslova_u_char_frequency_count), + ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_maslova_u_char_frequency_count)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); +const auto kTestName = MaslovaURunFuncTestsProcesses::PrintFuncTestName; +INSTANTIATE_TEST_SUITE_P(charFreqTests, MaslovaURunFuncTestsProcesses, kGtestValues, kTestName); + +} // namespace +} // namespace maslova_u_char_frequency_count diff --git a/tasks/maslova_u_char_frequency_count/tests/performance/main.cpp b/tasks/maslova_u_char_frequency_count/tests/performance/main.cpp new file mode 100644 index 0000000000..6737b10bea --- /dev/null +++ b/tasks/maslova_u_char_frequency_count/tests/performance/main.cpp @@ -0,0 +1,60 @@ +#include + +#include +#include + +#include "maslova_u_char_frequency_count/common/include/common.hpp" +#include "maslova_u_char_frequency_count/mpi/include/ops_mpi.hpp" +#include "maslova_u_char_frequency_count/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace maslova_u_char_frequency_count { + +class MaslovaUPerfTests : public ppc::util::BaseRunPerfTests { + private: + InType input_data_; + OutType expected_output_{}; + + protected: + void SetUp() override { + const size_t str_size = 100000000; + const char input_char = 'y'; + + std::string generated_string(str_size, 'a'); + size_t expected_count = 0; + + if constexpr (str_size > 100) { + for (size_t i = 0; i < str_size / 100; ++i) { + generated_string[i * 50] = input_char; + expected_count++; + } + } + + input_data_ = {generated_string, input_char}; + expected_output_ = expected_count; + } + + InType GetTestInputData() final { + return input_data_; + } + + bool CheckTestOutputData(OutType &output_data) final { + return output_data == expected_output_; + } +}; + +TEST_P(MaslovaUPerfTests, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_maslova_u_char_frequency_count); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = MaslovaUPerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, MaslovaUPerfTests, kGtestValues, kPerfTestName); + +} // namespace maslova_u_char_frequency_count