Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
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
16 changes: 16 additions & 0 deletions tasks/khruev_a_min_elem_vec/common/include/common.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

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

#include "task/include/task.hpp"

namespace khruev_a_min_elem_vec {
Comment thread
allnes marked this conversation as resolved.

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

} // namespace khruev_a_min_elem_vec
9 changes: 9 additions & 0 deletions tasks/khruev_a_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/khruev_a_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 "khruev_a_min_elem_vec/common/include/common.hpp"
#include "task/include/task.hpp"

namespace khruev_a_min_elem_vec {

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

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

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

#include <mpi.h>

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

#include "khruev_a_min_elem_vec/common/include/common.hpp"

namespace khruev_a_min_elem_vec {

KhruevAMinElemVecMPI::KhruevAMinElemVecMPI(const InType &in) {
Comment thread
allnes marked this conversation as resolved.
SetTypeOfTask(GetStaticTypeOfTask()); // mpi scoreboard
GetInput() = in; // dannie doljna bit vidna vsem func rodytelya and stabilizaciya
GetOutput() = 0;
}

bool KhruevAMinElemVecMPI::ValidationImpl() { // input check
return GetOutput() == 0;
}

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

bool KhruevAMinElemVecMPI::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.

Data is not being scattered from rank 0 to other ranks

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Не уверен, что правильно понял вас. Вы имеете в виду, что программа в принципе должна быть так написана или про мою прошлую реализацию? Если второе, то я ведь уже поправил report.md и там актуальное описание.

const auto &input = GetInput();
if (input.empty()) {
GetOutput() = INT_MAX;
return true;
}

int rank = 0;
int size = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);

int n = static_cast<int>(input.size());
int int_part = n / size;
int remainder = n % size;

std::vector<int> sendcounts(size);
std::vector<int> displs(size);

for (int i = 0; i < size; ++i) {
sendcounts[i] = int_part + (i < remainder ? 1 : 0);
displs[i] = (i == 0 ? 0 : displs[i - 1] + sendcounts[i - 1]);
}

std::vector<int> local_chunk(sendcounts[rank] > 0 ? sendcounts[rank] : 1);
MPI_Scatterv(input.data(), sendcounts.data(), displs.data(), MPI_INT,
sendcounts[rank] > 0 ? local_chunk.data() : nullptr, sendcounts[rank], MPI_INT, 0, MPI_COMM_WORLD);

int local_min = INT_MAX;
if (sendcounts[rank] > 0) {
local_min = *std::min_element(local_chunk.begin(), local_chunk.begin() + sendcounts[rank]);
}

int global_min = 0;

MPI_Reduce(&local_min, &global_min, 1, MPI_INT, MPI_MIN, 0, MPI_COMM_WORLD);
MPI_Bcast(&global_min, 1, MPI_INT, 0, MPI_COMM_WORLD);

GetOutput() = global_min;

return true;
}

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

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

- Student: Хруев Антон Олегович, group 3823Б1ФИ1
- Technology: SEQ | MPI
- Variant: 4

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

## 2. Постановка задачи
Есть вектор $V$ состоящий из $n$ элементов, необходимо найти наименьший его элемент $a$.


## 3. Baseline Algorithm (Sequential)
Последовательно в цикле перебираем элементы вектора $V$, сравнивая минимум с текущим элементом и если элемент меньше, то обновляем минимум. После $n$ итераций гарантировано находим $a$.

## 4. Parallelization Scheme
- Разделим вектор $V$ на $k$ частей, где $k$ - количество процессов. Каждому процессу отправим данные из 0-го с помощью функции ScatterV. Предварительно определяется какую часть вектора возьмет каждый из процессов. Для этого инициализируется 2 вектора: **sendcounts**, который определяет сколько элементов получит процесс с индексом $i$ (это количество определяется с учетом остатка $n$ % $k$ = $r$, который распределяется равномерно между процессами с рангом меньшим $r$); **displs**, определяющий смещение. После нахождения локальных минимумов, вызовем функцию MPI_Reduce, которая найдет минимум среди локальных и отправит результат 0-му процессу.

## 5. Experimental Setup
- Hardware/OS: CPU model - AMD Ryzen 3 7320U, cores/threads - 4/2, RAM=8GB, OS version - Ubuntu 24.04.2 LTS
- Toolchain: compiler - g++, version - g++ 13.3.0, build type - Release



## 6. Результаты

### 6.1 Корректность
**Функциональные тесты.** $V$ может состоять из: положительных, отрицательных, одинаковых чисел.
Так же может содержать один или несколько отрицательных чисел среди положительных или не содержать чисел вовсе.
Параллельная реализация алгоритма запускалась на 4 ядрах, поэтому эталонное усокрение - 4 раза.
**Тест на производительность.** Генерация n чисел в порядке возрастания.

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

| Размер данных (n) | SEQ версия (с) | MPI версия (с) | Эффективность |
|-------------|-------|---------|---------|
| 100 | 0.000005 | 0.000225 | 0.02× |
| 10000 | 0.000115 | 0.0003031 | 0.37× |
| 1000000 | 0.011989 | 0.0142523 | 0.83× |
| 100000000| 1.08006 | 1.30234 | 0.83× |

## 7. Заключение
Результатом работы стали реализации последовательных и параллельных алгоритмов нахождения минимального элемента $a$ вектора $V$. Можно заметить, что SEQ версия работает быстрее MPI. Это происходит из-за большого количества накладных расходов при вызовах MPI_Scatterv, MPI_Reduce, MPI_Bcast, и малого количества ядер процессора.

## 8. Ссылки
1. Лекции по параллельному программированию
2. Практические занятия по параллельному программированию


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

#include <vector>

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

namespace khruev_a_min_elem_vec {

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

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

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

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

#include "khruev_a_min_elem_vec/common/include/common.hpp"

namespace khruev_a_min_elem_vec {

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

bool KhruevAMinElemVecSEQ::ValidationImpl() {
return (GetOutput() == 0);
}

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

bool KhruevAMinElemVecSEQ::RunImpl() {
if (GetInput().empty()) {
GetOutput() = INT_MAX;
return true;
}
int mininmum = GetInput()[0];
size_t vec_size = GetInput().size();
for (size_t i = 1; i < vec_size; i++) {
mininmum = std::min(GetInput()[i], mininmum);
}
GetOutput() = mininmum;

return true;
}

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

} // namespace khruev_a_min_elem_vec
7 changes: 7 additions & 0 deletions tasks/khruev_a_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/khruev_a_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
101 changes: 101 additions & 0 deletions tasks/khruev_a_min_elem_vec/tests/functional/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#include <gtest/gtest.h>
#include <stb/stb_image.h>

#include <array>
#include <climits>
#include <cstddef>
#include <iostream>
#include <string>
#include <tuple>
#include <vector>

#include "khruev_a_min_elem_vec/common/include/common.hpp"
#include "khruev_a_min_elem_vec/mpi/include/ops_mpi.hpp"
#include "khruev_a_min_elem_vec/seq/include/ops_seq.hpp"
#include "util/include/func_test_util.hpp"
#include "util/include/util.hpp"

namespace khruev_a_min_elem_vec {

class KhruevAMinElemVecFuncTests : public ppc::util::BaseRunFuncTests<InType, OutType, TestType> {
public:
static std::string PrintTestParam(const TestType &test_param) {
return std::get<1>(test_param);
}

protected:
void SetUp() override {
// size_t size = 10;
// for (size_t i = 1; i <= size; i++) {
// input_data_.push_back(i);
// }
// expected_ = 1;
TestType params = std::get<static_cast<std::size_t>(ppc::util::GTestParamIndex::kTestParams)>(GetParam());
std::cout << "SetUp for " << std::get<1>(params) << '\n';
input_data_ = std::vector(std::get<0>(std::get<0>(params)));
std::cout << "We set input data: \n";
for (auto x : input_data_) {
std::cout << x << ' ';
}
std::cout << '\n';
// expected_ = std::get<1>(std::get<0>(params));
std::cout << "We set expected result: " << std::get<1>(std::get<0>(params)) << '\n';
}

bool CheckTestOutputData(OutType &output_data) final {
std::cout << "CheckTestOutputData for "
<< std::get<1>(std::get<static_cast<std::size_t>(ppc::util::GTestParamIndex::kTestParams)>(GetParam()))
<< '\n';
return (std::get<1>(std::get<0>(std::get<static_cast<std::size_t>(ppc::util::GTestParamIndex::kTestParams)>(
GetParam()))) == output_data);
}

InType GetTestInputData() final {
return input_data_;
}

private:
InType input_data_;
// OutType expected_;
};

namespace {

TEST_P(KhruevAMinElemVecFuncTests, MinElemVecc) {
ExecuteTest(GetParam());
}
std::vector<int> instanse1{1, 2, 3, 4, 5, 6};
std::vector<int> instanse2{2, 2, -13, 4, 1, 5, 6};
std::vector<int> instanse3{-100, 2, 30, 4, 12, 6, 1, 6, 2};
std::vector<int> instanse4{1, 2, 3, 4, 5, 6, 9, 1, 1, 0};
std::vector<int> instanse5{1, 2, 3, 4};
std::vector<int> instanse6{2, 5, 2, 0, -10};
std::vector<int> instanse7{1, 2};
std::vector<int> instanse8{-1, -2, -8, -10, -11};
std::vector<int> instanse9{1, 1, 1, 1, 1};
std::vector<int> instanse10{};

const std::array<TestType, 10> kTestParam = {std::make_tuple(std::make_tuple(instanse1, 1), "test1"),
std::make_tuple(std::make_tuple(instanse2, -13), "test2"),
std::make_tuple(std::make_tuple(instanse3, -100), "test3"),
std::make_tuple(std::make_tuple(instanse4, 0), "test4"),
std::make_tuple(std::make_tuple(instanse5, 1), "test5"),
std::make_tuple(std::make_tuple(instanse6, -10), "test6"),
std::make_tuple(std::make_tuple(instanse7, 1), "test7"),
std::make_tuple(std::make_tuple(instanse8, -11), "test8"),
std::make_tuple(std::make_tuple(instanse9, 1), "test9"),
std::make_tuple(std::make_tuple(instanse10, INT_MAX), "test10")};

const auto kTestTasksList = std::tuple_cat(
ppc::util::AddFuncTask<KhruevAMinElemVecMPI, InType>(kTestParam, PPC_SETTINGS_khruev_a_min_elem_vec),
ppc::util::AddFuncTask<KhruevAMinElemVecSEQ, InType>(kTestParam, PPC_SETTINGS_khruev_a_min_elem_vec));

const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList);

const auto kPerfTestName = KhruevAMinElemVecFuncTests::PrintFuncTestName<KhruevAMinElemVecFuncTests>;

INSTANTIATE_TEST_SUITE_P(MinElemVec, KhruevAMinElemVecFuncTests, kGtestValues, kPerfTestName);

} // namespace

} // namespace khruev_a_min_elem_vec
Loading
Loading