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

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

#include "task/include/task.hpp"

namespace konstantinov_s_broadcast {

using TestType = std::tuple<int, std::string>;

using ETypeInt = int;
using ETypeFloat = float;
using ETypeDouble = double;

using EType = ETypeInt;
using InType = std::vector<EType>;
using OutType = InType;

template <typename T>
using BaseTask = ppc::task::Task<std::vector<T>, std::vector<T>>;

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

#include <vector>

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

namespace konstantinov_s_broadcast {

template <typename T>
class KonstantinovSBroadcastMPI : public BaseTask<T> {
public:
using InType = std::vector<T>;
using OutType = InType;
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
return ppc::task::TypeOfTask::kMPI;
}
explicit KonstantinovSBroadcastMPI(const InType &in);

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

template <typename T>
constexpr MPI_Datatype GetMpiType();

template <>
constexpr MPI_Datatype GetMpiType<int>() {
return MPI_INT;
}

template <>
constexpr MPI_Datatype GetMpiType<float>() {
return MPI_FLOAT;
}

template <>
constexpr MPI_Datatype GetMpiType<double>() {
return MPI_DOUBLE;
}

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

#include <mpi.h>

#include <cstring>
// #include<iostream>
#include <algorithm>

#include "konstantinov_s_broadcast/common/include/common.hpp"
// #include "util/include/util.hpp"

namespace konstantinov_s_broadcast {

template <typename T>
KonstantinovSBroadcastMPI<T>::KonstantinovSBroadcastMPI(const InType &in) {
this->SetTypeOfTask(GetStaticTypeOfTask());
this->GetInput() = in;
this->GetOutput().resize(this->GetInput().size());
}

template <typename T>
bool KonstantinovSBroadcastMPI<T>::ValidationImpl() {
// std::cout << "\t\tValidation mpi\n";
return !this->GetInput().empty();
}

template <typename T>
bool KonstantinovSBroadcastMPI<T>::PreProcessingImpl() {
return true;
}

template <typename T>
bool KonstantinovSBroadcastMPI<T>::RunImpl() {
int prank = 0;
int pcount = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &prank);
MPI_Comm_size(MPI_COMM_WORLD, &pcount);

const int tag_count = 1;
const int tag_data = 2;

MPI_Datatype mpi_type = GetMpiType<T>();

T *odata = nullptr;

int root_prank = pcount - 1;
int k = std::min(2, pcount); // k-арное дерево

// ранг процесса в дереве
int tree_rank = (prank - root_prank + pcount) % pcount;

// родитель в дереве
int parent_tree_rank = (tree_rank == 0) ? -1 : (tree_rank - 1) / k;
int parent_prank = (parent_tree_rank < 0) ? -1 : (parent_tree_rank + root_prank) % pcount;

int recv_count = 0;
// приём данных от родителя (корень читает доступные ему данные)
if (tree_rank == 0) {
auto &ibuffer = this->GetInput();
int elem_count = static_cast<int>(ibuffer.size());
odata = ibuffer.data();
recv_count = elem_count;
this->GetOutput() = ibuffer;
} else {
// std::cout << "Rank " << prank << " (t " << tree_rank << ") recv from "<< parent_prank << " (t " <<
// parent_tree_rank << ")\n";
MPI_Recv(&recv_count, 1, MPI_INT, parent_prank, tag_count, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
odata = new T[recv_count];
MPI_Recv(odata, recv_count, mpi_type, parent_prank, tag_data, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
memcpy(this->GetOutput().data(), odata, recv_count * sizeof(T));
}

// вычисляем потомков и рассылаем
// std::vector<int> child_pranks;
for (int i = 1; i <= k; ++i) {
int child_tree_rank = (k * tree_rank) + i;
if (child_tree_rank < pcount) {
int child_prank = (child_tree_rank + root_prank) % pcount;
// std::cout << "Rank " << prank << " (t " << tree_rank << ") send to " << ch << "\n";
MPI_Send(&recv_count, 1, MPI_INT, child_prank, tag_count, MPI_COMM_WORLD);
MPI_Send(odata, recv_count, mpi_type, child_prank, tag_data, MPI_COMM_WORLD);
// child_pranks.push_back(child_prank);
}
}
if (tree_rank != 0) {
delete[] odata;
}

// this->GetOutput() = ibuffer;
return true;
}

template <typename T>
bool KonstantinovSBroadcastMPI<T>::PostProcessingImpl() {
return true;
}
template class KonstantinovSBroadcastMPI<ETypeInt>;
template class KonstantinovSBroadcastMPI<ETypeFloat>;
template class KonstantinovSBroadcastMPI<ETypeDouble>;

} // namespace konstantinov_s_broadcast
57 changes: 57 additions & 0 deletions tasks/konstantinov_s_broadcast/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Передача от одного всем (broadcast)

- Student: Константинов Семён Анатольевич, group 3823Б1ФИ3
- Technology: SEQ | MPI
- Variant: 1

## 1. Introduction
Эффективная рассылка данных от одного процесса всем остальным - важная часть в работе MPI, реализуемая функцией MPI_Bcast. Алгоритм пересылки данных можно повторить, взяв за основу идею k-арного дерева и используя только базовые операции `MPI_Recv` и `MPI_Send`.
## 2. Problem Statement
### Входные данные:
- Ранг процесса, который будет корневым в дереве
- Массив входных данных
### Выходные данные:
- Одинаковые массивы на всех процессах, совпадающие с входным массивом
## 3. Baseline Algorithm (Sequential)
Последовательный алгоритм не имеет смысла в контексте алгоритмов обмена данными между процессами. Реализовано копирование входного массива в выходной.

## 4. Parallelization Scheme
Алгоритм основан на k-арном дереве: каждый процесс из своего ранга процесса `prank`, ранга корня дерева `root_prank` и количества процессов `pcount` получает свой ранг в дереве `tree_rank = (prank - root_prank + pcount) % pcount`. Из тех же данных вычисляются ранги в дереве и ранги процесса у родителя процесса и каждого из от 0 до k детей процесса.
```
parent_tree_rank = (tree_rank == 0) ? -1 : (tree_rank - 1) / k;
parent_prank = (parent_tree_rank < 0) ? -1 : (parent_tree_rank + root_prank) % pcount;
child_tree_rank = (k * tree_rank) + i<k;
child_prank = (child_tree_rank + root_prank) % pcount
```
Сначала процессы ожидают данные от своего родителя (сначала данные о размере массива с помощью MPI_Recv с тегом tag_count, затем сам массив с тегом tag_count). Уже имеющие данные (сначала только корень) отсылают их своим детям через MPI_Send с аналогичными тегами.
Таким образом данные достигают всех процессов за log от числа процессов операций.

## 5. Experimental Setup
- Hardware/OS: ЦП AMD Ryzen 5 3600 6-Core Processor, 16 GB RAM, Windows 10 x64
- Toolchain: Cmake 4.2.0, Visual Studio 2022, MSVC, x64 Release
- Environment: PPC_NUM_THREADS=PPC_NUM_PROC=1/2/4/6/8/10/12/14/16/20/24/32/40/60/80, PPC_PERF_MAX_TIME=10000
- Data: стабильная генерация тестовых данных разного размера, различный выбор корня дерева

## 6. Results and Discussion

### 6.1 Correctness
Корректность проверяется сравнением входного и выходного массивов.
Корректность проверена на векторах разных размеров.

### 6.2 Performance
Измерение производительности не имеет смысла, так как количество работы зависит от числа процессов, но оно было проведено.
За эталон производительности взято время выполнения mpi версии на одном процессе.

| Count | Time, s | Speedup | Efficiency |
| ----- | ------- | ------- | ---------- |
| 1 | 0.0260 | 1.00 | N/A |
| 2 | 0.0263 | 0.98 | 49.4% |
| 4 | 0.0422 | 0.61 | 15.4% |
| 8 | 0.0894 | 0.29 | 0.036% |
| 12 | 0.1256 | 0.207 | 0.017% |
| 24 | 0.2550 | 0.102 | 0.004% |

## 7. Conclusions
Была разработана реализация алгоритма передачи данных от одного процесса на все. Реализация шаблонная, позволяет пересылать любые тривиально копируемые типы данных, алгоритм построения дерева позволяет выбрать любой процесс в качестве корня.
## 8. References
1. Курс лекций ННГУ "Параллельное программирование для кластерных систем"
25 changes: 25 additions & 0 deletions tasks/konstantinov_s_broadcast/seq/include/ops_seq.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once
#include <vector>

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

namespace konstantinov_s_broadcast {
template <typename T>
class KonstantinovSBroadcastSEQ : public BaseTask<T> {
public:
using InType = std::vector<T>;
using OutType = InType;
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
return ppc::task::TypeOfTask::kSEQ;
}
explicit KonstantinovSBroadcastSEQ(const InType &in);

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

} // namespace konstantinov_s_broadcast
40 changes: 40 additions & 0 deletions tasks/konstantinov_s_broadcast/seq/src/ops_seq.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include "konstantinov_s_broadcast/seq/include/ops_seq.hpp"

// #include <iostream>
// #include <numeric>
#include <cstring> // memcpy

#include "konstantinov_s_broadcast/common/include/common.hpp"
// #include "util/include/util.hpp"

namespace konstantinov_s_broadcast {
template <typename T>
KonstantinovSBroadcastSEQ<T>::KonstantinovSBroadcastSEQ(const InType &in) {
this->SetTypeOfTask(GetStaticTypeOfTask());
this->GetInput() = in;
this->GetOutput().resize(this->GetInput().size());
}
template <typename T>
bool KonstantinovSBroadcastSEQ<T>::ValidationImpl() {
// std::cout << "\t\tValidation seq\n";
return !this->GetInput().empty();
}
template <typename T>
bool KonstantinovSBroadcastSEQ<T>::PreProcessingImpl() {
return true;
}
template <typename T>
bool KonstantinovSBroadcastSEQ<T>::RunImpl() {
memcpy(this->GetOutput().data(), this->GetInput().data(), this->GetInput().size() * sizeof(T));
return true;
}
template <typename T>
bool KonstantinovSBroadcastSEQ<T>::PostProcessingImpl() {
return true;
}

template class KonstantinovSBroadcastSEQ<ETypeInt>;
template class KonstantinovSBroadcastSEQ<ETypeFloat>;
template class KonstantinovSBroadcastSEQ<ETypeDouble>;

} // namespace konstantinov_s_broadcast
7 changes: 7 additions & 0 deletions tasks/konstantinov_s_broadcast/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"tasks_type": "processes",
"tasks": {
"mpi": "enabled",
"seq": "enabled"
}
}
13 changes: 13 additions & 0 deletions tasks/konstantinov_s_broadcast/tests/.clang-tidy
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading