diff --git a/tasks/ovsyannikov_n_simpson_method/all/include/ops_all.hpp b/tasks/ovsyannikov_n_simpson_method/all/include/ops_all.hpp new file mode 100644 index 000000000..299730646 --- /dev/null +++ b/tasks/ovsyannikov_n_simpson_method/all/include/ops_all.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "ovsyannikov_n_simpson_method/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace ovsyannikov_n_simpson_method { + +class OvsyannikovNSimpsonMethodALL : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kALL; + } + explicit OvsyannikovNSimpsonMethodALL(const InType &in); + + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + private: + static double Function(double x, double y); + static double GetCoeff(int i, int n); + InType params_ = {}; + OutType res_ = 0.0; +}; + +} // namespace ovsyannikov_n_simpson_method diff --git a/tasks/ovsyannikov_n_simpson_method/all/report.md b/tasks/ovsyannikov_n_simpson_method/all/report.md new file mode 100644 index 000000000..72ee4e24d --- /dev/null +++ b/tasks/ovsyannikov_n_simpson_method/all/report.md @@ -0,0 +1,107 @@ +# Вычисление многомерных интегралов с использованием многошаговой схемы (метод Симпсона) + +- **Student**: Овсянников Никита, группа 3823Б1ФИ2 +- **Technology**: ALL (SEQ, OMP, TBB, STL, MPI) +- **Variant**: 11 + +## 1. Introduction + +Данная работа посвящена реализации последовательного и параллельных алгоритмов численного +интегрирования функции двух переменных f(x,y)=x+y по составной формуле Симпсона (методу парабол). +Целью работы является сравнение эффективности различных технологий параллельного программирования +(OpenMP, TBB, STL threads и MPI) при решении вычислительно трудоемких задач на многоядерных системах. + +## 2. Problem Statement + +Задача: Вычислить двойной интеграл ∫(ay,by)∫(ax,bx)(x+y)dxdy на прямоугольной области. + +Ограничения: + +1) Количество узлов разбиения сетки по каждой оси должно быть четным и положительным, +что является обязательным условием применения классического метода Симпсона. +2) Необходимо обеспечить точность вычислений и корректную редукцию (суммирование) +результатов из параллельных потоков. + +## 3. Baseline Algorithm (Sequential) + +Последовательный алгоритм реализует классическую схему: + +1) Область интегрирования разбивается на сетку с шагами hx и hy. +2) Вычисляется взвешенная сумма значений функции во всех узлах сетки. Коэффициенты весов зависят +от положения узла: + 2.1) Углы: 1; + 2.2) Границы (не углы): 2 или 4; + 2.3) Внутренние узлы: 4, 8 или 16 (согласно произведению одномерных коэффициентов). +3) Итоговая сумма умножается на (hx⋅hy)/9. + +## 4. Parallelization Scheme + +Параллельные версии используют декомпозицию по внешнему циклу (интегрирование по строкам сетки): + +1) OpenMP: Использование директивы #pragma omp parallel for с механизмом reduction(+:total_sum) +для автоматического сбора результатов. +2) TBB: Применение алгоритма tbb::parallel_reduce, который эффективно разделяет диапазон строк +между потоками и суммирует локальные результаты. +3) STL: Ручное создание пула потоков std::thread, разделение сетки на равные «чанки» и запись +локальных сумм в защищенный от race condition вектор с финальным сложением через std::accumulate. +4) ALL (Hybrid): Совмещение MPI (для распределения данных между узлами/процессами) и +OpenMP/TBB (для внутреннего распараллеливания в рамках одного процесса). + +## 5. Implementation Details + +Валидация: В каждой реализации метод ValidationImpl проверяет входные параметры на четность +и положительность. +Оптимизация: Для снижения когнитивной сложности и устранения избыточных тернарных операторов +логика выбора коэффициентов Симпсона вынесена в отдельный метод GetCoeff. +Безопасность потоков: Во всех версиях используется локальное копирование полей класса в стек +потока для исключения конфликтов доступа и оптимизации работы кэша процессора. +Специфика MPI: В гибридной версии используется MPI_Reduce для сбора локальных сумм со всех +процессов на главный узел (rank 0). + +## 6. Experimental Setup + +Hardware: 13th Gen Intel(R) Core(TM) i5-13500H, 12 ядер (16 логических потоков). +RAM: 16.0 GB (Speed: 4266 MT/s). +OS: Windows 11 (VS 2026 Insiders / Ninja Build). +Toolchain: MSVC v143, CMake 3.30. +Data: Сетка размером 2000×2000 узлов. + +## 7. Results and Discussion + +### 7.1 Correctness + +Корректность всех реализаций подтверждена 30 функциональными тестами. Проверены случаи с нормальной +сеткой, нулевой шириной интервала (результат 0.0) и отрицательными координатами. + +### 7.2 Performance + +Время измерялось на фиксированном числе потоков (соответствующем числу ядер CPU). + +| Mode | Count (Nodes) | Time, s | Speedup | +|------|---------------|---------|---------| +| SEQ | 2000x2000 | 0.1285 | 1.00 | +| OMP | 2000x2000 | 0.0134 | 9.59 | +| TBB | 2000x2000 | 0.1302 | 0.98 | +| STL | 2000x2000 | 0.1354 | 0.95 | +| ALL | 2000x2000 | 0.2199 | 0.58 | + +**Анализ результатов**: + +OpenMP показал наилучший результат с ускорением почти в 9.6 раз, что близко к идеальному для +12-ядерного процессора. Это объясняется минимальными накладными расходами директивы parallel for. + +TBB и STL локально показали время, близкое к последовательной версии. Это связано с особенностями +планировщика Windows и накладными расходами на создание потоков std::thread для относительно +быстрой задачи (0.1 сек). В CI-окружении на Linux данные технологии показывают значительно +большую эффективность. + +ALL (MPI) показал замедление, так как запуск нескольких процессов MPI на одной локальной машине +добавляет значительные задержки на межпроцессное взаимодействие (IPC) и синхронизацию. + +## 8. Conclusions + +В ходе работы были успешно реализованы все запланированные версии алгоритма Симпсона. Наиболее +эффективной технологией для данной задачи в среде Windows оказалась OpenMP. Гибридная схема (ALL) +продемонстрировала работоспособность всех механизмов синхронизации (MPI + OMP + TBB), однако её +применение оправдано только на распределенных кластерах или при значительно больших объемах +вычислений. diff --git a/tasks/ovsyannikov_n_simpson_method/all/src/ops_all.cpp b/tasks/ovsyannikov_n_simpson_method/all/src/ops_all.cpp new file mode 100644 index 000000000..1b7b747a1 --- /dev/null +++ b/tasks/ovsyannikov_n_simpson_method/all/src/ops_all.cpp @@ -0,0 +1,107 @@ +#include "ovsyannikov_n_simpson_method/all/include/ops_all.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "ovsyannikov_n_simpson_method/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace ovsyannikov_n_simpson_method { + +double OvsyannikovNSimpsonMethodALL::Function(double x, double y) { + return x + y; +} + +double OvsyannikovNSimpsonMethodALL::GetCoeff(int i, int n) { + if (i == 0 || i == n) { + return 1.0; + } + return (i % 2 == 1) ? 4.0 : 2.0; +} + +OvsyannikovNSimpsonMethodALL::OvsyannikovNSimpsonMethodALL(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool OvsyannikovNSimpsonMethodALL::ValidationImpl() { + return GetInput().nx > 0 && GetInput().nx % 2 == 0 && GetInput().ny > 0 && GetInput().ny % 2 == 0; +} + +bool OvsyannikovNSimpsonMethodALL::PreProcessingImpl() { + params_ = GetInput(); + res_ = 0.0; + return true; +} + +bool OvsyannikovNSimpsonMethodALL::RunImpl() { + int rank = 0; + int size = 1; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + const int nx_l = params_.nx; + const int ny_l = params_.ny; + const double ax_l = params_.ax; + const double ay_l = params_.ay; + const double hx = (params_.bx - params_.ax) / nx_l; + const double hy = (params_.by - params_.ay) / ny_l; + + int total_rows = nx_l + 1; + int rows_per_rank = total_rows / size; + int remainder = total_rows % size; + + int my_start = rank * rows_per_rank + std::min(rank, remainder); + int my_end = my_start + rows_per_rank + (rank < remainder ? 1 : 0); + + double local_sum = 0.0; + +#pragma omp parallel for default(none) shared(my_start, my_end, nx_l, ny_l, ax_l, ay_l, hx, hy) reduction(+ : local_sum) + for (int i = my_start; i < my_end; ++i) { + const double x = ax_l + (static_cast(i) * hx); + const double coeff_x = GetCoeff(i, nx_l); + + double row_sum = tbb::parallel_reduce(tbb::blocked_range(0, ny_l + 1), 0.0, + [&](const tbb::blocked_range &r, double sum) { + for (int j = r.begin(); j < r.end(); ++j) { + const double y = ay_l + (static_cast(j) * hy); + const double coeff_y = GetCoeff(j, ny_l); + sum += coeff_y * Function(x, y); + } + return sum; + }, std::plus<>()); + + local_sum += coeff_x * row_sum; + } + + double total_sum = 0.0; + MPI_Reduce(&local_sum, &total_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); + + if (rank == 0) { + res_ = (hx * hy / 9.0) * total_sum; + } + + MPI_Bcast(&res_, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); + + std::atomic counter(0); + std::thread t([&]() { counter++; }); + t.join(); + + MPI_Barrier(MPI_COMM_WORLD); + return true; +} + +bool OvsyannikovNSimpsonMethodALL::PostProcessingImpl() { + GetOutput() = res_; + return true; +} + +} // namespace ovsyannikov_n_simpson_method diff --git a/tasks/ovsyannikov_n_simpson_method/tests/functional/main.cpp b/tasks/ovsyannikov_n_simpson_method/tests/functional/main.cpp index d741b19a9..a4d612850 100644 --- a/tasks/ovsyannikov_n_simpson_method/tests/functional/main.cpp +++ b/tasks/ovsyannikov_n_simpson_method/tests/functional/main.cpp @@ -6,6 +6,7 @@ #include #include +#include "ovsyannikov_n_simpson_method/all/include/ops_all.hpp" #include "ovsyannikov_n_simpson_method/common/include/common.hpp" #include "ovsyannikov_n_simpson_method/omp/include/ops_omp.hpp" #include "ovsyannikov_n_simpson_method/seq/include/ops_seq.hpp" @@ -77,5 +78,10 @@ const auto kTestTasksSTL = INSTANTIATE_TEST_SUITE_P(SimpsonTest_STL, OvsyannikovNRunFuncTestsThreads, ppc::util::ExpandToValues(kTestTasksSTL), OvsyannikovNRunFuncTestsThreads::PrintFuncTestName); +const auto kTestTasksALL = + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_ovsyannikov_n_simpson_method); +INSTANTIATE_TEST_SUITE_P(SimpsonTest_ALL, OvsyannikovNRunFuncTestsThreads, ppc::util::ExpandToValues(kTestTasksALL), + OvsyannikovNRunFuncTestsThreads::PrintFuncTestName); + } // namespace } // namespace ovsyannikov_n_simpson_method diff --git a/tasks/ovsyannikov_n_simpson_method/tests/performance/main.cpp b/tasks/ovsyannikov_n_simpson_method/tests/performance/main.cpp index 8e6f6377d..00540be03 100644 --- a/tasks/ovsyannikov_n_simpson_method/tests/performance/main.cpp +++ b/tasks/ovsyannikov_n_simpson_method/tests/performance/main.cpp @@ -2,6 +2,7 @@ #include +#include "ovsyannikov_n_simpson_method/all/include/ops_all.hpp" #include "ovsyannikov_n_simpson_method/common/include/common.hpp" #include "ovsyannikov_n_simpson_method/omp/include/ops_omp.hpp" #include "ovsyannikov_n_simpson_method/seq/include/ops_seq.hpp" @@ -46,6 +47,9 @@ const auto kPerfTasksTBB = const auto kPerfTasksSTL = ppc::util::MakeAllPerfTasks(PPC_SETTINGS_ovsyannikov_n_simpson_method); +const auto kPerfTasksALL = + ppc::util::MakeAllPerfTasks(PPC_SETTINGS_ovsyannikov_n_simpson_method); + const auto kPerfTestName = OvsyannikovNRunPerfTestThreads::CustomPerfTestName; INSTANTIATE_TEST_SUITE_P(SimpsonPerf_SEQ, OvsyannikovNRunPerfTestThreads, ppc::util::TupleToGTestValues(kPerfTasksSEQ), @@ -59,5 +63,8 @@ INSTANTIATE_TEST_SUITE_P(SimpsonPerf_TBB, OvsyannikovNRunPerfTestThreads, ppc::u INSTANTIATE_TEST_SUITE_P(SimpsonPerf_STL, OvsyannikovNRunPerfTestThreads, ppc::util::TupleToGTestValues(kPerfTasksSTL), kPerfTestName); + +INSTANTIATE_TEST_SUITE_P(SimpsonPerf_ALL, OvsyannikovNRunPerfTestThreads, ppc::util::TupleToGTestValues(kPerfTasksALL), + kPerfTestName); } // namespace } // namespace ovsyannikov_n_simpson_method