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/kutergin_a_closest_pair/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 kutergin_a_closest_pair {

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

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

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

namespace kutergin_a_closest_pair {

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

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

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

#include <mpi.h>

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

#include "kutergin_a_closest_pair/common/include/common.hpp"

namespace kutergin_a_closest_pair {

KuterginAClosestPairMPI::KuterginAClosestPairMPI(const InType &in) {
SetTypeOfTask(GetStaticTypeOfTask());
GetInput() = in;
GetOutput() = -1;
}

bool KuterginAClosestPairMPI::ValidationImpl() {
return true;
}

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

namespace {

std::vector<int> DistributeData(int rank, int size, int n, const std::vector<int> &v) {
int local_size = n / size;
int remainder = n % size;

int start = (rank * local_size) + std::min(rank, remainder);
int end = start + local_size + (rank < remainder ? 1 : 0);

if (rank == size - 1) {
end = n;
}

std::vector<int> local_data(end - start);
if (rank == 0) {
std::copy(v.begin() + start, v.begin() + end, local_data.begin());
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.

I think this copy will ruin your performance even though it is not really needed


for (int i = 1; i < size; ++i) {
int other_start = (i * local_size) + std::min(i, remainder);
int other_end = other_start + local_size + (i < remainder ? 1 : 0);
if (i == size - 1) {
other_end = n;
}

MPI_Send(v.data() + other_start, other_end - other_start, MPI_INT, i, 0, MPI_COMM_WORLD);
}
} else {
MPI_Recv(local_data.data(), static_cast<int>(local_data.size()), MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
}

return local_data;
}

int FindLocalMin(const std::vector<int> &local_data, int start_idx, int &found_idx) {
int local_min = std::numeric_limits<int>::max();
found_idx = -1;

for (int i = 0; i < static_cast<int>(local_data.size()) - 1; ++i) {
int diff = std::abs(local_data[i + 1] - local_data[i]);
if (diff < local_min) {
local_min = diff;
found_idx = start_idx + i;
}
}

return local_min;
}

int CalculateStartIndex(int rank, int size, int n) {
int local_size = n / size;
int remainder = n % size;
return (rank * local_size) + std::min(rank, remainder);
}

int CalculateEndIndex(int rank, int size, int n) {
int local_size = n / size;
int remainder = n % size;
int end = CalculateStartIndex(rank, size, n) + local_size + (rank < remainder ? 1 : 0);
if (rank == size - 1) {
end = n;
}
return end;
}

int CheckBoundary(int rank, int size, int end, int n, const std::vector<int> &v, const std::vector<int> &local_data,
int current_min, int &current_idx) {
if (rank < size - 1 && end < n) {
int boundary_diff = std::abs(v[end] - local_data.back());
if (boundary_diff < current_min) {
current_min = boundary_diff;
current_idx = end - 1;
}
}
return current_min;
}

} // namespace

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

const auto &v = GetInput();
int n = static_cast<int>(v.size());

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

if (n < 2) {
GetOutput() = -1;
return true;
}

auto local_data = DistributeData(rank, size, n, v);
if (local_data.empty()) {
GetOutput() = -1;
return true;
}

int start_idx = CalculateStartIndex(rank, size, n);
int local_idx = -1;
int local_min = FindLocalMin(local_data, start_idx, local_idx);

int end = CalculateEndIndex(rank, size, n);
local_min = CheckBoundary(rank, size, end, n, v, local_data, local_min, local_idx);

struct MinIndex {
int val = 0;
int idx = -1;
};

MinIndex local_result;
local_result.val = local_min;
local_result.idx = local_idx;

MinIndex global_result;

MPI_Allreduce(&local_result, &global_result, 1, MPI_2INT, MPI_MINLOC, MPI_COMM_WORLD);

GetOutput() = global_result.idx;
return true;
}

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

} // namespace kutergin_a_closest_pair
52 changes: 52 additions & 0 deletions tasks/kutergin_a_closest_pair/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Нахождение наиболее близких соседних элементов вектора

- Student: Кутергин Антон Андреевич, group 3823Б1ФИ1
- Technology: SEQ | MPI
- Variant: 7

## 1 Введение

Нужно было реализовать параллельный и последовательный алгоритм для поиска наиболее близких соседних элементов вектора

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

На вход подается вектор v, нужно найти такой индекс i, что велечина |v[i] - v[i+1]| минимальна среди всех пар элемента

## 3. Baseline Algorithm (Sequential)

Последовательно просматривает пары v[i], v[i+1], сравнивает разности и возвращает индекс максимума

## 4. Parallelization Scheme

Вектор разбивается на MPI процессы, корректно обрабатывая границы между процессами, чтобы последнему числу этого процесса и первому следующего процесса также образуют пару. Каждый ищет свой локальный минимум среди всех своих соседних пар. Возвращаем глобальный индекс пары с помощью MPI_Reduce

## 6. Experimental Setup

-HARDWARE/OS: CPU - Intel Core i5-8300H, cores/threads - 4/8, RAM - 12gb, OS - Windows 10
TOOLCHAIN: g++ 13.3.0, build type - Release


## 7. Results and Discussion

### 7.1 Correctness

Функциональные тесты: вектор может принимать как положительные, так и отрицательные значения или содержать и положительные и отрицательные значения, так же обрабатывается случай, когда вектор пуст или у него одно значение и пару ему не найти.
MPI версия запускалась на 4 ядрах - эталонное ускорение в 4 раза.
Тест на производительность: генерация n чисел и запуск алгоритмов, чтобы узнать время выполнения.

### 7.2 Performance

| Размер данных |Кол-во процессов| MPI версия (сек) | SEQ версия (сек) |
|---------------|----------------|------------------------|-------------------------|
| 100,000,000 | 2 | 0.1726183200 | 0.2780640800 |
| 100,000,000 | 4 | 0.1601058600 | 0.3270964400 |
| 100,000,000 | 8 | 0.1926481400 | 0.3761903000 |


## 8. Conclusions

В ходе работы были разработаны последовательная(SEQ) и параллельная (MPI) версии алгоритма поиска пары соседних элементов с минимальной разностью. Оба варианта корректно проходят функциональные тесты, включая случаи с отрицательными значениями, смешанными диапазонами чисел и граничными условиями (пустой или одноэлементный вектор). MPI-распараллеливание показало ожидаемый выигрыш на 2 и 4 процессах относительно последовательной версии, что подтверждает эффективность алгоритма. При увелечении числа процессов до 8 наблюдается просадка из-за роста накладных расходов и нехваткой физических ядер, что привело к увелечению время выполнения. В целом работа выполнена успешно: достигнута корректность, показано преимущество MPI версии над SEQ.

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

#include <vector>

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

namespace kutergin_a_closest_pair {

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

private:
std::vector<int> data_;
bool ValidationImpl() override;
bool PreProcessingImpl() override;
bool RunImpl() override;
bool PostProcessingImpl() override;
};

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

#include <algorithm>
#include <cstdlib>
#include <ranges>
#include <vector>

#include "kutergin_a_closest_pair/common/include/common.hpp"

namespace kutergin_a_closest_pair {

KuterginAClosestPairSEQ::KuterginAClosestPairSEQ(const InType &in) {
SetTypeOfTask(GetStaticTypeOfTask());
GetInput() = in;
GetOutput() = -1;
}

bool KuterginAClosestPairSEQ::ValidationImpl() {
return true;
}

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

bool KuterginAClosestPairSEQ::RunImpl() {
const auto &v = GetInput();

if (v.size() < 2) {
GetOutput() = -1;
return true;
}

auto idx_range = std::views::iota(size_t{0}, v.size() - 1);

auto comparator = [&](size_t i, size_t j) {
int diff_i = std::abs(v[i] - v[i + 1]);
int diff_j = std::abs(v[j] - v[j + 1]);
return (diff_i < diff_j) || (diff_i == diff_j && i < j);
};

auto min_it = std::ranges::min_element(idx_range, comparator);
int min_idx = static_cast<int>(*min_it);

GetOutput() = min_idx;
return true;
}

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

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