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
17 changes: 17 additions & 0 deletions tasks/savva_d_min_elem_vec/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 <vector>

#include "task/include/task.hpp"

namespace savva_d_min_elem_vec {

using InType = std::vector<int>;
using OutType = int;
using TestType = std::tuple<std::vector<int>, std::string>; // нужен как вспомогательный тип, чтобы было удобно
// проводить тесты (например хранит путь к файлу)
using BaseTask = ppc::task::Task<InType, OutType>;

} // namespace savva_d_min_elem_vec
3 changes: 3 additions & 0 deletions tasks/savva_d_min_elem_vec/data/data.txt

Large diffs are not rendered by default.

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

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

namespace savva_d_min_elem_vec {

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

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

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

#include <mpi.h>

#include <algorithm>
#include <limits>
#include <vector>

#include "savva_d_min_elem_vec/common/include/common.hpp"

namespace savva_d_min_elem_vec {

SavvaDMinElemVecMPI::SavvaDMinElemVecMPI(const InType &in) { // эта функция не изменяется в задачах
SetTypeOfTask(GetStaticTypeOfTask()); // конструктор правильная постановка задачи
GetInput() = in; // GetInput() нужен чтобы больше не использовать сами данные in
GetOutput() = 0;
}

bool SavvaDMinElemVecMPI::ValidationImpl() {
return (!GetInput().empty()) && (GetOutput() == 0);
}

bool SavvaDMinElemVecMPI::PreProcessingImpl() {
return true;
Comment thread
allnes marked this conversation as resolved.
}

bool SavvaDMinElemVecMPI::RunImpl() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no data scatter from rank 0 to other ranks

int rank = 0;
int size = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
int local_n = 0;
int global_n = 0;
int *local_data = nullptr;
const int *global_data = nullptr;
int *counts = new int[size]();
int *displacements = new int[size]();

// если вектор - пустой, то false
if (rank == 0) {
global_data = GetInput().data();
global_n = static_cast<int>(GetInput().size());
int elements_per_proc = global_n / size;
int remainder = global_n % size;
int offset = 0;

for (int i = 0; i < size; ++i) {
counts[i] = elements_per_proc + (i < remainder ? 1 : 0);
displacements[i] = offset;
offset += counts[i];
}
}

MPI_Bcast(&global_n, 1, MPI_INT, 0, MPI_COMM_WORLD);
if (global_n == 0) {
delete[] counts;
delete[] displacements;
return false;
}
MPI_Bcast(counts, size, MPI_INT, 0, MPI_COMM_WORLD);
local_data = new int[counts[rank]];
local_n = counts[rank];
MPI_Scatterv(global_data, counts, displacements, MPI_INT, local_data, local_n, MPI_INT, 0, MPI_COMM_WORLD);

int local_min = std::numeric_limits<int>::max();
for (int i = 0; i < local_n; ++i) {
local_min = std::min(local_data[i], local_min);
}

int global_min = 0;
MPI_Allreduce(&local_min, &global_min, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD);

GetOutput() = global_min;

delete[] counts;
delete[] displacements;
delete[] local_data;

// Синхронизация
MPI_Barrier(MPI_COMM_WORLD);

return true;
}

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

} // namespace savva_d_min_elem_vec
108 changes: 108 additions & 0 deletions tasks/savva_d_min_elem_vec/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Минимальный элемент вектора

- Студент: Савва Дария Александровна, 3823Б1ФИ1
- Технологии: SEQ | MPI
- Вариант: 4

## 1. Введение
Целью данной работы является реализация алгоритма нахождения минимального элемента вектора. Алгоритм должен иметь последовательную и параллельную версии.

## 2. Постановка задачи
Дан вектор составленный из N элементов, элементами являются числа. Среди данных элементов необходимо найти элемент, имеющий минимальное значение.

## 3. Описание линейного алгоритма

Последовательная версия:
осуществляется последовательный проход по всем элементам вектора, на каждом шаге сравнивается значение текущего элемента с элементом, который является минимальным на данный момент.


## 4. Описание схемы параллельного алгоритма
Пусть имеется K процессов и вектор размера N. Если N кратно K, вектор делится на K равных частей. Каждая часть обратывается соответсвующим процессом.
Локальная обработка части вектора - это последовательный поиск минимума в данной части вектора. Если N не кратно K, каждый процесс получает S элементов,
где S - результат целочисленного деления N на K, и первые M процессов получают на один элемент больше, где M - остаток от деления N на K.
Таким образом, вектор делится процессами на примерно равные части, каждая из которых локально обрабатывается своим процессом, что обеспечивает максимальную эффективность алгоритма.
Когда локальная обработка процессами завершена, результат пересылается на нулевой процесс, на котором вычисляется минимум из локальных минимумов. Затем осуществляется рассылка
результата на другие процессы.


## Описание программной реализации параллельного алгоритма
Реализация выполнена на языке C++ с использованием библиотеки MPI.

Основные этапы:
- Инициализация и валидация входных данных;
- Определение диапазона элементов, обрабатываемых каждым процессом;
- Локальное вычисление минимума;
- Сбор частичных результатов между всеми процессами, нахождение и пересылка результата на каждый процесс при помощи MPI-функции `MPI_Allreduce`;
- Возврат итогового значения каждым процессом.
### Ключевые функции:
- `MPI_Comm_rank`, `MPI_Comm_size` — определяют номер процесса и общее количество процессов;
- `MPI_Allreduce` — собирает результаты со всех процессов и осуществляет над ними итоговую операцию поиска минимума, рассылая итоговый результат на все процессы.
### Валидация данных
Не допускается пустой вектор в качестве входных данных, поскольку в нём невозможно опеределить минимальный элемент.


## Результаты экспериментов
### Условия экспериментов
- Размеры векторов: \(10^7\) , \(5*10^7\) , \(10^9\) элементов.
- Среда выполнения: Windows, MPI (4 процесса).
- Измерение времени проводилось встроенными средствами тестового фреймворка GoogleTest.
---
### Результаты при размере вектора \(10^7\)
| Режим выполнения | Число процессов | Время (сек) | Ускорение | Эффективность, % |
|:-----------------|-------------:|-----------------:|:-------------|-------------|
| SEQ | 1 | 9.15 | 1.00 | |
| MPI | 2 | 9.1 | 1.01 | 25,2 |
| MPI | 4 | 12.5 | 0.99 | 24,7 |
| MPI | 8 | 24.9 | 0.99 | 12,3 |
**Вывод:** наибольшее ускорение достигается при 2 процессах, что не ожидалось, поскольку процессор имеет 4 ядра, возможно это связано с тем что при 2 процессах меньше накладные расходы на коммуникацию
---
### Результаты при размере вектора \(5*10^7\)
| Режим выполнения | Число процессов | Время (сек) | Ускорение | Эффективность, % |
|:-----------------|-------------:|-----------------:|:-------------|-------------|
| SEQ | 1 | 44.5 | 1.00 | |
| MPI | 2 | 44.8 | 0.99 | 12,3 |
| MPI | 4 | 60.6 | 1.01 | 25,2 |
| MPI | 8 | 24.9 | 0.99 | 12,3 |
**Вывод:** как ожидалось, наибольшее ускорение достигается на 4 процессах для 4-ядерного процессора, ускорение малое
---
### Результаты при размере вектора \(10^9\)
| Режим выполнения | Число процессов | Время (сек) | Ускорение | Эффективность, % |
|:-----------------|-------------:|-----------------:|:-------------|-------------|
| SEQ | 1 | 91 | 1.00 | |
| MPI | 2 | 98 | 0.92 | 11,6 |
| MPI | 4 | 128 | 0.99 | 12,3 |
**Вывод:** на очень больших данных происходит замедление при использовании параллельной версии, что не оправдало ожидания (ожидалось, что при больших данных распараллеливание будет эффективнее). Возможно, это связано с ограниченностью кэшированной памяти, что усложняет распараллеливание на больших данных.
## Подтверждение корректности
Были выбраны функциональные тесты на следующих входных данных, предполагающие различные сценарии работы алгоритмов:

1. Малый вектор {3, 1, 4, 1, 5}
2. Вектор с единстенным минимальным элементом в середине {9, 2, 6, 1, 8, 3, 7}
3. Вектор со смешанными числами {5, 4, 3, 2, 1, 0, -1, -2}
4. Вектор с повторяющимися отрицательными значениями {-5, -55, -5, -3, -100000, -111111, -9, -111111}
5. Вектор из одного элемента {7}

Результаты последовательной и параллельной версий совпали со значением, возвращаемым стандартной функцией std::min_element, следовательно, оба алгоритма работают корректно.

---
## Выводы
1. Реализованы последовательная (SEQ) и параллельная (MPI) версии алгоритма поиска минимального элемента в векторе.
2. Тестовая инфраструктура обеспечивает комплексную проверку корректности обеих реализаций алгоритма с точки зрения функциональности и производительности при различных размерах тестовых данных.
3. Предполагалось, что параллельная MPI-реализация эффективно распределяет вычисления между процессами, используя стратегию блочного распределения данных с учетом остатка для балансировки нагрузки.
Ожидания не оправдались, так как ускорение удалось получить небольшое и лишь при определённом размере входных данных (больше млн и меньше млр элементов). Вероятно, это связано с
накладными расходами на параллельные вычисленения и некоторыми техническими особенностями реализации параллельного алгоритма. Алгоритм может быть модернизирован для получения большего ускорения, так как в данный момент
использование параллельных вычислений не оправдывает затрат на его реализацию.
---
## Заключение.
В ходе лабораторной работы был реализован и протестирован алгоритм нахождения минимума вектора с использованием технологии MPI. Проведён сравнительный анализ с последовательной версией, подтверждена корректность вычислений и измерено ускорение. Было так же показано, что MPI версия при достаточно больших данных имеет ускорение по сравнению с версией SEQ.
---
## Список литературы
1. Документация по OpenMPI — https://www.open-mpi.org/doc/
2. cppreference.com - https://en.cppreference.com/
3. Лекции по параллельному программированию ННГУ
---






22 changes: 22 additions & 0 deletions tasks/savva_d_min_elem_vec/seq/include/ops_seq.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

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

namespace savva_d_min_elem_vec {

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

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

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

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

#include "savva_d_min_elem_vec/common/include/common.hpp"

namespace savva_d_min_elem_vec {

SavvaDMinElemVecSEQ::SavvaDMinElemVecSEQ(const InType &in) {
SetTypeOfTask(GetStaticTypeOfTask());
GetInput() = in;
GetOutput() = 0;
}

bool SavvaDMinElemVecSEQ::ValidationImpl() {
return (!GetInput().empty()) && (GetOutput() == 0);
}

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

bool SavvaDMinElemVecSEQ::RunImpl() {
if (GetInput().empty()) {
return false;
}

int min_val = GetInput()[0];
for (size_t i = 1; i < GetInput().size(); ++i) {
min_val = std::min(GetInput()[i], min_val);
}
GetOutput() = min_val;

return true;
}

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

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