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

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

#include "task/include/task.hpp"

namespace kutergin_a_fox_algorithm {

using InType = std::pair<std::vector<std::vector<double>>, std::vector<std::vector<double>>>;
using OutType = std::vector<std::vector<double>>;
using TestType = std::tuple<int, std::string>;
using BaseTask = ppc::task::Task<InType, OutType>;

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

#include <utility>
#include <vector>

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

namespace kutergin_a_fox_algorithm {

class FoxAlgorithmMPI : public BaseTask {
public:
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
return ppc::task::TypeOfTask::kMPI;
}
explicit FoxAlgorithmMPI(const InType &in);

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

static bool CheckMatrices(const std::vector<std::vector<double>> &ma, const std::vector<std::vector<double>> &mb);
void SpreadB(int n, std::vector<double> &lb);
void SendRowsA(int target, int rpp, int rem, int n, int &curr);
void SpreadA(int rk, int sz, int n, int lr, std::vector<double> &la);
static void MultiplyBlocks(int n, int lr, const std::vector<double> &la, const std::vector<double> &lb,
std::vector<double> &lc);
void CollectResults(int rk, int sz, int n, int rpp, int rem, int lr, const std::vector<double> &lc);
void SyncFinalResult(int rk, int n);
};

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

#include <mpi.h>

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

#include "kutergin_a_fox_algorithm/common/include/common.hpp"

namespace {

int RowsForRank(int rank, int rpp, int rem) {
return (rank < rem) ? (rpp + 1) : rpp;
}

void CopyRows(const std::vector<std::vector<double>> &src, int start_row, int rows, int n, std::vector<double> &dst) {
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < n; ++j) {
dst[(static_cast<std::size_t>(i) * n) + j] = src[start_row + i][j];
}
}
}

void CopyBlockToMatrix(const std::vector<double> &src, int rows, int n, int start_row,
std::vector<std::vector<double>> &dst) {
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < n; ++j) {
dst[start_row + i][j] = src[(static_cast<std::size_t>(i) * n) + j];
}
}
}

} // namespace

namespace kutergin_a_fox_algorithm {

FoxAlgorithmMPI::FoxAlgorithmMPI(const InType &in) {
SetTypeOfTask(GetStaticTypeOfTask());
GetInput() = in;
}

bool FoxAlgorithmMPI::CheckMatrices(const std::vector<std::vector<double>> &ma,
const std::vector<std::vector<double>> &mb) {
if (ma.empty() || mb.empty()) {
return false;
}

const auto n = static_cast<int>(ma.size());

if (!std::ranges::all_of(ma, [n](const auto &row) { return row.size() == static_cast<std::size_t>(n); })) {
return false;
}

if (mb.size() != static_cast<std::size_t>(n)) {
return false;
}

if (!std::ranges::all_of(mb, [n](const auto &row) { return row.size() == static_cast<std::size_t>(n); })) {
return false;
}

return true;
}

void FoxAlgorithmMPI::SpreadB(int n, std::vector<double> &lb) {
int rk = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rk);

if (rk == 0) {
const auto &matrix_b = GetInput().second;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
lb[(static_cast<std::size_t>(i) * n) + j] = matrix_b[i][j];
}
}
}

MPI_Bcast(lb.data(), n * n, MPI_DOUBLE, 0, MPI_COMM_WORLD);
}

void FoxAlgorithmMPI::SpreadA(int rk, int sz, int n, int lr, std::vector<double> &la) {
const auto &matrix_a = GetInput().first;

const int rpp = n / sz;
const int rem = n % sz;

if (rk != 0) {
MPI_Recv(la.data(), lr * n, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
return;
}

int curr_row = 0;

CopyRows(matrix_a, curr_row, lr, n, la);
curr_row += lr;

for (int dist = 1; dist < sz; ++dist) {
const int rows = RowsForRank(dist, rpp, rem);
std::vector<double> buf(static_cast<std::size_t>(rows) * n);

CopyRows(matrix_a, curr_row, rows, n, buf);
curr_row += rows;

MPI_Send(buf.data(), rows * n, MPI_DOUBLE, dist, 0, MPI_COMM_WORLD);
}
}

void FoxAlgorithmMPI::MultiplyBlocks(int n, int lr, const std::vector<double> &la, const std::vector<double> &lb,
std::vector<double> &lc) {
for (int i = 0; i < lr; ++i) {
for (int k = 0; k < n; ++k) {
const double tmp = la[(static_cast<std::size_t>(i) * n) + k];
for (int j = 0; j < n; ++j) {
lc[(static_cast<std::size_t>(i) * n) + j] += tmp * lb[(static_cast<std::size_t>(k) * n) + j];
}
}
}
}

void FoxAlgorithmMPI::CollectResults(int rk, int sz, int n, int rpp, int rem, int lr, const std::vector<double> &lc) {
if (rk != 0) {
MPI_Send(lc.data(), lr * n, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);
return;
}

CopyBlockToMatrix(lc, lr, n, 0, GetOutput());

int curr_row = lr;

for (int proc = 1; proc < sz; ++proc) {
const int rows = RowsForRank(proc, rpp, rem);
std::vector<double> buf(static_cast<std::size_t>(rows) * n);

MPI_Recv(buf.data(), rows * n, MPI_DOUBLE, proc, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
CopyBlockToMatrix(buf, rows, n, curr_row, GetOutput());

curr_row += rows;
}
}

bool FoxAlgorithmMPI::ValidationImpl() {
int rk = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rk);

if (rk == 0) {
return CheckMatrices(GetInput().first, GetInput().second);
}

return true;
}

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

bool FoxAlgorithmMPI::RunImpl() {
int rk = 0;
int sz = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rk);
MPI_Comm_size(MPI_COMM_WORLD, &sz);

int n = 0;
if (rk == 0) {
n = static_cast<int>(GetInput().first.size());
}

MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);

if (n == 0) {
return false;
}

const int rpp = n / sz;
const int rem = n % sz;
const int lr = RowsForRank(rk, rpp, rem);

std::vector<double> lb(static_cast<std::size_t>(n) * n);
SpreadB(n, lb);

std::vector<double> la(static_cast<std::size_t>(lr) * n);
SpreadA(rk, sz, n, lr, la);

std::vector<double> lc(static_cast<std::size_t>(lr) * n, 0.0);
MultiplyBlocks(n, lr, la, lb, lc);

if (rk == 0) {
GetOutput().assign(static_cast<std::size_t>(n), std::vector<double>(n));
}

CollectResults(rk, sz, n, rpp, rem, lr, lc);
if (rk != 0) {
GetOutput().assign(static_cast<std::size_t>(n), std::vector<double>(n));
}

for (int i = 0; i < n; ++i) {
MPI_Bcast(GetOutput()[i].data(), n, MPI_DOUBLE, 0, MPI_COMM_WORLD);
}
return true;
}

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

} // namespace kutergin_a_fox_algorithm
78 changes: 78 additions & 0 deletions tasks/kutergin_a_fox_algorithm/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Умножение плотных матриц. Элементы типа double. Блочная схема, алгоритм Фокса

Студент: Кутергин Антон Андреевич, группа 3823Б1ФИ1
Технологии: SEQ-MPI
Вариант: 2

## 1. Введение
Умножение матриц является одной из фундаментальных операций в линейной алгебре и высокопроизводительных вычислениях.Целью данной работы является реализация алгоритма Фокса для распределенных систем памяти с использованием стандарта MPI. Ожидаемый результат — корректно работающая программа, демонстрирующая ускорение вычислений при увеличении числа процессов.

## 2. Постановка задачи
Даны две квадратные плотные матрицы размером n на n. Требуется вычислить результирующую матрицу, которая является результатом перемножения этих матриц
*Входные данные: Две матрицы A и B (тип double).
*Выходные данные: Матрица C.
*Ограничения:Количество процессов p должно являться полным квадратом (p = q * q).
Размерность матрицы N должна делиться нацело на размерность решетки процессов q (для упрощения блочного распределения).

## 3. Базовый алгоритм
В качестве базового алгоритма используется классическое матричное умножение с тремя вложенными циклами

## 4. Схема распараллеливания
Для распараллеливания используется Алгоритм Фокса. Процессы организованы в виде двумерной декартовой решетки размером q * q, где q = sqrt(p).
Распределение данных:
Матрицы A, B и C разбиваются на блочные подматрицы размером (N/q) * (N/q). Каждому процессу (row, col) в решетке назначается соответствующий блок A_{row, col}, B_{row, col} и C_{row, col}.
Алгоритм (для каждого процесса):Алгоритм выполняется за q итераций. На этапе k (0 <= k <= q):
*1 Broadcast: Процесс, находящийся в текущей строке решетки на позиции (i, (i+k)%q), рассылает свой блок матрицы A всем процессам в этой же строке.
*2 Multiply: Каждый процесс умножает полученный блок $A$ на свой текущий локальный блок B и добавляет результат в C.
*3 Shift: Блоки матрицы B сдвигаются циклически вверх по столбцам решетки процессов.

## 5. Детали реализации
Реализация выполнена на языке C++ с использованием библиотеки MPI.
*Ключевые файлы:
ops_seq.hpp/cpp: Реализация последовательной версии (FoxAlgorithmSEQ).
ops_mpi.hpp/cpp: Реализация параллельной версии (FoxAlgorithmMPI).
*Особенности реализации:
Используется коммуникатор MPI_COMM_CART для создания топологии решетки.Для рассылки блоков $A$ используется MPI_Bcast по строковым коммуникаторам.
Для сдвига блоков $B$ используется MPI_Sendrecv_replace, что позволяет экономить память, не создавая дополнительных буферов.
Обработка ошибок: Добавлены проверки на то, что N делится на sqrt{p}, и что количество процессов является полным квадратом.

## 6. Экспериментальная установка
- Hardware/OS: Intel Core i5 8300h (4 ядра), 12 GB RAM, Windows 10
- Toolchain: MSVC (Visual Studio 2022), CMake 3.28.1, сборка Release
- Environment: Количество процессов задаётся через `mpiexec -n N` (2, 4, 8 процессов)
- Data: Матрицы генерировались программно, размер матрицы для замеров производительности: 512 * 512.

## 7. Результаты и обсуждение

### 7.1 Корректность

Корректность алгоритма верифицирована с помощью набора из 26 функциональных тестов (Google Test).Проверены сценарии:
- Матрицы малых размеров (1 * 1, 2 * 2, 4 * 4)
- .Специальные матрицы: нулевые, единичные, диагональные, треугольные.
- Проверка точности double.
- Все тесты пройдены успешно.

### 7.2 Производительность

Замеры проводились на матрицах размерностью 512x512.
| Режим | Количество процессов | Время, с | Ускорение | Эффективность |
|-------|---------------------|----------|-----------|---------------|
| seq | 1 | 0.0560 | 1.00 | N/A |
| mpi | 2 | 0.0350 | 1.60 | 80.0% |
| mpi | 4 | 0.0262 | 2.14 | 53.4% |
| mpi | 8 | 0.0446 | 1.26 | 15.7% |

Наилучший результат по времени был достигнут при использовании 4 процессов. При переходе с 4 на 8 процессов время выполнения увеличилось с 0.0262 до 0.0446 сек. Это связано с тем, что для данной размерности матрицы 512^2 накладные расходы на коммуникации MPI (пересылка блоков матриц между узлами) начинают доминировать над временем полезных вычислений. Высокая эффективность на 2 процессах (80%) подтверждает хорошую сбалансированность алгоритма для небольших вычислительных групп.

## 8. Выводы

В ходе выполнения работы был реализован параллельный алгоритм матричного умножения Фокса. Экспериментальные данные показали:

- Алгоритм работает корректно и обеспечивает значительный прирост производительности на малом количестве процессов.
- Оптимальным количеством процессов для задачи размером 512x512 в данной тестовой среде является 4.
- Дальнейшее увеличение числа процессов без увеличения размера матриц нецелесообразно из-за возрастающих затрат на синхронизацию и пересылку данных.

## 9. Список литературы

Лекции по Параллельному программированию.
Практические занятия по Параллельному программированию.
28 changes: 28 additions & 0 deletions tasks/kutergin_a_fox_algorithm/seq/include/ops_seq.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include <vector>

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

namespace kutergin_a_fox_algorithm {

class FoxAlgorithmSEQ : public BaseTask {
public:
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
return ppc::task::TypeOfTask::kSEQ;
}
explicit FoxAlgorithmSEQ(const InType &in);

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

static void MultiplyBlock(int ars, int are, int acs, int ace, int bcs, int bce,
const std::vector<std::vector<double>> &ma, const std::vector<std::vector<double>> &mb,
std::vector<std::vector<double>> &mc);
};

} // namespace kutergin_a_fox_algorithm
Loading
Loading