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

#include <vector>

#include "task/include/task.hpp"

namespace vector_scalar_product {

struct DotProductInput {
std::vector<double> lhs;
std::vector<double> rhs;
};

using InType = DotProductInput;
using OutType = double;
using BaseTask = ppc::task::Task<InType, OutType>;

} // namespace vector_scalar_product
3 changes: 3 additions & 0 deletions tasks/vector_scalar_product/data/vectors_sample.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
8,1.2 -3.4 5.1 0.0 2.8 -1.7 4.3 9.1,0.5 3.6 -2.1 4.8 -1.2 0.0 7.3 2.2
16,2.4 -1.1 0.0 3.9 5.6 -2.7 8.8 -4.4 6.6 -3.3 1.1 4.2 -5.8 7.9 -0.6 2.0,-1.5 2.7 3.3 -4.1 6.0 -7.2 1.5 2.8 -0.9 5.5 -3.6 4.4 2.2 -1.8 0.0 3.1
24,0.25 0.5 0.75 1.0 1.25 1.5 1.75 2.0 2.25 2.5 2.75 3.0 3.25 3.5 3.75 4.0 4.25 4.5 4.75 5.0 5.25 5.5 5.75 6.0,6.0 5.75 5.5 5.25 5.0 4.75 4.5 4.25 4.0 3.75 3.5 3.25 3.0 2.75 2.5 2.25 2.0 1.75 1.5 1.25 1.0 0.75 0.5 0.25
9 changes: 9 additions & 0 deletions tasks/vector_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ФИ1",
"task_number": "9"
}
}
32 changes: 32 additions & 0 deletions tasks/vector_scalar_product/mpi/include/ops_mpi.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include <vector>

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

namespace vector_scalar_product {

class VectorScalarProductMpi : public BaseTask {
public:
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
return ppc::task::TypeOfTask::kMPI;
}

explicit VectorScalarProductMpi(const InType &in);

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

int rank_ = 0;
int world_size_ = 1;
std::vector<double> local_lhs_;
std::vector<double> local_rhs_;
double local_sum_ = 0.0;
double result_ = 0.0;
};

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

#include <mpi.h>

#include <cstddef>
#include <numeric>
#include <vector>

#include "vector_scalar_product/common/include/common.hpp"

namespace vector_scalar_product {
namespace {
std::vector<int> BuildCounts(int total, int parts) {
std::vector<int> counts(parts, 0);
const int base = total / parts;
int remainder = total % parts;
for (int i = 0; i < parts; ++i) {
counts[i] = base + (remainder > 0 ? 1 : 0);
if (remainder > 0) {
--remainder;
}
}
return counts;
}

std::vector<int> BuildDisplacements(const std::vector<int> &counts) {
std::vector<int> displs(counts.size(), 0);
for (std::size_t i = 1; i < counts.size(); ++i) {
displs[i] = displs[i - 1] + counts[i - 1];
}
return displs;
}
} // namespace

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

bool VectorScalarProductMpi::ValidationImpl() {
const auto &lhs = GetInput().lhs;
const auto &rhs = GetInput().rhs;
return !lhs.empty() && lhs.size() == rhs.size();
}

bool VectorScalarProductMpi::PreProcessingImpl() {
MPI_Comm_rank(MPI_COMM_WORLD, &rank_);
MPI_Comm_size(MPI_COMM_WORLD, &world_size_);

const auto &lhs = GetInput().lhs;
const auto &rhs = GetInput().rhs;
int global_size = 0;
if (rank_ == 0) {
global_size = static_cast<int>(lhs.size());
}
MPI_Bcast(&global_size, 1, MPI_INT, 0, MPI_COMM_WORLD);

// Prepare scattering meta information
auto counts = BuildCounts(global_size, world_size_);
auto displs = BuildDisplacements(counts);
const int local_count = counts[rank_];

local_lhs_.assign(static_cast<std::size_t>(local_count), 0.0);
local_rhs_.assign(static_cast<std::size_t>(local_count), 0.0);

const double *lhs_ptr = rank_ == 0 ? lhs.data() : nullptr;
const double *rhs_ptr = rank_ == 0 ? rhs.data() : nullptr;

MPI_Scatterv(lhs_ptr, counts.data(), displs.data(), MPI_DOUBLE, local_lhs_.data(), local_count, MPI_DOUBLE, 0,
MPI_COMM_WORLD);
MPI_Scatterv(rhs_ptr, counts.data(), displs.data(), MPI_DOUBLE, local_rhs_.data(), local_count, MPI_DOUBLE, 0,
MPI_COMM_WORLD);

local_sum_ = 0.0;
result_ = 0.0;
return true;
}

bool VectorScalarProductMpi::RunImpl() {
local_sum_ = std::inner_product(local_lhs_.begin(), local_lhs_.end(), local_rhs_.begin(), 0.0);

return MPI_Reduce(&local_sum_, &result_, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD) == MPI_SUCCESS;
}

bool VectorScalarProductMpi::PostProcessingImpl() {
MPI_Bcast(&result_, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
GetOutput() = result_;
return true;
}

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

**Студент:** Шеленкова Мария Сергеевна
**Группа:** 3823Б1ФИ1
Copy link
Member

Choose a reason for hiding this comment

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

3823Б1ФИ1 or 2 ? Data in tasks/vector_scalar_product/info.json and tasks/vector_scalar_product/report.md mismatch. Please, fix

**Вариант:** 9

### 1. Введение
**Мотивация.** Скалярное произведение лежит в основе множества алгоритмов линейной алгебры и анализа данных, поэтому важно исследовать его последовательную и параллельную реализации.
**Проблематика.** Операция элементарна и содержит мало вычислений на одну итерацию. Коммуникационные накладные расходы могут превысить выигрыш от распараллеливания.
**Ожидаемый результат.** Предполагалось, что параллельная версия окажется эффективной только для очень длинных векторов; на малых размерах преимущество сохранит последовательный алгоритм.

### 2. Постановка задачи
**Задано:** два вещественных вектора одинаковой длины.
**Требуется:** вычислить их скалярное произведение.
**Входные данные:** массивы `double a[i]`, `double b[i]`, `size_t n`.
**Выходные данные:** значение `double result`.

### 3. Последовательный алгоритм
1. Проверяются корректность входных данных и равенство размеров векторов.
2. Используется стандартная функция `std::inner_product`, последовательно перемножающая пары элементов и суммирующая результат.
3. Алгоритм имеет линейную сложность `O(n)` и использует константный объём дополнительной памяти.

### 4. Схема распараллеливания
**Декомпозиция данных.** Индексный диапазон разделяется на блоки по числу процессов. Первые `n % P` процессов получают на один элемент больше.
**Коммуникация.** Корневой процесс рассылает длину векторов и параметры распределения с помощью `MPI_Bcast`. После локальных вычислений частичные суммы сводятся к корню через `MPI_Reduce` с операцией `MPI_SUM`; при необходимости результат снова транслируется `MPI_Bcast`. Дополнительных синхронизаций не требуется.

### 5. Экспериментальная установка
* Процессор: 13th Gen Intel(R) Core(TM) i9-13980HX (24 физических ядра, 32 логических потока).
* Оперативная память: 32 GB.
* Операционная система: Microsoft Windows 11 Pro.
* Компилятор: MSVC 19.40, конфигурация Release x64.
* MPI-библиотека: Microsoft MPI 10.1.12498.52.

### 6. Результаты и обсуждение
#### 6.1. Корректность
Проверка выполнена командой `ppc_func_tests.exe --gtest_filter=*vector_scalar_product*`. Все тесты завершились успешно; значения параллельной версии совпадают с последовательным эталоном.

#### 6.2. Производительность
Измерения проводились с помощью `ppc_perf_tests.exe --gtest_filter=*vector_scalar_product*`, размер тестового входа — 20 млн элементов. Последовательный вариант запускался на одном процессе, параллельный — на восьми (`mpiexec -n 8`).

| Реализация | Процессов | Режим теста | Время, сек |
|-----------:|----------:|-------------|-----------:|
| seq | 1 | pipeline | 0.012362 |
| seq | 1 | task_only | 0.012612 |
| mpi | 8 | pipeline | 0.063408 |
| mpi | 8 | task_only | 0.012235 |

При чистом `task_only` MPI-версия достигла близкой к последовательной скорости, но конвейерная цепочка осталась медленнее из-за стоимости распределения и сбора данных. Даже при увеличенном размере вектора ускорение не появляется.

### 7. Выводы
Последовательный алгоритм остаётся предпочтительным для небольших векторов, где коммуникационные затраты доминируют. Реализация MPI корректна и готова к масштабированию на большие объёмы данных, при которых вычислительная нагрузка превысит стоимость обменов.
25 changes: 25 additions & 0 deletions tasks/vector_scalar_product/seq/include/ops_seq.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

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

namespace vector_scalar_product {

class VectorScalarProductSeq : public BaseTask {
public:
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
return ppc::task::TypeOfTask::kSEQ;
}

explicit VectorScalarProductSeq(const InType &in);

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

double partial_sum_ = 0.0;
};

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

#include <numeric>

#include "vector_scalar_product/common/include/common.hpp"

namespace vector_scalar_product {

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

bool VectorScalarProductSeq::ValidationImpl() {
const auto &lhs = GetInput().lhs;
const auto &rhs = GetInput().rhs;
return !lhs.empty() && lhs.size() == rhs.size();
}

bool VectorScalarProductSeq::PreProcessingImpl() {
partial_sum_ = 0.0;
return true;
}

bool VectorScalarProductSeq::RunImpl() {
const auto &lhs = GetInput().lhs;
const auto &rhs = GetInput().rhs;
partial_sum_ = std::inner_product(lhs.begin(), lhs.end(), rhs.begin(), 0.0);
return true;
}

bool VectorScalarProductSeq::PostProcessingImpl() {
GetOutput() = partial_sum_;
return true;
}

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

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

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

namespace vector_scalar_product {

struct DotProductCase {
std::vector<double> lhs;
std::vector<double> rhs;
double expected = 0.0;
};

using TestType = DotProductCase;

class VectorScalarProductFuncTests : public ppc::util::BaseRunFuncTests<InType, OutType, TestType> {
public:
static std::string PrintTestParam(const TestType &param) {
const auto size_tag = std::to_string(param.lhs.size());
const auto value_tag = std::to_string(static_cast<int>(param.expected));
return size_tag + "_" + value_tag;
}

protected:
void SetUp() override {
const auto &param = std::get<static_cast<std::size_t>(ppc::util::GTestParamIndex::kTestParams)>(GetParam());
input_.lhs = param.lhs;
input_.rhs = param.rhs;
expected_ = param.expected;
}

InType GetTestInputData() final {
return input_;
}

bool CheckTestOutputData(OutType &output_data) final {
return std::fabs(output_data - expected_) < 1e-9;
}

private:
InType input_{};
double expected_ = 0.0;
};

namespace {

constexpr double kPi = std::numbers::pi;

const std::array<TestType, 3> kTestParams = {TestType{{1.0, 2.0, 3.0}, {4.0, -5.0, 6.0}, 12.0},
TestType{{0.5, 1.5, -2.0, 4.0}, {2.0, 1.0, -1.0, 0.0}, 4.5},
TestType{{kPi, 0.0, -kPi}, {1.0, 2.0, -1.0}, 2.0 * kPi}};

const auto kTaskList = std::tuple_cat(
ppc::util::AddFuncTask<VectorScalarProductSeq, InType>(kTestParams, PPC_SETTINGS_vector_scalar_product),
ppc::util::AddFuncTask<VectorScalarProductMpi, InType>(kTestParams, PPC_SETTINGS_vector_scalar_product));

const auto kGTestValues = ppc::util::ExpandToValues(kTaskList);

// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables,modernize-type-traits)
INSTANTIATE_TEST_SUITE_P(DotProduct, VectorScalarProductFuncTests, kGTestValues,
VectorScalarProductFuncTests::PrintFuncTestName<VectorScalarProductFuncTests>);

TEST_P(VectorScalarProductFuncTests, Runs) {
ExecuteTest(GetParam());
}

} // namespace

} // namespace vector_scalar_product
Loading
Loading