Skip to content
Closed
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/nikitina_v_max_elem_matr/common/include/common.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include <string>
#include <vector>

#include "task/include/task.hpp"

namespace nikitina_v_max_elem_matr {

// Вход: std::vector<int>, где первые 2 элемента - размеры, остальные - матрица
using InType = std::vector<int>;
// Выход: 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"
}
}
27 changes: 27 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,27 @@
#pragma once

#include <vector>

#include "nikitina_v_max_elem_matr/common/include/common.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;

int rows, cols;
int global_max;
std::vector<int> matrix_;
};

} // namespace nikitina_v_max_elem_matr
92 changes: 92 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,92 @@
#include "nikitina_v_max_elem_matr/mpi/include/ops_mpi.hpp"

#include <mpi.h>

#include <algorithm>
#include <climits>
#include <stdexcept>
#include <vector>

namespace nikitina_v_max_elem_matr {

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

bool MaxElementMatrMPI::ValidationImpl() {
const auto &in_ = GetInput();
if (in_.size() < 2) {
return false;
}
rows = in_[0];
cols = in_[1];
if (rows < 0 || cols < 0 || static_cast<size_t>(rows * cols) != in_.size() - 2) {
return false;
}
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(rows * cols);
std::copy(in_.begin() + 2, in_.end(), std::back_inserter(matrix_));
}
}
return true;
}

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

if (rows * cols == 0) {
global_max = INT_MIN;
} else {
const int total_elements = rows * cols;
const int elements_per_proc = total_elements / world_size;
const int remainder_elements = total_elements % world_size;

std::vector<int> sendcounts(world_size);
std::vector<int> displs(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];
}

std::vector<int> recv_buf(sendcounts[rank]);

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

int local_max = (recv_buf.empty()) ? INT_MIN : recv_buf[0];
for (int val : recv_buf) {
if (val > local_max) {
local_max = val;
}
}

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.
27 changes: 27 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,27 @@
#pragma once

#include <vector>

#include "nikitina_v_max_elem_matr/common/include/common.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, cols;
int max_val;
std::vector<int> matrix_;
};

} // namespace nikitina_v_max_elem_matr
57 changes: 57 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,57 @@
#include "nikitina_v_max_elem_matr/seq/include/ops_seq.hpp"

#include <algorithm>
#include <climits>
#include <stdexcept>

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];
if (rows < 0 || cols < 0 || static_cast<size_t>(rows * cols) != in_.size() - 2) {
return false;
}
return true;
}

bool MaxElementMatrSEQ::PreProcessingImpl() {
const auto &in_ = GetInput();
matrix_.clear();
if (rows > 0 && cols > 0) {
matrix_.reserve(rows * cols);
std::copy(in_.begin() + 2, in_.end(), std::back_inserter(matrix_));
}
max_val = INT_MIN;
return true;
}

bool MaxElementMatrSEQ::RunImpl() {
if (matrix_.empty()) {
max_val = INT_MIN;
return true;
}
max_val = matrix_[0];
for (size_t i = 1; i < matrix_.size(); ++i) {
if (matrix_[i] > max_val) {
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