Skip to content

Commit c41643a

Browse files
Поташник Максим. Технология SEQ-MPI. Поиск кратчайших путей из одной вершины (алгоритм Беллмана-Форда). С CRS формой хранения графа. Вариант 23. (#291)
## Описание - **Задача**: Поиск кратчайших путей из одной вершины (алгоритм Беллмана-Форда). С CRS формой хранения графа. - **Вариант**: 23 - **Технология**: SEQ, MPI - **Описание**: В работе граф хранится в форме CRS, представляющую из себя набор из трёх массивов: row_ptr, col_idx, weights. SEQ: Инициализация: расстояние до исходной вершины = 0, до остальных = 1e9. Выполняет (n-1) итераций, на каждой обрабатываются все рёбра из вершин с известными расстояниями. Использует два массива расстояний (dist и dist_next) для корректного обновления за одну итерацию. MPI: Граф полностью рассылается на каждый процесс с помощью MPI_BCast, а нагрузка по обработке вершин графа равномерно распределяется между процессами. Глобальная синхронизация выполняется через MPI_Allreduce. --- ## Чек-лист - [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 0f5a451 commit c41643a

11 files changed

Lines changed: 600 additions & 0 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#pragma once
2+
3+
#include <algorithm>
4+
#include <cmath>
5+
#include <vector>
6+
7+
#include "task/include/task.hpp"
8+
9+
namespace potashnik_m_short_ways_bellford {
10+
11+
// CRS Graph class
12+
class Graph {
13+
public:
14+
int n;
15+
16+
std::vector<int> row_ptr;
17+
std::vector<int> col_ind;
18+
std::vector<int> weights;
19+
Graph() : n(0) {}
20+
explicit Graph(int n_vertices) : n(n_vertices), row_ptr(n_vertices + 1, 0) {}
21+
22+
void BuildGraph(const std::vector<int> &src, const std::vector<int> &dst, const std::vector<int> &w) {
23+
int m = static_cast<int>(src.size());
24+
25+
for (int i = 0; i < m; i++) {
26+
row_ptr[src[i] + 1]++;
27+
}
28+
29+
for (int i = 0; i < n; i++) {
30+
row_ptr[i + 1] += row_ptr[i];
31+
}
32+
33+
col_ind.resize(m);
34+
weights.resize(m);
35+
std::vector<int> cur = row_ptr;
36+
37+
for (int i = 0; i < m; i++) {
38+
int u = src[i];
39+
int pos = cur[u]++;
40+
41+
col_ind[pos] = dst[i];
42+
weights[pos] = w[i];
43+
}
44+
}
45+
46+
[[nodiscard]] int Begin(int u) const {
47+
return row_ptr[u];
48+
}
49+
[[nodiscard]] int End(int u) const {
50+
return row_ptr[u + 1];
51+
}
52+
};
53+
54+
inline void IterateThroughVertex(const Graph &g, int u, const std::vector<int> &dist, std::vector<int> &dist_out) {
55+
for (int i = g.Begin(u); i < g.End(u); i++) {
56+
int v = g.col_ind[i];
57+
int w = g.weights[i];
58+
59+
int new_dist = dist[u] + w;
60+
dist_out[v] = std::min(new_dist, dist_out[v]);
61+
}
62+
}
63+
64+
inline Graph GenerateGraph(int n) {
65+
Graph g(n);
66+
std::vector<int> src;
67+
std::vector<int> dst;
68+
std::vector<int> w;
69+
int layers = static_cast<int>(std::sqrt(n));
70+
layers = std::max(layers, 1);
71+
int layer_size = n / layers;
72+
for (int lidx = 0; lidx < layers - 1; lidx++) {
73+
int start_u = lidx * layer_size;
74+
int end_u = (lidx + 1) * layer_size;
75+
int start_v = (lidx + 1) * layer_size;
76+
int end_v = (lidx + 2) * layer_size;
77+
end_v = std::min(end_v, n);
78+
for (int uidx = start_u; uidx < end_u; uidx++) {
79+
for (int vidx = start_v; vidx < end_v; vidx++) {
80+
src.push_back(uidx);
81+
dst.push_back(vidx);
82+
int weight = ((uidx * 13 + vidx * 7) % 10) + 1;
83+
w.push_back(weight);
84+
}
85+
}
86+
}
87+
g.BuildGraph(src, dst, w);
88+
return g;
89+
}
90+
91+
using InType = Graph; // Graph
92+
using OutType = std::vector<int>; // Vector of shortest paths
93+
using TestType = int; // Amount of vertices
94+
using BaseTask = ppc::task::Task<InType, OutType>;
95+
96+
} // namespace potashnik_m_short_ways_bellford
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ФИ3",
7+
"task_number": "3"
8+
}
9+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#pragma once
2+
3+
#include <mpi.h>
4+
5+
#include <vector>
6+
7+
#include "potashnik_m_short_ways_bellford/common/include/common.hpp"
8+
#include "task/include/task.hpp"
9+
10+
namespace potashnik_m_short_ways_bellford {
11+
12+
class PotashnikMShortWaysBellfordMPI : public BaseTask {
13+
public:
14+
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
15+
return ppc::task::TypeOfTask::kMPI;
16+
}
17+
explicit PotashnikMShortWaysBellfordMPI(const InType &in);
18+
19+
private:
20+
bool ValidationImpl() override;
21+
bool PreProcessingImpl() override;
22+
bool RunImpl() override;
23+
bool PostProcessingImpl() override;
24+
};
25+
26+
inline void BellmanFordAlgoIterationMpi(const Graph &g, const std::vector<int> &dist, std::vector<int> &dist_next,
27+
int start, int end) {
28+
dist_next = dist;
29+
for (int uidx = start; uidx < end; uidx++) {
30+
if (dist[uidx] == 1e9) {
31+
continue;
32+
}
33+
IterateThroughVertex(g, uidx, dist, dist_next);
34+
}
35+
}
36+
37+
inline void BellmanFordAlgoMpi(const Graph &g, int source, std::vector<int> &dist) {
38+
int rank = 0;
39+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
40+
int size = 0;
41+
MPI_Comm_size(MPI_COMM_WORLD, &size);
42+
43+
int n = g.n;
44+
45+
dist.assign(n, 1e9);
46+
if (rank == 0) {
47+
dist[source] = 0;
48+
}
49+
50+
MPI_Bcast(dist.data(), n, MPI_INT, 0, MPI_COMM_WORLD);
51+
52+
std::vector<int> dist_next(n);
53+
54+
int start = rank * n / size;
55+
int end = (rank + 1) * n / size;
56+
57+
for (int i = 0; i < n - 1; i++) {
58+
BellmanFordAlgoIterationMpi(g, dist, dist_next, start, end);
59+
MPI_Allreduce(dist_next.data(), dist.data(), n, MPI_INT, MPI_MIN, MPI_COMM_WORLD);
60+
}
61+
}
62+
63+
} // namespace potashnik_m_short_ways_bellford
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#include "potashnik_m_short_ways_bellford/mpi/include/ops_mpi.hpp"
2+
3+
#include <cmath>
4+
#include <vector>
5+
6+
#include "potashnik_m_short_ways_bellford/common/include/common.hpp"
7+
8+
namespace potashnik_m_short_ways_bellford {
9+
10+
PotashnikMShortWaysBellfordMPI::PotashnikMShortWaysBellfordMPI(const InType &in) {
11+
SetTypeOfTask(GetStaticTypeOfTask());
12+
GetInput() = in;
13+
GetOutput() = OutType{};
14+
}
15+
16+
bool PotashnikMShortWaysBellfordMPI::ValidationImpl() {
17+
return true;
18+
}
19+
20+
bool PotashnikMShortWaysBellfordMPI::PreProcessingImpl() {
21+
return true;
22+
}
23+
24+
bool PotashnikMShortWaysBellfordMPI::RunImpl() {
25+
std::vector<int> dist;
26+
potashnik_m_short_ways_bellford::BellmanFordAlgoMpi(GetInput(), 0, dist);
27+
GetOutput() = dist;
28+
return true;
29+
}
30+
31+
bool PotashnikMShortWaysBellfordMPI::PostProcessingImpl() {
32+
return true;
33+
}
34+
35+
} // namespace potashnik_m_short_ways_bellford
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Поиск кратчайших путей из одной вершины (алгоритм Беллмана-Форда). С CRS формой хранения графа.
2+
- Student: Поташник Максим Ярославович, group 3823Б1ФИ3
3+
- Technology: SEQ | MPI
4+
- Variant: 23
5+
6+
## 1. Introduction
7+
Задача поиска кратчайших путей из одной вершины является одной из базовых задач теории графов. При этом, часто возникает необходимость решать подобные задачи для больших графов, из-за чего большую актуальность обретают алгоритмы, позволяющие выполнить распараллеливание.
8+
9+
Алгоритм Беллмана–Форда обладает высокой вычислительной сложностью, что делает его подходящим кандидатом для распараллеливания. В данной работе исследуется ускорение вычислений при использовании технологии MPI по сравнению с SEQ реализацией.
10+
11+
## 2. Problem statement
12+
Требуется найти кратчайшие расстояния от фиксированной вершины графа до всех остальных вершин с использованием алгоритма Беллмана–Форда. Граф хранится в форме CRS.
13+
14+
### Входные данные:
15+
Ориентированный взвешенный граф, представленный в CRS (Compressed Row Storage) формате.
16+
Граф генерируется детерминированно по заданному числу вершин.
17+
Источником кратчайших путей является вершина с индексом 0.
18+
19+
### Выходные данные:
20+
Вектор целых чисел — кратчайшие расстояния от вершины-источника до всех остальных вершин графа.
21+
22+
### 3. Baseline Algorithm (Sequential)
23+
Для хранения графа в форме CRS реализован класс Graph, который состоит из:
24+
1. массива row_ptr, задающего границы списков смежности вершин;
25+
2. массива col_idx, содержащего номера смежных вершин;
26+
3. массива weights, содержащего веса рёбер.
27+
28+
```
29+
class Graph {
30+
public:
31+
int n;
32+
std::vector<int> row_ptr;
33+
std::vector<int> col_ind;
34+
std::vector<int> weights;
35+
};
36+
```
37+
38+
В обеих реализациях алгоритма (SEQ и MPI) выполняется релаксация рёбер из вершины:
39+
```
40+
inline void IterateThroughVertex(const Graph &g, int u, const std::vector<int> &dist, std::vector<int> &dist_out) {
41+
for (int i = g.Begin(u); i < g.End(u); i++) {
42+
int v = g.col_ind[i];
43+
int w = g.weights[i];
44+
int new_dist = dist[u] + w;
45+
dist_out[v] = std::min(new_dist, dist_out[v]);
46+
}
47+
}
48+
```
49+
50+
Инициализация: dist[source] = 0, все остальные dist[v] = 1e9
51+
Повторить (n-1) раз:
52+
- Для каждой вершины u с dist[u] != 1e9:
53+
- Для каждого ребра u->v с весом w:
54+
- dist[v] = min(dist[v], dist[u] + w)
55+
Результат: dist[v] содержит кратчайшее расстояние от вершины source до v для каждой вершины v
56+
57+
SEQ Алгоритм:
58+
```
59+
inline void BellmanFordAlgoSeq(const Graph &g, int source, std::vector<int> &dist) {
60+
dist.assign(n, 1e9);
61+
dist[source] = 0;
62+
63+
for (int i = 0; i < n - 1; i++) {
64+
BellmanFordAlgoIterationSeq(g, dist, dist_next);
65+
dist.swap(dist_next);
66+
}
67+
}
68+
```
69+
Итерация алгоритма:
70+
```
71+
inline void BellmanFordAlgoIterationSeq(const Graph &g, const std::vector<int> &dist, std::vector<int> &dist_next) {
72+
int n = g.n;
73+
dist_next = dist;
74+
for (int uidx = 0; uidx < n; uidx++) {
75+
if (dist[uidx] == 1e9) {
76+
continue;
77+
}
78+
IterateThroughVertex(g, uidx, dist, dist_next);
79+
}
80+
}
81+
```
82+
83+
### 4. Parallelization Scheme
84+
В параллельной реализации граф полностью реплицируется на каждом процессе, и все процессы хранят одинаковую CRS-структуру графа. Это происходит с помощью операции MPI_BCast.
85+
Распараллеливание достигается за счёт разделения множества вершин на непересекающиеся диапазоны, при этом каждый MPI-процесс выполняет релаксацию рёбер только для своего диапазона вершин - равномерно распределяется вычислительная нагрузка между процессами.
86+
После каждой итерации локальные результаты объединяются с помощью операции MPI_Allreduce.
87+
88+
MPI Алгоритм:
89+
```
90+
inline void BellmanFordAlgoMpi(const Graph &g, int source, std::vector<int> &dist) {
91+
// Подготовка данных
92+
93+
int start = rank * n / size;
94+
int end = (rank + 1) * n / size;
95+
96+
for (int i = 0; i < n - 1; i++) {
97+
BellmanFordAlgoIterationMpi(g, dist, dist_next, start, end);
98+
MPI_Allreduce(dist_next.data(), dist.data(), n, MPI_INT, MPI_MIN, MPI_COMM_WORLD);
99+
}
100+
}
101+
```
102+
Итерация алгоритма:
103+
```
104+
inline void BellmanFordAlgoIterationMpi(const Graph &g, const std::vector<int> &dist, std::vector<int> &dist_next, int start, int end) {
105+
dist_next = dist;
106+
for (int uidx = start; uidx < end; uidx++) {
107+
if (dist[uidx] == 1e9) {
108+
continue;
109+
}
110+
IterateThroughVertex(g, uidx, dist, dist_next);
111+
}
112+
}
113+
```
114+
115+
### 5. Experimental Setup
116+
- Hardware/OS: 12th gen Intel(R) Core(TM) i5-12450H, 8 ядер, 16 GB RAM, Windows 11 x64
117+
- Toolchain: compiler, version, build type (Release/RelWithDebInfo)
118+
- Cmake 3.28.3
119+
- Компилятор: g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
120+
- Использовался Docker-контейнер.
121+
- Режим сборки: Release.
122+
- Data: Для замера производительности использовался граф с 7000 вершинами, генерируемый произвольно (но детерминированно).
123+
124+
## 6. Results and Discussion
125+
126+
### 6.1 Correctness
127+
Корректность работы проверена с помощью тестов Google Test на графах с кол-вом вершин: 5, 7, 10, 15, 20.
128+
129+
### 6.2 Performance
130+
| Mode | Count | Time, s | Speedup | Efficiency |
131+
|-------------|-------|---------|---------|------------|
132+
| seq | 1 | 3.300 | 1.00 | N/A |
133+
| mpi | 2 | 2.030 | 1.63 | 81.5% |
134+
| mpi | 4 | 1.690 | 1.95 | 48.8% |
135+
136+
## 7. Conclusions
137+
Эффективность работы mpi версии при 2-х процессах составляет 81.5%, что является хорошим результатом. Эффективность про 4-х процессах - 48.8%, что объясняется накладными расходами на коммуникацию и синхронизацию между процессами.
138+
139+
## 8. References
140+
1. "Параллельное программирование для кластерных систем" ННГУ им. Лобачевского, ИИТММ

0 commit comments

Comments
 (0)