Skip to content

Commit 0dbdf93

Browse files
Кутергин Антон. Технология SEQ-MPI. Нахождение наиболее близких соседних элементов вектора. Вариант 7 (#101)
- **Задача**: Нахождение наиболее близких соседних элементов вектора - **Вариант**: Вариант 7 - **Технология**: SEQ-MPI - **Описание** : На вход подается вектор v, нужно найти такой индекс i, что велечина |v[i] - v[i+1]| минимальна среди всех пар элемента SEQ: Последовательно просматривает пары v[i], v[i+1], сравнивает разности и возвращает индекс максимума MPI: Вектор разбивается на MPI процессы, корректно обрабатывая границы между процессами, чтобы последнему числу этого процесса и первому следующего процесса также образуют пару. Каждый ищет свой локальный минимум среди всех своих соседних пар. Возвращаем глобальный индекс пары с помощью MPI_Reduce - [x] **Статус CI**: Все CI-задачи (сборка, тесты, генерация отчёта) успешно проходят на моей ветке в моем форке - [x] **Директория и именование задачи**: Я создал директорию с именем `<фамилия>_<первая_буква_имени>_<короткое_название_задачи>` - [x] **Полное описание задачи**: Я предоставил полное описание задачи в теле pull request - [x] **clang-format**: Мои изменения успешно проходят `clang-format` локально в моем форке (нет ошибок форматирования) - [x] **clang-tidy**: Мои изменения успешно проходят `clang-tidy` локально в моем форке (нет предупреждений/ошибок) - [x] **Функциональные тесты**: Все функциональные тесты успешно проходят локально на моей машине - [x] **Тесты производительности**: Все тесты производительности успешно проходят локально на моей машине - [x] **Ветка**: Я работаю в ветке, названной точно так же, как директория моей задачи (например, `nesterov_a_vector_sum`), а не в `master` - [x] **Правдивое содержание**: Я подтверждаю, что все сведения, указанные в этом pull request, являются точными и достоверными
1 parent f7bcf4a commit 0dbdf93

11 files changed

Lines changed: 490 additions & 0 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <tuple>
5+
#include <vector>
6+
7+
#include "task/include/task.hpp"
8+
9+
namespace kutergin_a_closest_pair {
10+
11+
using InType = std::vector<int>;
12+
;
13+
using OutType = int;
14+
using TestType = std::tuple<std::tuple<InType, int>, std::string>;
15+
using BaseTask = ppc::task::Task<InType, OutType>;
16+
17+
} // namespace kutergin_a_closest_pair
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"student": {
3+
"first_name": "Антон",
4+
"last_name": "Кутергин",
5+
"middle_name": "Андреевич",
6+
"group_number": "3823Б1ФИ1",
7+
"task_number": "7"
8+
}
9+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
3+
#include "kutergin_a_closest_pair/common/include/common.hpp"
4+
#include "task/include/task.hpp"
5+
6+
namespace kutergin_a_closest_pair {
7+
8+
class KuterginAClosestPairMPI : public BaseTask {
9+
public:
10+
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
11+
return ppc::task::TypeOfTask::kMPI;
12+
}
13+
explicit KuterginAClosestPairMPI(const InType &in);
14+
15+
private:
16+
bool ValidationImpl() override;
17+
bool PreProcessingImpl() override;
18+
bool RunImpl() override;
19+
bool PostProcessingImpl() override;
20+
};
21+
22+
} // namespace kutergin_a_closest_pair
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#include "kutergin_a_closest_pair/mpi/include/ops_mpi.hpp"
2+
3+
#include <mpi.h>
4+
5+
#include <algorithm>
6+
#include <cmath>
7+
#include <limits>
8+
#include <vector>
9+
10+
#include "kutergin_a_closest_pair/common/include/common.hpp"
11+
12+
namespace kutergin_a_closest_pair {
13+
14+
KuterginAClosestPairMPI::KuterginAClosestPairMPI(const InType &in) {
15+
SetTypeOfTask(GetStaticTypeOfTask());
16+
GetInput() = in;
17+
GetOutput() = -1;
18+
}
19+
20+
bool KuterginAClosestPairMPI::ValidationImpl() {
21+
return true;
22+
}
23+
24+
bool KuterginAClosestPairMPI::PreProcessingImpl() {
25+
return true;
26+
}
27+
28+
namespace {
29+
30+
std::vector<int> DistributeData(int rank, int size, int n, const std::vector<int> &v) {
31+
int local_size = n / size;
32+
int remainder = n % size;
33+
34+
int start = (rank * local_size) + std::min(rank, remainder);
35+
int end = start + local_size + (rank < remainder ? 1 : 0);
36+
37+
if (rank == size - 1) {
38+
end = n;
39+
}
40+
41+
std::vector<int> local_data(end - start);
42+
if (rank == 0) {
43+
std::copy(v.begin() + start, v.begin() + end, local_data.begin());
44+
45+
for (int i = 1; i < size; ++i) {
46+
int other_start = (i * local_size) + std::min(i, remainder);
47+
int other_end = other_start + local_size + (i < remainder ? 1 : 0);
48+
if (i == size - 1) {
49+
other_end = n;
50+
}
51+
52+
MPI_Send(v.data() + other_start, other_end - other_start, MPI_INT, i, 0, MPI_COMM_WORLD);
53+
}
54+
} else {
55+
MPI_Recv(local_data.data(), static_cast<int>(local_data.size()), MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
56+
}
57+
58+
return local_data;
59+
}
60+
61+
int FindLocalMin(const std::vector<int> &local_data, int start_idx, int &found_idx) {
62+
int local_min = std::numeric_limits<int>::max();
63+
found_idx = -1;
64+
65+
for (int i = 0; i < static_cast<int>(local_data.size()) - 1; ++i) {
66+
int diff = std::abs(local_data[i + 1] - local_data[i]);
67+
if (diff < local_min) {
68+
local_min = diff;
69+
found_idx = start_idx + i;
70+
}
71+
}
72+
73+
return local_min;
74+
}
75+
76+
int CalculateStartIndex(int rank, int size, int n) {
77+
int local_size = n / size;
78+
int remainder = n % size;
79+
return (rank * local_size) + std::min(rank, remainder);
80+
}
81+
82+
int CalculateEndIndex(int rank, int size, int n) {
83+
int local_size = n / size;
84+
int remainder = n % size;
85+
int end = CalculateStartIndex(rank, size, n) + local_size + (rank < remainder ? 1 : 0);
86+
if (rank == size - 1) {
87+
end = n;
88+
}
89+
return end;
90+
}
91+
92+
int CheckBoundary(int rank, int size, int end, int n, const std::vector<int> &v, const std::vector<int> &local_data,
93+
int current_min, int &current_idx) {
94+
if (rank < size - 1 && end < n) {
95+
int boundary_diff = std::abs(v[end] - local_data.back());
96+
if (boundary_diff < current_min) {
97+
current_min = boundary_diff;
98+
current_idx = end - 1;
99+
}
100+
}
101+
return current_min;
102+
}
103+
104+
} // namespace
105+
106+
bool KuterginAClosestPairMPI::RunImpl() {
107+
int rank = 0;
108+
int size = 0;
109+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
110+
MPI_Comm_size(MPI_COMM_WORLD, &size);
111+
112+
const auto &v = GetInput();
113+
int n = static_cast<int>(v.size());
114+
115+
MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);
116+
117+
if (n < 2) {
118+
GetOutput() = -1;
119+
return true;
120+
}
121+
122+
auto local_data = DistributeData(rank, size, n, v);
123+
if (local_data.empty()) {
124+
GetOutput() = -1;
125+
return true;
126+
}
127+
128+
int start_idx = CalculateStartIndex(rank, size, n);
129+
int local_idx = -1;
130+
int local_min = FindLocalMin(local_data, start_idx, local_idx);
131+
132+
int end = CalculateEndIndex(rank, size, n);
133+
local_min = CheckBoundary(rank, size, end, n, v, local_data, local_min, local_idx);
134+
135+
struct MinIndex {
136+
int val = 0;
137+
int idx = -1;
138+
};
139+
140+
MinIndex local_result;
141+
local_result.val = local_min;
142+
local_result.idx = local_idx;
143+
144+
MinIndex global_result;
145+
146+
MPI_Allreduce(&local_result, &global_result, 1, MPI_2INT, MPI_MINLOC, MPI_COMM_WORLD);
147+
148+
GetOutput() = global_result.idx;
149+
return true;
150+
}
151+
152+
bool KuterginAClosestPairMPI::PostProcessingImpl() {
153+
return true;
154+
}
155+
156+
} // namespace kutergin_a_closest_pair
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Нахождение наиболее близких соседних элементов вектора
2+
3+
- Student: Кутергин Антон Андреевич, group 3823Б1ФИ1
4+
- Technology: SEQ | MPI
5+
- Variant: 7
6+
7+
## 1 Введение
8+
9+
Нужно было реализовать параллельный и последовательный алгоритм для поиска наиболее близких соседних элементов вектора
10+
11+
## 2 Постановка задачи
12+
13+
На вход подается вектор v, нужно найти такой индекс i, что велечина |v[i] - v[i+1]| минимальна среди всех пар элемента
14+
15+
## 3. Baseline Algorithm (Sequential)
16+
17+
Последовательно просматривает пары v[i], v[i+1], сравнивает разности и возвращает индекс максимума
18+
19+
## 4. Parallelization Scheme
20+
21+
Вектор разбивается на MPI процессы, корректно обрабатывая границы между процессами, чтобы последнему числу этого процесса и первому следующего процесса также образуют пару. Каждый ищет свой локальный минимум среди всех своих соседних пар. Возвращаем глобальный индекс пары с помощью MPI_Reduce
22+
23+
## 6. Experimental Setup
24+
25+
-HARDWARE/OS: CPU - Intel Core i5-8300H, cores/threads - 4/8, RAM - 12gb, OS - Windows 10
26+
TOOLCHAIN: g++ 13.3.0, build type - Release
27+
28+
29+
## 7. Results and Discussion
30+
31+
### 7.1 Correctness
32+
33+
Функциональные тесты: вектор может принимать как положительные, так и отрицательные значения или содержать и положительные и отрицательные значения, так же обрабатывается случай, когда вектор пуст или у него одно значение и пару ему не найти.
34+
MPI версия запускалась на 4 ядрах - эталонное ускорение в 4 раза.
35+
Тест на производительность: генерация n чисел и запуск алгоритмов, чтобы узнать время выполнения.
36+
37+
### 7.2 Performance
38+
39+
| Размер данных |Кол-во процессов| MPI версия (сек) | SEQ версия (сек) |
40+
|---------------|----------------|------------------------|-------------------------|
41+
| 100,000,000 | 2 | 0.1726183200 | 0.2780640800 |
42+
| 100,000,000 | 4 | 0.1601058600 | 0.3270964400 |
43+
| 100,000,000 | 8 | 0.1926481400 | 0.3761903000 |
44+
45+
46+
## 8. Conclusions
47+
48+
В ходе работы были разработаны последовательная(SEQ) и параллельная (MPI) версии алгоритма поиска пары соседних элементов с минимальной разностью. Оба варианта корректно проходят функциональные тесты, включая случаи с отрицательными значениями, смешанными диапазонами чисел и граничными условиями (пустой или одноэлементный вектор). MPI-распараллеливание показало ожидаемый выигрыш на 2 и 4 процессах относительно последовательной версии, что подтверждает эффективность алгоритма. При увелечении числа процессов до 8 наблюдается просадка из-за роста накладных расходов и нехваткой физических ядер, что привело к увелечению время выполнения. В целом работа выполнена успешно: достигнута корректность, показано преимущество MPI версии над SEQ.
49+
50+
## 9. References
51+
1.Лекции по параллельному программированию
52+
2.Практические занятия по параллельному программированию
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#pragma once
2+
3+
#include <vector>
4+
5+
#include "kutergin_a_closest_pair/common/include/common.hpp"
6+
#include "task/include/task.hpp"
7+
8+
namespace kutergin_a_closest_pair {
9+
10+
class KuterginAClosestPairSEQ : public BaseTask {
11+
public:
12+
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
13+
return ppc::task::TypeOfTask::kSEQ;
14+
}
15+
explicit KuterginAClosestPairSEQ(const InType &in);
16+
17+
private:
18+
std::vector<int> data_;
19+
bool ValidationImpl() override;
20+
bool PreProcessingImpl() override;
21+
bool RunImpl() override;
22+
bool PostProcessingImpl() override;
23+
};
24+
25+
} // namespace kutergin_a_closest_pair
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include "kutergin_a_closest_pair/seq/include/ops_seq.hpp"
2+
3+
#include <algorithm>
4+
#include <cstdlib>
5+
#include <ranges>
6+
#include <vector>
7+
8+
#include "kutergin_a_closest_pair/common/include/common.hpp"
9+
10+
namespace kutergin_a_closest_pair {
11+
12+
KuterginAClosestPairSEQ::KuterginAClosestPairSEQ(const InType &in) {
13+
SetTypeOfTask(GetStaticTypeOfTask());
14+
GetInput() = in;
15+
GetOutput() = -1;
16+
}
17+
18+
bool KuterginAClosestPairSEQ::ValidationImpl() {
19+
return true;
20+
}
21+
22+
bool KuterginAClosestPairSEQ::PreProcessingImpl() {
23+
return true;
24+
}
25+
26+
bool KuterginAClosestPairSEQ::RunImpl() {
27+
const auto &v = GetInput();
28+
29+
if (v.size() < 2) {
30+
GetOutput() = -1;
31+
return true;
32+
}
33+
34+
auto idx_range = std::views::iota(size_t{0}, v.size() - 1);
35+
36+
auto comparator = [&](size_t i, size_t j) {
37+
int diff_i = std::abs(v[i] - v[i + 1]);
38+
int diff_j = std::abs(v[j] - v[j + 1]);
39+
return (diff_i < diff_j) || (diff_i == diff_j && i < j);
40+
};
41+
42+
auto min_it = std::ranges::min_element(idx_range, comparator);
43+
int min_idx = static_cast<int>(*min_it);
44+
45+
GetOutput() = min_idx;
46+
return true;
47+
}
48+
49+
bool KuterginAClosestPairSEQ::PostProcessingImpl() {
50+
return true;
51+
}
52+
53+
} // namespace kutergin_a_closest_pair
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"tasks_type": "processes",
3+
"tasks": {
4+
"mpi": "enabled",
5+
"seq": "enabled"
6+
}
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
InheritParentConfig: true
2+
3+
Checks: >
4+
-modernize-loop-convert,
5+
-cppcoreguidelines-avoid-goto,
6+
-cppcoreguidelines-avoid-non-const-global-variables,
7+
-misc-use-anonymous-namespace,
8+
-modernize-use-std-print,
9+
-modernize-type-traits
10+
11+
CheckOptions:
12+
- key: readability-function-cognitive-complexity.Threshold
13+
value: 50 # Relaxed for tests

0 commit comments

Comments
 (0)