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

#include <tuple>
#include <vector>

#include "task/include/task.hpp"

namespace zavyalov_a_scalar_product {

using InType = std::tuple<std::vector<double>, std::vector<double>>;
using OutType = double;
using TestType = unsigned int; // size of vectors
using BaseTask = ppc::task::Task<InType, OutType>;

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

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

namespace zavyalov_a_scalar_product {

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

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

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

#include <mpi.h>

#include <vector>

#include "zavyalov_a_scalar_product/common/include/common.hpp"

namespace zavyalov_a_scalar_product {

ZavyalovAScalarProductMPI::ZavyalovAScalarProductMPI(const InType &in) {
SetTypeOfTask(GetStaticTypeOfTask());
int rank = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if (rank == 0) {
GetInput() = in;
}
GetOutput() = 0.0;
}

bool ZavyalovAScalarProductMPI::ValidationImpl() {
int rank = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if (rank != 0) {
return true;
}
return (!std::get<0>(GetInput()).empty()) && (std::get<0>(GetInput()).size() == std::get<1>(GetInput()).size());
}

bool ZavyalovAScalarProductMPI::PreProcessingImpl() {
Comment thread
allnes marked this conversation as resolved.
return true;
}
bool ZavyalovAScalarProductMPI::RunImpl() {
const double *left_data = nullptr;
const double *right_data = nullptr;

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

int vector_size = 0;

if (rank == 0) {
GetOutput() = 0.0;
const auto &input = GetInput();
if (!std::get<0>(input).empty()) { // it does not compile in ubuntu without this line
left_data = std::get<0>(input).data();
}
if (!std::get<1>(input).empty()) { // it does not compile in ubuntu without this line
right_data = std::get<1>(input).data();
}
vector_size = static_cast<int>(std::get<0>(input).size());
}

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

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

int blocksize = vector_size / world_size;
int elements_left = vector_size - (world_size * blocksize);
int elements_processed = 0;

for (int i = 0; i < world_size; i++) {
sendcounts[i] = blocksize + (i < elements_left ? 1 : 0);
displs[i] = elements_processed;
elements_processed += sendcounts[i];
}

int elements_count = sendcounts[rank];
std::vector<double> local_left(elements_count);
std::vector<double> local_right(elements_count);

MPI_Scatterv(left_data, sendcounts.data(), displs.data(), MPI_DOUBLE, local_left.data(), elements_count, MPI_DOUBLE,
0, MPI_COMM_WORLD);
MPI_Scatterv(right_data, sendcounts.data(), displs.data(), MPI_DOUBLE, local_right.data(), elements_count, MPI_DOUBLE,
0, MPI_COMM_WORLD);

double cur_res = 0.0;
for (int i = 0; i < elements_count; i++) {
cur_res += local_left[i] * local_right[i];
}

double glob_res = 0.0;
MPI_Allreduce(&cur_res, &glob_res, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);

GetOutput() = glob_res;

return true;
}

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

} // namespace zavyalov_a_scalar_product
66 changes: 66 additions & 0 deletions tasks/zavyalov_a_scalar_product/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Скалярное произведение векторов

- Student: Завьялов Алексей Алексеевич, group 3823Б1ФИ3
- Technology: SEQ | MPI
- Variant: 9

## 1. Introduction

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

Ожидается небольшое ускорение mpi версии программы относительно последовательной версии.

## 2. Problem Statement
Скалярное произведение двух векторов одной размерности - это число, равное сумме произведений соответствующих координат этих векторов.
Требуется найти скалярное произведение двух заданных векторов.

```math
(a, b) = \sum_{i=0}^{n-1} a_i b_i.
```

### Входные данные:
Два математических вектора произвольной размерности, состоящих из действительных чисел.

### Выходные данные:
Одно действительное число - скалярное произведение заданных векторов.

## 3. Baseline Algorithm (Sequential)
Инициализируем результат значением 0. Затем последовательно проходим по векторам, добавляя к результату произведение соответствующих координат.

## 4. Parallelization Scheme
Делим размер вектора нацело на `n`, где `n` - число процессов, получаем размер блока.

Корневой процесс рассылает остальным соответствующие части обоих векторов. В случае, когда размер вектора не кратен n, то есть когда при делении остаётся остаток `remainder`, не равный 0, первым `remainder` процессам отсылается на 1 элемент каждого вектора больше. Этот этап реализуется с помощью функции `MPI_Scatterv()`

Далее каждый процесс вычисляет свою часть слагаемых, составляющих скалярное произведение.

В конце работы результаты всех процессов складываются и записываются в ответ с помощью функции `MPI_Allreduce()`.

## 5. Experimental Setup
- Hardware/OS: AMD Ryzen 5 7520U, 4 ядра, 16 GB RAM, Windows 10 x64
- Toolchain:
- Cmake 3.28.3
- Компилятор: g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
- Использовался Docker-контейнер.
- Режим сборки: Release.
- Data: Для замера производительности использовались векторы размером 40.000.000:
- Первый вектор состоит из натуральных чисел от 0 до 39.999.999 включительно.
- Второй вектор состоит из чётных натуральных чисел от 0 до 79.999.998 включительно.

## 6. Results and Discussion

### 6.1 Correctness
Корректность работы проверена с помощью тестов Google Test на парах векторов размером от 1 до 10 включительно.

### 6.2 Performance
| Mode | Count | Time, s | Speedup | Efficiency |
|-------------|-------|---------|---------|------------|
| seq | 1 | 0.070 | 1.00 | N/A |
| mpi | 2 | 0.737 | 0.09 | 4.5% |
| mpi | 4 | 0.599 | 0.11 | 2.75% |

## 7. Conclusions
Накладные расходы на работу с процессами и рассылка данных занимают большую часть времени работы программы, из-за чего эффективность mpi версии значительно меньше 100%.

## 8. References
1. Курс лекций ННГУ "Параллельное программирование для кластерных систем"
22 changes: 22 additions & 0 deletions tasks/zavyalov_a_scalar_product/seq/include/ops_seq.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

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

namespace zavyalov_a_scalar_product {

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

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

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

#include <cstdlib>
#include <vector>

#include "zavyalov_a_scalar_product/common/include/common.hpp"

namespace zavyalov_a_scalar_product {

ZavyalovAScalarProductSEQ::ZavyalovAScalarProductSEQ(const InType &in) {
SetTypeOfTask(GetStaticTypeOfTask());
GetInput() = in;
GetOutput() = 0.0;
}

bool ZavyalovAScalarProductSEQ::ValidationImpl() {
return (!std::get<0>(GetInput()).empty()) && (std::get<0>(GetInput()).size() == std::get<1>(GetInput()).size());
}

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

bool ZavyalovAScalarProductSEQ::RunImpl() {
const auto &input = GetInput();
const std::vector<double> &left = std::get<0>(input);
const std::vector<double> &right = std::get<1>(input);

double res = 0.0;
for (size_t i = 0; i < left.size(); i++) {
res += left[i] * right[i];
}
GetOutput() = res;
return true;
}

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

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

#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <string>
#include <tuple>
#include <vector>

#include "util/include/func_test_util.hpp"
#include "util/include/util.hpp"
#include "zavyalov_a_scalar_product/common/include/common.hpp"
#include "zavyalov_a_scalar_product/mpi/include/ops_mpi.hpp"
Comment thread
allnes marked this conversation as resolved.
#include "zavyalov_a_scalar_product/seq/include/ops_seq.hpp"

namespace zavyalov_a_scalar_product {

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

protected:
void SetUp() override {
TestType params = std::get<static_cast<std::size_t>(ppc::util::GTestParamIndex::kTestParams)>(GetParam());
std::vector<double> left_vec(params);
std::vector<double> right_vec(params);

double minus = -1.0;
for (unsigned int i = 0; i < params; i++) {
left_vec[i] = (i * 0.5) + 0.1;
right_vec[i] = static_cast<double>(i) + 1.0;
right_vec[i] *= minus;
minus *= -1.0;
}

input_data_ = std::make_tuple(left_vec, right_vec);
}

bool CheckTestOutputData(OutType &output_data) final {
double res = 0.0;
for (size_t i = 0; i < std::get<0>(input_data_).size(); i++) {
res += std::get<0>(input_data_)[i] * std::get<1>(input_data_)[i];
}
double diff = fabs(res - output_data);
double epsilon = 1e-9 * (1 + std::max(fabs(res), fabs(output_data)));
return diff < epsilon;
}

InType GetTestInputData() final {
return input_data_;
}

private:
InType input_data_;
};

namespace {

TEST_P(ZavyalovAScalarProductFuncTests, MatmulFromPic) {
ExecuteTest(GetParam());
}

const std::array<TestType, 10> kTestParam = {1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U};

const auto kTestTasksList = std::tuple_cat(
ppc::util::AddFuncTask<ZavyalovAScalarProductMPI, InType>(kTestParam, PPC_SETTINGS_zavyalov_a_scalar_product),
ppc::util::AddFuncTask<ZavyalovAScalarProductSEQ, InType>(kTestParam, PPC_SETTINGS_zavyalov_a_scalar_product));

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

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

INSTANTIATE_TEST_SUITE_P(PicMatrixTests, ZavyalovAScalarProductFuncTests, kGtestValues, kPerfTestName);

} // namespace

} // namespace zavyalov_a_scalar_product
Loading
Loading