Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d1931e1
feat: Add initial sequential implementation for trapezoid method
VALancaster Oct 26, 2025
e1a00c6
feat: Add MPI implementation and refactor file structure
VALancaster Nov 1, 2025
4c64275
So... let's try CI pipeline
VALancaster Nov 5, 2025
34ee0a4
style: Apply clang-format
VALancaster Nov 5, 2025
0d947a7
fix: Make test 'n' divisible by process count
VALancaster Nov 6, 2025
c6f9513
style: Apply some clang-format fixes
VALancaster Nov 6, 2025
62ea508
style: Apply some clang-format fixes 2.0
VALancaster Nov 6, 2025
f94b474
fix: Guard all MPI calls with IsUnderMpirun check
VALancaster Nov 6, 2025
3271837
refactor: Implement robust handling of non-divisible 'n' in MPI
VALancaster Nov 6, 2025
e29f81b
fix: Address clang-tidy warnings
VALancaster Nov 9, 2025
cca8cca
fix: Address clang-tidy warnings 2.0
VALancaster Nov 9, 2025
71dc973
fix: Address clang-tidy warnings 3.0
VALancaster Nov 9, 2025
658f1d5
fix: Address clang-tidy warnings 4.0
VALancaster Nov 9, 2025
ece5816
feat: Finalize Task 1 with full functional test coverage
VALancaster Nov 12, 2025
0b4fb74
feat: Add performance tests and apply format/tidy fixes
VALancaster Nov 12, 2025
9d5547a
perf: Increase problem size to meet CI time threshold
VALancaster Nov 13, 2025
ff29fc3
docs: Add report.md
VALancaster Nov 13, 2025
fe86e42
test: Add final tests to achieve >95% code coverage
VALancaster Nov 13, 2025
40f977e
fix: Address all review comments and finalize tests
VALancaster Nov 22, 2025
da85fd1
refactor: Move MPI communication from PreProcessing to RunImpl
VALancaster Nov 30, 2025
40c25ac
fix: Revert changes to common files per review
VALancaster Dec 4, 2025
c667f9c
chore: Revert devcontainer.json to upstream version
VALancaster Dec 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include <string>
#include <tuple>
Comment thread
allnes marked this conversation as resolved.

#include "task/include/task.hpp"

namespace kutergin_v_trapezoid_seq {

struct InputData {
double a;
double b;
int n;
};

using InType = InputData;
using OutType = double;
using BaseTask = ppc::task::Task<InType, OutType>;
using TestType = std::tuple<InputData, double, std::string>; // Тип для тестовых параметров {входные данные, ожидаемый
// результат, имя_теста}

} // namespace kutergin_v_trapezoid_seq
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.0 10.0 3000000
9 changes: 9 additions & 0 deletions tasks/kutergin_v_trapezoid_method_of_integration/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"student": {
"first_name": "Валентин",
"last_name": "Кутергин",
"middle_name": "Тимофеевич",
"group_number": "3823Б1ФИ3 ",
"task_number": "1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include "../../common/include/common.hpp"
#include "task/include/task.hpp"

namespace kutergin_v_trapezoid_mpi {

double Func(double x);

class TrapezoidIntegrationMPI
: public kutergin_v_trapezoid_seq::BaseTask // наследник BaseTask (псевдоним для ppc::task::Task<InType, OutType>)
{
public:
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
return ppc::task::TypeOfTask::kMPI;
}
explicit TrapezoidIntegrationMPI(
const kutergin_v_trapezoid_seq::InType &in); // конструктор принимает InType (псевдоним для структуры InputData)

protected:
bool ValidationImpl() override;
bool PreProcessingImpl() override;
bool RunImpl() override;
bool PostProcessingImpl() override;
};

} // namespace kutergin_v_trapezoid_mpi
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#include "../include/trapezoid_integration_mpi.hpp"

#include <mpi.h>

#include <cmath>

#include "../../common/include/common.hpp"

namespace kutergin_v_trapezoid_mpi {

double Func(double x) // интегрируемая функция для примера
{
return x * x;
}

TrapezoidIntegrationMPI::TrapezoidIntegrationMPI(const kutergin_v_trapezoid_seq::InType &in) {
SetTypeOfTask(GetStaticTypeOfTask()); // установка типа задачи
GetInput() = in; // сохранение входных данных
GetOutput() = 0.0; // инициализация выходных данных
Comment thread
allnes marked this conversation as resolved.
}

bool TrapezoidIntegrationMPI::ValidationImpl() {
return (GetInput().a <= GetInput().b) && (GetInput().n > 0);
}

Comment thread
allnes marked this conversation as resolved.
bool TrapezoidIntegrationMPI::PreProcessingImpl() {
Comment thread
allnes marked this conversation as resolved.
return true;
}

bool TrapezoidIntegrationMPI::RunImpl() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sending the data to different ranks from rank 0 is missing

int process_rank = 0;
int process_count = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &process_rank);
MPI_Comm_size(MPI_COMM_WORLD, &process_count);

kutergin_v_trapezoid_seq::InType broadcast_data;
if (process_rank == 0) {
broadcast_data = GetInput();
}

// Каждое поле рассылается отдельно
MPI_Bcast(&broadcast_data.a, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
MPI_Bcast(&broadcast_data.b, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
MPI_Bcast(&broadcast_data.n, 1, MPI_INT, 0, MPI_COMM_WORLD);

double a = broadcast_data.a;
double b = broadcast_data.b;
int n = broadcast_data.n;
double h = (b - a) / n;

const int base_n = n / process_count; // целое часть от деления числа разбиений на число процессов
const int remainder = n % process_count; // остаток от деления числа разбиений на число процессов

const int local_n = base_n + (process_rank < remainder ? 1 : 0); // количество разбиений (трапеций) на один процесс

int start_index = 0;
if (process_rank < remainder) {
start_index = process_rank * (base_n + 1);
} else {
start_index = (remainder * (base_n + 1)) + ((process_rank - remainder) * base_n);
}

double local_a = a + (start_index * h); // начало отрезка для текущего процесса

// локальные вычисления
double local_sum = 0.0;
if (local_n > 0) {
local_sum = (Func(local_a) + Func(local_a + (local_n * h))) / 2.0;
}

for (int i = 1; i < local_n; ++i) {
local_sum += Func(local_a + (i * h));
}

// агрегация
double global_sum = 0.0;
MPI_Reduce(&local_sum, &global_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);

if (process_rank == 0) {
GetOutput() = global_sum * h;
}

return true;
}

bool TrapezoidIntegrationMPI::PostProcessingImpl() {
return true;
}

} // namespace kutergin_v_trapezoid_mpi
125 changes: 125 additions & 0 deletions tasks/kutergin_v_trapezoid_method_of_integration/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Отчет по лабораторной работе №1
## "Интегрирование методом трапеций"

**Студент:** Кутергин Валентин
**Группа:** 3823Б1ФИ3
**Вариант:** 20

### 1. Введение
**Мотивация:** Численное интегрирование — фундаментальная задача во многих научных и инженерных областях. Цель данной работы — реализовать параллельную версию алгоритма интегрирования методом трапеций с использованием технологии MPI и исследовать эффективность распараллеливания по сравнению с последовательным вариантом.

**Проблема:** Метод трапеций, как и многие численные методы, хорошо поддается декомпозиции. Однако, каждая отдельная итерация (вычисление площади одной трапеции) имеет очень низкую вычислительную сложность. Это создает риск, что накладные расходы на организацию параллельного взаимодействия (коммуникации) могут превысить выгоду от параллельных вычислений.

**Ожидаемый результат:** Ожидается, что MPI-версия покажет ускорение только на задачах с очень большим количеством итераций (`n`), где время вычислений станет сопоставимым с затратами на коммуникацию. Для задач с низкой или средней вычислительной нагрузкой параллельная версия, скорее всего, будет уступать последовательной.

### 2. Постановка задачи
**Задано:** Функция `f(x)`, отрезок интегрирования `[a, b]` и количество шагов разбиения `n`.

**Требуется:** Численно вычислить определенный интеграл функции `f(x)` на заданном отрезке.

**Входные данные:** `double a`, `double b`, `int n`.
**Выходные данные:** `double result` — приближенное значение интеграла.

### 3. Базовый алгоритм (Последовательный)
Последовательный алгоритм реализует формулу метода трапеций:
`Интеграл ≈ h * [ (f(a) + f(b))/2 + f(x_1) + f(x_2) + ... + f(x_{n-1}) ]`, где `h = (b-a)/n`.

Алгоритм состоит из одного цикла, который итеративно вычисляет и суммирует значения функции в точках разбиения. Сложность алгоритма — `O(n)`.

### 4. Схема распараллеливания
**Декомпозиция данных:**
Основная вычислительная нагрузка — это суммирование `n` значений функции. Эта сумма была разделена между `P` MPI-процессами. Используется стратегия **блочного распределения**: общее число итераций `n` делится на `P`.

**Обработка остатка:**
Чтобы алгоритм работал для любых `n` и `P`, реализована корректная обработка остатка от деления. Первые `n % P` процессов получают на одну итерацию больше, чем остальные. Это обеспечивает максимально равномерное распределение нагрузки.

**Вычисления на процессе:**
Каждый процесс независимо вычисляет свою **локальную сумму** (`local_sum`) на своем уникальном подотрезке.

**Коммуникация:**
* В начале работы (этап `PreProcessing`) корневой процесс (`rank 0`) рассылает всем остальным исходные данные (`a`, `b`, `n`) с помощью коллективной операции **`MPI_Bcast`**.
* После завершения локальных вычислений все процессы участвуют в коллективной операции **`MPI_Reduce`** с операцией `MPI_SUM`. Все `local_sum` суммируются, и итоговый результат (`global_sum`) становится доступен только на корневом процессе.

### 5. Экспериментальная установка
* **Hardware/OS:** AMD Ryzen 5 7500F, 6 ядер, 32GB RAM, Windows 10
* **Toolchain & Environment:**
* Работа производилась внутри **Dev Container** (Ubuntu)
* Компилятор: **GCC 14.2.0**
* MPI-библиотека: **Open MPI 3.1**
* Тип сборки: **Release**
* **Data:** Для замеров производительности использовалась функция `f(x) = x*x` на отрезке `[0.0, 10.0]` с `n = 25,200,000`.

### 6. Результаты и обсуждение
#### 6.1 Корректность
Корректность обеих реализаций (`seq` и `mpi`) проверена набором функциональных тестов, включая:
* Тесты с фиксированными эталонными значениями.
* Тесты со случайно сгенерированными границами интегрирования и числом шагов `n`.
Все тесты успешно пройдены, что подтверждает корректность вычислений.

#### 6.2 Производительность
Замеры производительности проводились на системе, указанной в п.5. В таблице представлено время выполнения только вычислительной части (`RunImpl`).

| Mode | Count | Time, s | Speedup | Efficiency |
| :--- | :--- | :--- | :--- | :--- |
| seq | 1 | 0.0000289 | 1.00 | N/A |
| mpi | 4 | 0.0002408 | **0.12** | **3.0%** |

* **Speedup** = `Time(seq) / Time(mpi)`
* **Efficiency** = `Speedup / Count`

Как видно из результатов, параллельная реализация на 4-х процессах оказалась значительно (приблизительно в 8.3 раза) **медленнее** последовательной. Ускорение составило всего 0.12, что говорит о сильной деградации производительности.

### 7. Выводы
Реализация параллельного алгоритма интегрирования методом трапеций с использованием MPI продемонстрировала свою **неэффективность для данной задачи**.

Основная причина — **крайне низкая вычислительная интенсивность** операции. Время, затрачиваемое на вычисление `x*x`, ничтожно мало по сравнению с **накладными расходами на MPI-коммуникации**. Операции `MPI_Bcast` и `MPI_Reduce` требуют упаковки данных, пересылки и синхронизации, что в данном случае "съедает" весь потенциальный выигрыш от параллельных вычислений и приводит к существенному замедлению.

ДЭффективность MPI достигается только тогда, когда время, затраченное на локальные вычисления, **значительно превышает** время на коммуникацию между процессами.

### 8. Приложение: Исходный код MPI-реализации (`RunImpl`)
```cpp
bool TrapezoidIntegrationMPI::RunImpl() {
int process_rank = 0;
int process_count = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &process_rank);
MPI_Comm_size(MPI_COMM_WORLD, &process_count);

double a = GetInput().a;
double b = GetInput().b;
int n = GetInput().n;
double h = (b - a) / n;

const int base_n = n / process_count; // целое часть от деления числа разбиений на число процессов
const int remainder = n % process_count; // остаток от деления числа разбиений на число процессов

const int local_n = base_n + (process_rank < remainder ? 1 : 0); // количество разбиений (трапеций) на один процесс

int start_index = 0;
if (process_rank < remainder) {
start_index = process_rank * (base_n + 1);
} else {
start_index = (remainder * (base_n + 1)) + ((process_rank - remainder) * base_n);
}

double local_a = a + (start_index * h); // начало отрезка для текущего процесса

// локальные вычисления
double local_sum = 0.0;
if (local_n > 0) {
local_sum = (Func(local_a) + Func(local_a + (local_n * h))) / 2.0;
}

for (int i = 1; i < local_n; ++i) {
local_sum += Func(local_a + (i * h));
}

// агрегация
double global_sum = 0.0;
MPI_Reduce(&local_sum, &global_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);

if (process_rank == 0) {
GetOutput() = global_sum * h;
}

return true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include "../../common/include/common.hpp"
#include "task/include/task.hpp"

namespace kutergin_v_trapezoid_seq {

double Func(double x);

class TrapezoidIntegrationSequential
: public BaseTask // наследник BaseTask (псевдоним для ppc::task::Task<InType, OutType>)
{
public:
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
return ppc::task::TypeOfTask::kSEQ;
}
explicit TrapezoidIntegrationSequential(
const InType &in); // конструктор принимает InType (псевдоним для структуры InputData)

protected:
bool ValidationImpl() override;
bool PreProcessingImpl() override;
bool RunImpl() override;
bool PostProcessingImpl() override;
};

} // namespace kutergin_v_trapezoid_seq
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "../include/trapezoid_integration_sequential.hpp"

#include "../../common/include/common.hpp"

namespace kutergin_v_trapezoid_seq {

double Func(double x) // интегрируемая функция для примера
{
return x * x;
}

TrapezoidIntegrationSequential::TrapezoidIntegrationSequential(const InType &in) {
SetTypeOfTask(GetStaticTypeOfTask()); // установка типа задачи
GetInput() = in; // сохранение входных данных
GetOutput() = 0.0; // инициализация выходных данных
}

bool TrapezoidIntegrationSequential::ValidationImpl() {
return (GetInput().b >= GetInput().a) &&
(GetInput().n > 0); // проверка b >= a (границ интегрирования) и n > 0 (число разбиений)
}

bool TrapezoidIntegrationSequential::PreProcessingImpl() {
return true;
}

bool TrapezoidIntegrationSequential::RunImpl() {
double a = GetInput().a;
double b = GetInput().b;
int n = GetInput().n;

double h = (b - a) / n;
double integral_res = (Func(a) + Func(b)) / 2.0;

for (int i = 1; i < n; ++i) {
integral_res += Func(a + (i * h));
}

GetOutput() = integral_res * h;

return true;
}

bool TrapezoidIntegrationSequential::PostProcessingImpl() {
return true;
}

} // namespace kutergin_v_trapezoid_seq
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"tasks_type": "processes",
"tasks":
{
"seq": "enabled",
"mpi": "enabled"
}
}
Loading
Loading