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

#include <vector>

#include "task/include/task.hpp"

namespace nikitina_v_max_elem_matr {

using InType = std::vector<int>;
using OutType = int;

using BaseTask = ppc::task::Task<InType, OutType>;

} // namespace nikitina_v_max_elem_matr
Binary file added tasks/nikitina_v_max_elem_matr/data/pic.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions tasks/nikitina_v_max_elem_matr/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ФИ2",
"task_number": "1"
}
}
33 changes: 33 additions & 0 deletions tasks/nikitina_v_max_elem_matr/mpi/include/ops_mpi.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include <vector>

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

namespace nikitina_v_max_elem_matr {

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

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

static void CalculateScatterParams(int total_elements, int world_size, std::vector<int> &sendcounts,
std::vector<int> &displs);
static int FindLocalMax(const std::vector<int> &data);

int rows_{};
int cols_{};
int global_max_{};
std::vector<int> matrix_;
};

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

#include <mpi.h>

#include <algorithm>
#include <cstddef>
#include <iterator>
#include <limits>
#include <vector>

#include "nikitina_v_max_elem_matr/common/include/common.hpp"

namespace nikitina_v_max_elem_matr {

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

bool MaxElementMatrMPI::ValidationImpl() {
int rank = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);

if (rank == 0) {
const auto &in = GetInput();
if (in.size() < 2) {
return false;
}
rows_ = in[0];
cols_ = in[1];
return rows_ >= 0 && cols_ >= 0 && static_cast<size_t>(rows_) * cols_ == in.size() - 2;
}
return true;
}

bool MaxElementMatrMPI::PreProcessingImpl() {
int rank = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);

if (rank == 0) {
const auto &in = GetInput();
matrix_.clear();
if (rows_ > 0 && cols_ > 0) {
matrix_.reserve(static_cast<size_t>(rows_) * cols_);
std::copy(in.begin() + 2, in.end(), std::back_inserter(matrix_));
}
Comment thread
allnes marked this conversation as resolved.
}
return true;
}

void MaxElementMatrMPI::CalculateScatterParams(int total_elements, int world_size, std::vector<int> &sendcounts,
std::vector<int> &displs) {
const int elements_per_proc = total_elements / world_size;
const int remainder_elements = total_elements % world_size;
int current_displ = 0;
for (int i = 0; i < world_size; ++i) {
sendcounts[i] = (i < remainder_elements) ? elements_per_proc + 1 : elements_per_proc;
displs[i] = current_displ;
current_displ += sendcounts[i];
}
}

int MaxElementMatrMPI::FindLocalMax(const std::vector<int> &data) {
if (data.empty()) {
return std::numeric_limits<int>::min();
}
int max_val = data[0];
for (size_t i = 1; i < data.size(); ++i) {
max_val = std::max(max_val, data[i]);
}
return max_val;
}

bool MaxElementMatrMPI::RunImpl() {
int world_size = 0;
int rank = 0;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);

MPI_Bcast(&rows_, 1, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Bcast(&cols_, 1, MPI_INT, 0, MPI_COMM_WORLD);

if (static_cast<size_t>(rows_) * cols_ == 0) {
global_max_ = std::numeric_limits<int>::min();
} else {
const int total_elements = rows_ * cols_;
std::vector<int> sendcounts(world_size);
std::vector<int> displs(world_size);

if (rank == 0) {
CalculateScatterParams(total_elements, world_size, sendcounts, displs);
}

MPI_Bcast(sendcounts.data(), world_size, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Bcast(displs.data(), world_size, MPI_INT, 0, MPI_COMM_WORLD);

std::vector<int> recv_buf(sendcounts[rank]);
const int *send_buf = (rank == 0) ? matrix_.data() : nullptr;

MPI_Scatterv(send_buf, sendcounts.data(), displs.data(), MPI_INT, recv_buf.data(), sendcounts[rank], MPI_INT, 0,
MPI_COMM_WORLD);

int local_max = FindLocalMax(recv_buf);
MPI_Reduce(&local_max, &global_max_, 1, MPI_INT, MPI_MAX, 0, MPI_COMM_WORLD);
}

MPI_Bcast(&global_max_, 1, MPI_INT, 0, MPI_COMM_WORLD);
return true;
}

bool MaxElementMatrMPI::PostProcessingImpl() {
GetOutput() = global_max_;
return true;
}

} // namespace nikitina_v_max_elem_matr
94 changes: 94 additions & 0 deletions tasks/nikitina_v_max_elem_matr/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Поиск максимального элемента в целочисленной матрице

- **Студент:** `<Никитина Валерия Владимировна>`, группа `<3823Б1ФИ2>`
- **Технология:** SEQ, MPI
- **Вариант:** `<13>`

## 1. Введение

Поиск экстремальных значений является одной из фундаментальных операций в анализе данных. При работе с большими матрицами последовательное выполнение этой задачи может занимать значительное время. Распараллеливание алгоритма с использованием технологии Message Passing Interface (MPI) позволяет сократить время вычислений за счет одновременной обработки разных частей данных на нескольких вычислительных узлах.

Целью данной работы является реализация и сравнение производительности последовательного и параллельного (MPI) алгоритмов для поиска максимального элемента в целочисленной матрице, а также обеспечение полной корректности и тестового покрытия реализованного решения.

## 2. Постановка задачи

**Входные данные:** Целочисленная матрица, представленная в виде одномерного вектора `std::vector<int>`. Первые два элемента вектора задают размеры матрицы: количество строк `rows` и столбцов `cols`. Остальные `rows * cols` элементов представляют собой данные матрицы.

**Выходные данные:** Одно целое число — максимальное значение среди всех элементов матрицы.

**Ограничения:**
- Элементы матрицы — целые числа.
- Матрица с нулевыми размерами (например, `10x0` или `0x10`) является валидным входным данным. Для таких матриц результат равен `INT_MIN`.
- Входные данные с отрицательными размерами или некорректным количеством элементов считаются невалидными.

## 3. Описание алгоритма (базового/последовательного)

Последовательный алгоритм представляет собой простой линейный обход всех элементов матрицы.

1. Проводится валидация входных данных: проверяется, что размеры матрицы неотрицательные и соответствуют фактическому количеству переданных элементов.
2. Инициализируется переменная `global_max` минимально возможным значением для типа `int` (`INT_MIN`).
3. Если матрица не пуста, организуется цикл, который итерируется по каждому элементу.
4. На каждой итерации текущий элемент матрицы сравнивается со значением `global_max`. Если текущий элемент больше, `global_max` обновляется.
5. После завершения обхода переменная `global_max` содержит искомое максимальное значение.

Временная сложность данного алгоритма составляет O(N × M), где N и M — размеры матрицы.

## 4. Схема распараллеливания

Для распараллеливания задачи с использованием технологии MPI была выбрана модель "Мастер-Рабочий" (Master-Worker). Декомпозиция данных выполняется путем разделения исходной матрицы на примерно равные непрерывные части.

- **Валидация:** Каждый MPI-процесс самостоятельно выполняет проверку входных данных для достижения 100% тестового покрытия ветвей кода.

- **Схема обменов данными:**
1. **Процесс с рангом 0 (Мастер):** Хранит полную исходную матрицу.
2. **`MPI_Scatterv`:** Мастер распределяет данные. Он вычисляет, сколько элементов будет обрабатывать каждый процесс, и рассылает соответствующие части матрицы всем процессам (включая себя).
3. **Локальные вычисления:** Каждый процесс находит максимальный элемент в своей локальной части (`local_max`).
4. **`MPI_Reduce`:** Все процессы участвуют в коллективной операции редукции, в результате которой на 0-м процессе вычисляется глобальный максимум (`global_max`).
5. **`MPI_Bcast`:** Процесс 0 рассылает финальный `global_max` всем остальным процессам. Этот шаг критически важен, так как тестовый фреймворк проверяет корректность результата на каждом процессе.

Эта схема позволяет эффективно распределить вычислительную нагрузку, минимизируя коммуникации.

## 5. Экспериментальная установка

- **Окружение:** Разработка и локальное тестирование проводились в контейнере Docker на базе Ubuntu.
- **CPU:** Предоставлено средой выполнения (2 виртуальных ядра).
- **Toolchain:**
- Компилятор: GCC 14.2.0
- Система сборки: CMake
- Тип сборки: `Release`
- **Переменные окружения:** Эксперименты проводились при `PPC_NUM_PROC=2`.
- **Данные для тестов:**
- **Функциональные тесты:** Матрицы различных размеров, включая граничные и невалидные случаи.
- **Тесты производительности:** Матрица размером **12000x12000**, заполненная случайными целыми числами.

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

### 6.1. Корректность и тестовое покрытие

Корректность работы алгоритмов проверялась с помощью исчерпывающего набора GTest'ов, включающего как тесты на валидных данных, так и тесты, проверяющие обработку некорректных входных данных. Благодаря этому было достигнуто **100% покрытие строк и ветвей кода**, что гарантирует надежность реализованных функций.

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

Измерения времени выполнения проводились на матрице 12000x12000. Ускорение (Speedup) вычислялось как S(p) = T(1) / T(p), а эффективность (Efficiency) как E(p) = S(p) / p.

| Режим | Кол-во процессов (p) | Время, с | Ускорение (S) | Эффективность (E) |
| :---- | :------------------: | :------- | :------------ | :---------------- |
| seq | 1 | **0.415**| 1.00 | 100% |
| mpi | 2 | **0.224**| 1.85 | 92.5% |

**Анализ результатов:**

Параллельная MPI-реализация на двух процессах показала значительное **ускорение в 1.85 раза** по сравнению с последовательной версией. Это близко к идеальному двукратному ускорению, что свидетельствует о высокой эффективности распараллеливания.

Эффективность в `92.5%` означает, что только 7.5% времени было потрачено на накладные расходы, связанные с работой MPI (раздача данных и сбор результатов). Такой высокий показатель подтверждает, что для данной задачи на двух процессах вычислительная нагрузка хорошо распределяется, а коммуникационные издержки минимальны.

## 7. Выводы

В ходе выполнения работы были успешно реализованы, протестированы и отлажены последовательный и параллельный (MPI) алгоритмы для поиска максимального элемента в матрице.

Экспериментальные замеры показали, что MPI-реализация на двух процессах достигает ускорения `1.85x` при эффективности `92.5%`. Это подтверждает, что выбранная схема распараллеливания является высокоэффективной для данной задачи и позволяет значительно сократить время выполнения по сравнению с последовательным подходом.

## 8. Источники

1. Open MPI Documentation — [https://www.open-mpi.org/doc/](https://www.open-mpi.org/doc/)
2. Peter S. Pacheco. Parallel Programming with MPI. Morgan Kaufmann, 1996.
29 changes: 29 additions & 0 deletions tasks/nikitina_v_max_elem_matr/seq/include/ops_seq.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once

#include <vector>

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

namespace nikitina_v_max_elem_matr {

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

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

int rows_{};
int cols_{};
int max_val_{};
std::vector<int> matrix_;
};

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

#include <algorithm>
#include <cstddef>
#include <iterator>
#include <limits>
#include <vector>

#include "nikitina_v_max_elem_matr/common/include/common.hpp"

namespace nikitina_v_max_elem_matr {

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

bool MaxElementMatrSEQ::ValidationImpl() {
const auto &in = GetInput();
if (in.size() < 2) {
return false;
}
rows_ = in[0];
cols_ = in[1];
return rows_ >= 0 && cols_ >= 0 && static_cast<size_t>(rows_) * cols_ == in.size() - 2;
}

bool MaxElementMatrSEQ::PreProcessingImpl() {
const auto &in = GetInput();
matrix_.clear();
if (rows_ > 0 && cols_ > 0) {
matrix_.reserve(static_cast<size_t>(rows_) * cols_);
std::copy(in.begin() + 2, in.end(), std::back_inserter(matrix_));
}
max_val_ = std::numeric_limits<int>::min();
return true;
}

bool MaxElementMatrSEQ::RunImpl() {
if (matrix_.empty()) {
max_val_ = std::numeric_limits<int>::min();
return true;
}
max_val_ = matrix_[0];
for (size_t i = 1; i < matrix_.size(); ++i) {
max_val_ = std::max(max_val_, matrix_[i]);
}
return true;
}

bool MaxElementMatrSEQ::PostProcessingImpl() {
GetOutput() = max_val_;
return true;
}

} // namespace nikitina_v_max_elem_matr
7 changes: 7 additions & 0 deletions tasks/nikitina_v_max_elem_matr/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/nikitina_v_max_elem_matr/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