Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <functional>
#include <string>
#include <tuple>
#include <vector>

#include "task/include/task.hpp"

namespace dorofeev_i_monte_carlo_integration_processes {

struct InputData {
std::function<double(const std::vector<double> &)> func; // f(x)
std::vector<double> a; // lower bounds
std::vector<double> b; // upper bounds
int samples = 0; // number of samples
};

using InType = InputData;
using OutType = double;

using TestType = std::tuple<int, std::string>; // for testing purposes
using BaseTask = ppc::task::Task<InType, OutType>;

} // namespace dorofeev_i_monte_carlo_integration_processes
9 changes: 9 additions & 0 deletions tasks/dorofeev_i_monte_carlo_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ФИ1",
"task_number": "1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

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

namespace dorofeev_i_monte_carlo_integration_processes {

class DorofeevIMonteCarloIntegrationMPI : public BaseTask {
public:
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
return ppc::task::TypeOfTask::kMPI;
}
explicit DorofeevIMonteCarloIntegrationMPI(const InType &in);

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

} // namespace dorofeev_i_monte_carlo_integration_processes
138 changes: 138 additions & 0 deletions tasks/dorofeev_i_monte_carlo_integration/mpi/src/ops_mpi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#include "dorofeev_i_monte_carlo_integration/mpi/include/ops_mpi.hpp"

#include <mpi.h>

#include <algorithm>
#include <cstddef>
#include <random>
#include <ranges>
#include <utility>
#include <vector>

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

namespace dorofeev_i_monte_carlo_integration_processes {

DorofeevIMonteCarloIntegrationMPI::DorofeevIMonteCarloIntegrationMPI(const InType &in) {
SetTypeOfTask(GetStaticTypeOfTask());
GetInput() = in;
GetOutput() = 0;
}

bool DorofeevIMonteCarloIntegrationMPI::ValidationImpl() {
int rank = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);

bool valid = true;

if (rank == 0) {
const auto &in = GetInput();
valid =
in.func && !in.a.empty() && in.a.size() == in.b.size() &&
std::ranges::all_of(std::views::iota(size_t{0}, in.a.size()), [&](size_t i) { return in.b[i] > in.a[i]; }) &&
in.samples > 0;
}

MPI_Bcast(&valid, 1, MPI_C_BOOL, 0, MPI_COMM_WORLD);
return valid;
}

Comment thread
4elodoy-Molovek marked this conversation as resolved.
bool DorofeevIMonteCarloIntegrationMPI::PreProcessingImpl() {
GetOutput() = 0.0;
return true;
}

bool DorofeevIMonteCarloIntegrationMPI::RunImpl() {
int rank = 0;
int size = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);

// BROADCAST INPUT
InType in;

if (rank == 0) {
in = GetInput();
}

// BROADCAST DIMS
int dims = static_cast<int>(rank == 0 ? in.a.size() : 0);
MPI_Bcast(&dims, 1, MPI_INT, 0, MPI_COMM_WORLD);

// resize on other ranks
if (rank != 0) {
in.a.resize(dims);
in.b.resize(dims);
}

// BROADCAST BOUNDS
MPI_Bcast(in.a.data(), dims, MPI_DOUBLE, 0, MPI_COMM_WORLD);
MPI_Bcast(in.b.data(), dims, MPI_DOUBLE, 0, MPI_COMM_WORLD);

// BROADCAST SAMPLES
MPI_Bcast(&in.samples, 1, MPI_INT, 0, MPI_COMM_WORLD);

// BROADCAST FUNC ID
int func_id = (rank == 0 ? 1 : 0);
MPI_Bcast(&func_id, 1, MPI_INT, 0, MPI_COMM_WORLD);

// restore the function
switch (func_id) {
case 1:
in.func = [](const std::vector<double> &x) { return x[0] * x[0]; };
break;
default:
in.func = nullptr;
}

// CALCULATIONS
int n_total = in.samples;
int n_local = n_total / size;
if (rank == size - 1) {
n_local += n_total % size;
}
Comment thread
4elodoy-Molovek marked this conversation as resolved.

std::vector<std::uniform_real_distribution<double>> dist;
dist.reserve(dims);
for (int dim = 0; std::cmp_less(dim, dims); dim++) {
dist.emplace_back(in.a[dim], in.b[dim]);
}

std::mt19937 gen(rank + 777);
double local_sum = 0.0;

std::vector<double> x;
x.assign(static_cast<size_t>(dims), 0.0);

for (int i = 0; i < n_local; ++i) {
for (int dim = 0; std::cmp_less(dim, dims); dim++) {
x[static_cast<size_t>(dim)] = dist[dim](gen);
}
local_sum += in.func(x);
}

// REDUCE
double global_sum = 0.0;
MPI_Reduce(&local_sum, &global_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);

// RESULT
double result = 0.0;
if (rank == 0) {
double volume = 1.0;
for (int dim = 0; std::cmp_less(dim, dims); dim++) {
volume *= (in.b[dim] - in.a[dim]);
}
result = (global_sum / n_total) * volume;
}

MPI_Bcast(&result, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);

GetOutput() = result;
return true;
}

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

} // namespace dorofeev_i_monte_carlo_integration_processes
158 changes: 158 additions & 0 deletions tasks/dorofeev_i_monte_carlo_integration/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Интегрирование – метод Монте-Карло

**Student:** Дорофеев Иван Денисович, group 3823Б1ФИ1
**Technology:** SEQ | MPI
**Variant:** 21

---

## 1. Introduction

Метод Монте-Карло — один из универсальных способов численного интегрирования, позволяющий эффективно оценивать интегралы любой размерности. Однако точность метода и время работы напрямую зависят от количества случайных выборок (samples). Увеличение количества выборок повышает точность, но также увеличивает время выполнения, что делает задачу подходящей для распараллеливания.

Ожидается, что MPI-версия позволит уменьшить время выполнения за счёт распределения выборок между процессами.

---

## 2. Problem statement

Требуется вычислить значение определённого интеграла методом Монте-Карло.

### Входные данные:

- Границы интегрирования:
$a = 0,\quad b = 1$

- Количество выборок: 190000

- Функция:
$f(x) = x^2$

### Выходные данные:

- Одно действительное число — приближённое значение интеграла.

---

## 3. Baseline Algorithm (Sequential)

1. Сгенерировать N равномерных случайных точек из интервала $[a, b]$.

2. Вычислить значение функции в каждой точке.

3. Найти среднее всех вычисленных значений.

4. Умножить среднее на длину интервала $(b-a)$.

Последовательная реализация проста, но требует выполнения всех N вычислений в одном процессе.

---

## 4. Parallelization Scheme (MPI)

Для распараллеливания используется идея независимости выборок:
каждый процесс может самостоятельно выполнить часть выборок и затем отправить свой частичный результат.

### Схема:

1. Корневой процесс рассылает параметры задачи всем участникам.

2. Каждый процесс:

- выполняет часть выборок:
$N_p = \frac{N}{P} $

- рассчитывает среднее значение функции на своём подмножестве.

3. Результаты всех процессов объединяются с помощью **MPI_Reduce**.

4. Корневой процесс вычисляет итоговый интеграл.

Поскольку метод Монте-Карло "естественно" распараллеливается, эффективность MPI в идеальных условиях может быть высокой.

---

## 5. Experimental Setup

- Hardware/OS:
- CPU: 13th Gen Intel i5-13420H (12) @ 4.6GHz, 8 ядер
- RAM: 16GB RAM
- OS: Ubuntu 25.10 x86_64
- Среда выполнения: Docker (Ubuntu trixie/sid (noble) x86_64)
- Toolchain: compiler, version, build type (Release/RelWithDebInfo)
- CMake 3.28.3
- g++ 13.3.0
- OpenMPI
- Сборка: Release
- Data:
- Количество выборок: 190000
- Функция: $f(x)=x^2$
- Тесты производительности запускались для **SEQ**, **MPI: 2**, **MPI: 4** процессов.

---

## 6. Results and Discussion

### 6.1 Correctness

Корректность проверена тестами GoogleTest.
Точное значение интеграла:

$\int_0^1 x^2 dx = \frac{1}{3} \approx 0.333333 $


Все варианты укладываются в допуск ±0.05.

---

## 6.2 Performance

| Mode | Processes | Time (s) | Speedup | Efficiency |
| ------- | --------- | ------------ | -------- | ---------- |
| **seq** | 1 | **0.002277** | **1.00** | — |
| **mpi** | 2 | **0.001751** | **1.30** | **65%** |
| **mpi** | 4 | **0.001137** | **2.00** | **50%** |

---

## 7. Discussion

Результаты экспериментов показывают, что MPI-версия действительно работает быстрее последовательной SEQ-реализации при одинаковом количестве выборок.

Для 2 процессов ускорение составляет **1.30×** при эффективности **65%**.
Для 4 процессов ускорение достигает **2.00×**, что соответствует эффективности **50%**.

Эффективность снижается с ростом числа процессов, это ожидаемое поведение для MPI, так как накладные расходы растут, а объём работы, приходящийся на каждый процесс, уменьшается.

Основные источники потерь производительности:

- необходимость синхронизации между процессами,

- выполнение коллективных операций (например, **MPI_Allreduce**),

- накладные расходы MPI, усиливающиеся при запуске внутри Docker-контейнера,

- относительно малый объём вычислений на один процесс при 4-процессном запуске.

Тем не менее ускорение остаётся значительным — даже с учётом накладных расходов распараллеливание уменьшает общее время расчёта примерно вдвое.

---

## 8. Conclusions

Метод Монте-Карло хорошо поддаётся параллелизации, и MPI-версия показывает ощутимое ускорение.

Хотя эффективность не достигает 100% из-за накладных расходов, ускорение в 2 раза при использовании 4 процессов подтверждает преимущества распараллеливания.

MPI-подход особенно полезен при значительно больших объёмах выборок, где накладные расходы становятся менее заметными.

---

## 9. References

1. "Параллельное программирование для кластерных систем" ИИТММ, ННГУ им. Лобачевского
2. [Open MPI Documentation](https://www.open-mpi.org/doc/)
3. [MPI Reference - Message Passing Interface | Microsoft Learn](https://learn.microsoft.com/en-us/message-passing-interface/mpi-reference)
4. [MPI: A Message-Passing Interface Standard](https://www.mpi-forum.org/docs/mpi-5.0/mpi50-report.pdf)


Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

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

namespace dorofeev_i_monte_carlo_integration_processes {

class DorofeevIMonteCarloIntegrationSEQ : public BaseTask {
public:
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
return ppc::task::TypeOfTask::kSEQ;
}
explicit DorofeevIMonteCarloIntegrationSEQ(const InType &in);

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

} // namespace dorofeev_i_monte_carlo_integration_processes
Loading
Loading