Skip to content

Commit 0f5a451

Browse files
Пихотский Роман. Технология SEQ-MPI. Умножение разреженных матриц. Элементы типа double. Формат хранения матрицы – строковый (CRS).. Вариант 4 (#286)
## Описание - **Задача**: Умножение разреженных матриц. Элементы типа double. Формат хранения матрицы – строковый (CRS) - **Вариант**: 4 - **Технология**: SEQ, MPI - **Описание** вашей реализации и отчёта. Реализация включает последовательную (SEQ) и параллельную (MPI) версии умножения разреженных матриц в формате CRS. SEQ-версия использует транспонирование второй матрицы и merge-подобный алгоритм для пересечения индексов ненулевых элементов, что позволяет избегать обработки нулей. MPI-версия применяет горизонтальное распределение строк первой матрицы (A) между процессами. Вторая матрица (B) транспонируется и целиком рассылается всем процессам для эффективного доступа к столбцам. Каждый процесс локально вычисляет свою часть результирующей матрицы C, после чего результаты собираются на нулевом процессе. В отчёте содержится постановка задачи, детальное описание алгоритмов (SEQ и MPI), структура проекта, функциональные тесты (12 кейсов) и результаты экспериментов, демонстрирующие ускорение параллельной версии. --- ## Чек-лист <!-- Пожалуйста, убедитесь, что следующие пункты выполнены **до** отправки pull request'а и запроса его ревью: --> - [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, являются точными и достоверными <!-- ПРИМЕЧАНИЕ: Ложные сведения в этом чек-листе могут привести к отклонению PR и получению нулевого балла за соответствующую задачу. -->
1 parent 6596297 commit 0f5a451

11 files changed

Lines changed: 953 additions & 0 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <tuple>
5+
#include <vector>
6+
7+
#include "task/include/task.hpp"
8+
9+
namespace pikhotskiy_r_multiplication_of_sparse_matrices {
10+
11+
struct SparseMatrixCRS {
12+
int rows;
13+
int cols;
14+
std::vector<double> values;
15+
std::vector<int> col_indices;
16+
std::vector<int> row_ptr;
17+
18+
SparseMatrixCRS() : rows(0), cols(0) {}
19+
SparseMatrixCRS(int r, int c) : rows(r), cols(c), row_ptr(r + 1, 0) {}
20+
};
21+
22+
using InType = std::tuple<SparseMatrixCRS, SparseMatrixCRS>;
23+
using OutType = SparseMatrixCRS;
24+
using TestType = std::tuple<SparseMatrixCRS, SparseMatrixCRS, std::string>;
25+
using BaseTask = ppc::task::Task<InType, OutType>;
26+
27+
SparseMatrixCRS DenseToCRS(const std::vector<double> &dense, int rows, int cols);
28+
29+
std::vector<double> CRSToDense(const SparseMatrixCRS &sparse);
30+
31+
SparseMatrixCRS TransposeCRS(const SparseMatrixCRS &matrix);
32+
33+
bool CompareSparseMatrices(const SparseMatrixCRS &a, const SparseMatrixCRS &b, double eps = 1e-9);
34+
35+
} // namespace pikhotskiy_r_multiplication_of_sparse_matrices
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": "3"
8+
}
9+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#pragma once
2+
3+
#include "pikhotskiy_r_multiplication_of_sparse_matrices/common/include/common.hpp"
4+
#include "task/include/task.hpp"
5+
6+
namespace pikhotskiy_r_multiplication_of_sparse_matrices {
7+
8+
class SparseMatrixMultiplicationMPI : public BaseTask {
9+
public:
10+
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
11+
return ppc::task::TypeOfTask::kMPI;
12+
}
13+
explicit SparseMatrixMultiplicationMPI(const InType &in);
14+
15+
private:
16+
bool ValidationImpl() override;
17+
bool PreProcessingImpl() override;
18+
bool RunImpl() override;
19+
bool PostProcessingImpl() override;
20+
21+
static void BroadcastSparseMatrix(SparseMatrixCRS &matrix, int root);
22+
void GatherResults(const SparseMatrixCRS &local_result, int my_num_rows);
23+
24+
SparseMatrixCRS mat_a_;
25+
SparseMatrixCRS mat_b_;
26+
SparseMatrixCRS mat_b_transposed_;
27+
};
28+
29+
} // namespace pikhotskiy_r_multiplication_of_sparse_matrices
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
#include "pikhotskiy_r_multiplication_of_sparse_matrices/mpi/include/ops_mpi.hpp"
2+
3+
#include <mpi.h>
4+
5+
#include <cmath>
6+
#include <cstddef>
7+
#include <utility>
8+
#include <vector>
9+
10+
#include "pikhotskiy_r_multiplication_of_sparse_matrices/common/include/common.hpp"
11+
12+
namespace pikhotskiy_r_multiplication_of_sparse_matrices {
13+
14+
namespace {
15+
16+
double ComputeRowColProduct(const SparseMatrixCRS &mat_a, const SparseMatrixCRS &mat_bt, int row_a, int row_bt) {
17+
double sum = 0.0;
18+
int a_idx = mat_a.row_ptr[row_a];
19+
int a_end = mat_a.row_ptr[row_a + 1];
20+
int bt_idx = mat_bt.row_ptr[row_bt];
21+
int bt_end = mat_bt.row_ptr[row_bt + 1];
22+
23+
while (a_idx < a_end && bt_idx < bt_end) {
24+
int a_col = mat_a.col_indices[a_idx];
25+
int bt_col = mat_bt.col_indices[bt_idx];
26+
if (a_col == bt_col) {
27+
sum += mat_a.values[a_idx] * mat_bt.values[bt_idx];
28+
++a_idx;
29+
++bt_idx;
30+
} else if (a_col < bt_col) {
31+
++a_idx;
32+
} else {
33+
++bt_idx;
34+
}
35+
}
36+
return sum;
37+
}
38+
39+
void ComputeDisplacements(int size, const std::vector<int> &all_nnz, const std::vector<int> &all_num_rows,
40+
std::vector<int> &nnz_displs, std::vector<int> &row_displs, int &total_nnz) {
41+
nnz_displs[0] = 0;
42+
row_displs[0] = 0;
43+
total_nnz = 0;
44+
for (int i = 0; i < size; ++i) {
45+
if (i > 0) {
46+
nnz_displs[i] = nnz_displs[i - 1] + all_nnz[i - 1];
47+
row_displs[i] = row_displs[i - 1] + all_num_rows[i - 1];
48+
}
49+
total_nnz += all_nnz[i];
50+
}
51+
}
52+
53+
void BuildResultRowPtr(SparseMatrixCRS &result, int size, const std::vector<int> &all_nnz,
54+
const std::vector<int> &all_num_rows, const std::vector<int> &all_row_ptr_shifted) {
55+
result.row_ptr[0] = 0;
56+
int current_offset = 0;
57+
int row_idx = 0;
58+
for (int proc = 0; proc < size; ++proc) {
59+
for (int ii = 0; ii < all_num_rows[proc]; ++ii) {
60+
result.row_ptr[row_idx + 1] = all_row_ptr_shifted[row_idx] + current_offset;
61+
++row_idx;
62+
}
63+
current_offset += all_nnz[proc];
64+
}
65+
}
66+
67+
} // namespace
68+
69+
SparseMatrixMultiplicationMPI::SparseMatrixMultiplicationMPI(const InType &in) {
70+
SetTypeOfTask(GetStaticTypeOfTask());
71+
int rank = 0;
72+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
73+
if (rank == 0) {
74+
GetInput() = in;
75+
}
76+
}
77+
78+
void SparseMatrixMultiplicationMPI::BroadcastSparseMatrix(SparseMatrixCRS &matrix, int root) {
79+
MPI_Bcast(&matrix.rows, 1, MPI_INT, root, MPI_COMM_WORLD);
80+
MPI_Bcast(&matrix.cols, 1, MPI_INT, root, MPI_COMM_WORLD);
81+
82+
int nnz = static_cast<int>(matrix.values.size());
83+
MPI_Bcast(&nnz, 1, MPI_INT, root, MPI_COMM_WORLD);
84+
85+
int rank = 0;
86+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
87+
if (rank != root) {
88+
matrix.values.resize(nnz);
89+
matrix.col_indices.resize(nnz);
90+
matrix.row_ptr.resize(matrix.rows + 1);
91+
}
92+
93+
if (nnz > 0) {
94+
MPI_Bcast(matrix.values.data(), nnz, MPI_DOUBLE, root, MPI_COMM_WORLD);
95+
MPI_Bcast(matrix.col_indices.data(), nnz, MPI_INT, root, MPI_COMM_WORLD);
96+
}
97+
MPI_Bcast(matrix.row_ptr.data(), matrix.rows + 1, MPI_INT, root, MPI_COMM_WORLD);
98+
}
99+
100+
bool SparseMatrixMultiplicationMPI::ValidationImpl() {
101+
int rank = 0;
102+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
103+
int error_flag = 0;
104+
105+
if (rank == 0) {
106+
const auto &mat_a = std::get<0>(GetInput());
107+
const auto &mat_b = std::get<1>(GetInput());
108+
109+
if (mat_a.cols != mat_b.rows) {
110+
error_flag = 1;
111+
}
112+
if (mat_a.rows <= 0 || mat_a.cols <= 0 || mat_b.rows <= 0 || mat_b.cols <= 0) {
113+
error_flag = 1;
114+
}
115+
if (mat_a.row_ptr.size() != static_cast<std::size_t>(mat_a.rows) + 1) {
116+
error_flag = 1;
117+
}
118+
if (mat_b.row_ptr.size() != static_cast<std::size_t>(mat_b.rows) + 1) {
119+
error_flag = 1;
120+
}
121+
}
122+
123+
MPI_Bcast(&error_flag, 1, MPI_INT, 0, MPI_COMM_WORLD);
124+
return error_flag == 0;
125+
}
126+
127+
bool SparseMatrixMultiplicationMPI::PreProcessingImpl() {
128+
int rank = 0;
129+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
130+
131+
if (rank == 0) {
132+
mat_a_ = std::get<0>(GetInput());
133+
mat_b_ = std::get<1>(GetInput());
134+
mat_b_transposed_ = TransposeCRS(mat_b_);
135+
}
136+
137+
BroadcastSparseMatrix(mat_a_, 0);
138+
BroadcastSparseMatrix(mat_b_transposed_, 0);
139+
140+
if (rank == 0) {
141+
mat_b_.rows = std::get<1>(GetInput()).rows;
142+
mat_b_.cols = std::get<1>(GetInput()).cols;
143+
}
144+
MPI_Bcast(&mat_b_.rows, 1, MPI_INT, 0, MPI_COMM_WORLD);
145+
MPI_Bcast(&mat_b_.cols, 1, MPI_INT, 0, MPI_COMM_WORLD);
146+
147+
return true;
148+
}
149+
150+
void SparseMatrixMultiplicationMPI::GatherResults(const SparseMatrixCRS &local_result, int my_num_rows) {
151+
int rank = 0;
152+
int size = 0;
153+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
154+
MPI_Comm_size(MPI_COMM_WORLD, &size);
155+
156+
int local_nnz = static_cast<int>(local_result.values.size());
157+
std::vector<int> all_nnz(size);
158+
std::vector<int> all_num_rows(size);
159+
MPI_Gather(&local_nnz, 1, MPI_INT, all_nnz.data(), 1, MPI_INT, 0, MPI_COMM_WORLD);
160+
MPI_Gather(&my_num_rows, 1, MPI_INT, all_num_rows.data(), 1, MPI_INT, 0, MPI_COMM_WORLD);
161+
162+
std::vector<int> nnz_displs(size);
163+
std::vector<int> row_displs(size);
164+
int total_nnz = 0;
165+
if (rank == 0) {
166+
ComputeDisplacements(size, all_nnz, all_num_rows, nnz_displs, row_displs, total_nnz);
167+
}
168+
169+
std::vector<double> all_values;
170+
std::vector<int> all_col_indices;
171+
if (rank == 0 && total_nnz > 0) {
172+
all_values.resize(total_nnz);
173+
all_col_indices.resize(total_nnz);
174+
}
175+
MPI_Gatherv(local_result.values.data(), local_nnz, MPI_DOUBLE, all_values.data(), all_nnz.data(), nnz_displs.data(),
176+
MPI_DOUBLE, 0, MPI_COMM_WORLD);
177+
MPI_Gatherv(local_result.col_indices.data(), local_nnz, MPI_INT, all_col_indices.data(), all_nnz.data(),
178+
nnz_displs.data(), MPI_INT, 0, MPI_COMM_WORLD);
179+
180+
std::vector<int> local_row_ptr_shifted(my_num_rows);
181+
for (int i = 0; i < my_num_rows; ++i) {
182+
local_row_ptr_shifted[i] = local_result.row_ptr[i + 1];
183+
}
184+
185+
std::vector<int> all_row_ptr_shifted;
186+
if (rank == 0 && mat_a_.rows > 0) {
187+
all_row_ptr_shifted.resize(mat_a_.rows);
188+
}
189+
MPI_Gatherv(local_row_ptr_shifted.data(), my_num_rows, MPI_INT, all_row_ptr_shifted.data(), all_num_rows.data(),
190+
row_displs.data(), MPI_INT, 0, MPI_COMM_WORLD);
191+
192+
if (rank == 0) {
193+
SparseMatrixCRS result(mat_a_.rows, mat_b_.cols);
194+
result.values = std::move(all_values);
195+
result.col_indices = std::move(all_col_indices);
196+
result.row_ptr.resize(mat_a_.rows + 1);
197+
BuildResultRowPtr(result, size, all_nnz, all_num_rows, all_row_ptr_shifted);
198+
GetOutput() = result;
199+
}
200+
201+
SparseMatrixCRS &output = GetOutput();
202+
BroadcastSparseMatrix(output, 0);
203+
}
204+
205+
bool SparseMatrixMultiplicationMPI::RunImpl() {
206+
int rank = 0;
207+
int size = 0;
208+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
209+
MPI_Comm_size(MPI_COMM_WORLD, &size);
210+
211+
int total_rows = mat_a_.rows;
212+
int base_rows = total_rows / size;
213+
int extra_rows = total_rows % size;
214+
215+
int my_start_row = 0;
216+
for (int rr = 0; rr < rank; ++rr) {
217+
my_start_row += base_rows + (rr < extra_rows ? 1 : 0);
218+
}
219+
int my_num_rows = base_rows + (rank < extra_rows ? 1 : 0);
220+
221+
SparseMatrixCRS local_result(my_num_rows, mat_b_.cols);
222+
local_result.row_ptr.resize(static_cast<std::size_t>(my_num_rows) + 1);
223+
if (!local_result.row_ptr.empty()) {
224+
local_result.row_ptr[0] = 0;
225+
}
226+
227+
for (int local_i = 0; local_i < my_num_rows; ++local_i) {
228+
int global_i = my_start_row + local_i;
229+
for (int jj = 0; jj < mat_b_.cols; ++jj) {
230+
double sum = ComputeRowColProduct(mat_a_, mat_b_transposed_, global_i, jj);
231+
if (std::abs(sum) > 1e-12) {
232+
local_result.values.push_back(sum);
233+
local_result.col_indices.push_back(jj);
234+
}
235+
}
236+
local_result.row_ptr[local_i + 1] = static_cast<int>(local_result.values.size());
237+
}
238+
239+
GatherResults(local_result, my_num_rows);
240+
return true;
241+
}
242+
243+
bool SparseMatrixMultiplicationMPI::PostProcessingImpl() {
244+
return true;
245+
}
246+
247+
} // namespace pikhotskiy_r_multiplication_of_sparse_matrices

0 commit comments

Comments
 (0)