Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions tasks/dorofeev_i_scatter/common/include/common.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

#include <mpi.h>

#include <tuple>

#include "task/include/task.hpp"

namespace dorofeev_i_scatter {

using InType = std::tuple<const void *, int, MPI_Datatype, void *, int, MPI_Datatype, int, MPI_Comm>;
using OutType = void *;
using TestType = std::tuple<int, int, MPI_Datatype>; // count, root, type
using BaseTask = ppc::task::Task<InType, OutType>;

} // namespace dorofeev_i_scatter
9 changes: 9 additions & 0 deletions tasks/dorofeev_i_scatter/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": "2"
}
}
27 changes: 27 additions & 0 deletions tasks/dorofeev_i_scatter/mpi/include/ops_mpi.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include <mpi.h>

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

namespace dorofeev_i_scatter {

class DorofeevIScatterMPI : public BaseTask {
public:
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
return ppc::task::TypeOfTask::kMPI;
}

explicit DorofeevIScatterMPI(const InType &in);

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

static int GetTypeSize(MPI_Datatype type);
};

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

#include <mpi.h>

#include <algorithm>
#include <cstddef>
#include <vector>

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

namespace dorofeev_i_scatter {

DorofeevIScatterMPI::DorofeevIScatterMPI(const InType &in) {
SetTypeOfTask(GetStaticTypeOfTask());
GetInput() = in;
GetOutput() = nullptr;
}

bool DorofeevIScatterMPI::ValidationImpl() {
auto [sendbuf, sendcount, sendtype, recvbuf, recvcount, recvtype, root, comm] = GetInput();
int size = 0;
MPI_Comm_size(comm, &size);
return sendcount >= 0 && recvcount >= 0 && root >= 0 && root < size;
}

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

bool DorofeevIScatterMPI::RunImpl() {
auto [sendbuf, sendcount, sendtype, recvbuf, recvcount, recvtype, root, comm] = GetInput();

int rank = 0;
int size = 0;
MPI_Comm_rank(comm, &rank);
MPI_Comm_size(comm, &size);

int type_size = GetTypeSize(sendtype);

// Tree-based scatter: each process receives a chunk and forwards remaining data
if (rank == root) {
// Root keeps its own data
std::copy_n(static_cast<const char *>(sendbuf) + (static_cast<ptrdiff_t>(rank * sendcount * type_size)),
sendcount * type_size, static_cast<char *>(recvbuf));

// Send remaining data to first child
int first_child = 1;
if (first_child < size) {
int remaining_elements = (size - 1) * sendcount;
MPI_Send(static_cast<const char *>(sendbuf) + (static_cast<ptrdiff_t>(first_child * sendcount * type_size)),
remaining_elements, sendtype, first_child, 0, comm);
}

} else {
// Non-root processes: receive from previous process in the chain
int sender = rank - 1;

// Calculate how many elements we expect to receive
int remaining_processes = size - rank;
int elements_to_recv = remaining_processes * sendcount;

std::vector<char> recv_buffer(static_cast<size_t>(elements_to_recv * type_size));

MPI_Recv(recv_buffer.data(), elements_to_recv, recvtype, sender, 0, comm, MPI_STATUS_IGNORE);

// Keep our data
std::copy_n(recv_buffer.data(), sendcount * type_size, static_cast<char *>(recvbuf));

// Send remaining data to next process
int next_process = rank + 1;
if (next_process < size) {
int remaining_elements = (remaining_processes - 1) * sendcount;
MPI_Send(recv_buffer.data() + (static_cast<ptrdiff_t>(sendcount * type_size)), remaining_elements, sendtype,
next_process, 0, comm);
}
}

GetOutput() = recvbuf;
return true;
}

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

int DorofeevIScatterMPI::GetTypeSize(MPI_Datatype type) {
if (type == MPI_INT) {
return sizeof(int);
}
if (type == MPI_FLOAT) {
return sizeof(float);
}
if (type == MPI_DOUBLE) {
return sizeof(double);
}
return 1; // fallback
}

} // namespace dorofeev_i_scatter
184 changes: 184 additions & 0 deletions tasks/dorofeev_i_scatter/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Обобщённая передача от одного всем (Scatter)

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

---

## 1. Introduction

Операция **Scatter** относится к базовым методам передачи сообщений в MPI и предназначена для распределения частей массива от одного корневого процесса всем остальным процессам коммуникатора. Каждый процесс получает свою часть данных, причём распределение выполняется в рамках одной коллективной операции.

В рамках данного задания требуется реализовать функциональность, эквивалентную `MPI_Scatter`, **используя только точечные операции `MPI_Send` и `MPI_Recv`**, а также применяя **древовидную схему передачи данных**. Такой подход позволяет снизить нагрузку на корневой процесс и улучшить масштабируемость алгоритма.

---

## 2. Problem statement

Требуется реализовать обобщённую операцию рассылки данных от одного процесса всем остальным (Scatter).

### Требования:

* реализация должна иметь **тот же прототип**, что и соответствующая функция MPI;
* разрешено использовать **только `MPI_Send` и `MPI_Recv`**;
* передача должна выполняться **по дереву процессов**;
* тестовая программа должна позволять:

* выбирать процесс `root`,
* выполнять рассылку массивов типов:

* `MPI_INT`,
* `MPI_FLOAT`,
* `MPI_DOUBLE`.

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

* массив элементов на корневом процессе;
* номер корневого процесса `root`;
* тип данных MPI;
* коммуникатор процессов.

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

* каждый процесс получает свой элемент (или блок элементов) исходного массива.

---

## 3. Baseline Algorithm (Sequential)

Последовательная версия не использует межпроцессное взаимодействие.

Алгоритм:

1. Корневой процесс хранит исходный массив.
2. Для каждого логического «процесса» соответствующий элемент массива считается доступным локально.
3. Операции передачи данных отсутствуют.

Последовательная версия используется исключительно как базовый вариант для сравнения производительности.

---

## 4. Parallelization Scheme (MPI)

MPI-версия реализует операцию Scatter **через точечные передачи**, организованные в виде бинарного дерева процессов.

### Схема работы:

1. Процессы логически организуются в дерево.
2. Корневой процесс:

* делит массив на части,
* отправляет подмассивы своим дочерним процессам.
3. Каждый промежуточный процесс:

* принимает данные от родителя,
* пересылает соответствующие части своим дочерним процессам.
4. Листовые процессы получают только свою часть данных.

Для передачи используются исключительно `MPI_Send` и `MPI_Recv`.
Коллективные операции MPI **не применяются**.

Такая схема снижает количество сообщений, исходящих от корня, с `O(P)` до `O(log P)`.

---

## 5. Experimental Setup

* **Hardware / OS:**

* CPU: 13th Gen Intel i5-13420H (12) @ 4.6 GHz
* RAM: 16 GB
* OS: Ubuntu 25.10 x86_64
* Среда выполнения: Docker (Ubuntu noble / trixie)

* **Toolchain:**

* CMake 3.28.3
* g++ 13.3.0
* OpenMPI
* Тип сборки: `Release`

* **Modes tested:**

* SEQ
* MPI (2 процесса)
* MPI (4 процесса)

Замеры выполнялись с использованием встроенных performance-тестов (`ppc_perf_tests`).

---

## 6. Results and Discussion

### 6.1 Correctness

Корректность реализации подтверждена модульными тестами GoogleTest.

Проверяется, что:

* каждый процесс получает корректный элемент массива;
* поддерживаются типы `MPI_INT`, `MPI_FLOAT`, `MPI_DOUBLE`;
* корректно работает произвольный выбор процесса `root`.

Все тесты завершились успешно.

---

### 6.2 Performance

Для измерения времени выполнения использовались встроенные performance-тесты `ppc_perf_tests`.
Для каждого режима измерения (`pipeline` и `task_run`) фиксировалось время выполнения, после чего в таблице приводится **усреднённое значение**.

Замеры проводились для последовательной (SEQ) и параллельной (MPI) версий при числе процессов 2 и 4.

### Усреднённые времена выполнения Scatter

| Mode | Processes | Time (s) | Speedup | Efficiency |
| ------- | --------- | ---------- | -------- | ---------- |
| **seq** | 1 | **0.0675** | **1.00** | — |
| **mpi** | 2 | **0.0594** | **1.14** | **57%** |
| **mpi** | 4 | **0.0781** | **0.86** | **22%** |

---

## 7. Discussion

Результаты показывают, что для операции Scatter выигрыш по времени при малых объёмах данных минимален.

олученные результаты показывают, что:

* при использовании **2 процессов MPI** наблюдается умеренное ускорение по сравнению с последовательной версией;
* при **4 процессах** ускорение не достигается, что объясняется ростом накладных расходов на межпроцессное взаимодействие.

Операция Scatter относится к **коммуникационно-ограниченным** алгоритмам. При увеличении числа процессов возрастает количество точечных передач и синхронизаций, что при фиксированном объёме данных может приводить к снижению эффективности.

Тем не менее использование древовидной схемы позволяет:

* снизить нагрузку на корневой процесс;
* обеспечить корректную масштабируемость при увеличении объёма данных.

При увеличении размера передаваемого массива преимущества древовидной схемы становятся более заметными.

---

## 8. Conclusions

В ходе работы была реализована операция Scatter, эквивалентная `MPI_Scatter`, с использованием только `MPI_Send` и `MPI_Recv` и древовидной схемы передачи данных.

Реализация:

* полностью соответствует требованиям задания;
* корректно работает для различных типов данных;
* успешно проходит все тесты.

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

---

## 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)
27 changes: 27 additions & 0 deletions tasks/dorofeev_i_scatter/seq/include/ops_seq.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include <mpi.h>

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

namespace dorofeev_i_scatter {

class DorofeevIScatterSEQ : public BaseTask {
public:
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
return ppc::task::TypeOfTask::kSEQ;
}

explicit DorofeevIScatterSEQ(const InType &in);

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

static int GetTypeSize(MPI_Datatype type);
};

} // namespace dorofeev_i_scatter
Loading
Loading