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
5 changes: 5 additions & 0 deletions tasks/ovsyannikov_n_num_mistm_in_two_str/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ppc_add_task(
ovsyannikov_n_num_mistm_in_two_str
seq
mpi
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include <cstddef>
#include <string>
#include <tuple>
#include <utility>

#include "task/include/task.hpp"

namespace ovsyannikov_n_num_mistm_in_two_str {
using InType = std::pair<std::string, std::string>; // Входные данные: пара строк
using OutType = size_t; // Выходные данные: количество несовпадений
using BaseTask = ppc::task::Task<InType, OutType>;
using TestType = std::tuple<InType, OutType, std::string>;
} // namespace ovsyannikov_n_num_mistm_in_two_str
9 changes: 9 additions & 0 deletions tasks/ovsyannikov_n_num_mistm_in_two_str/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ФИ2",
"task_number": "27"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

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

namespace ovsyannikov_n_num_mistm_in_two_str {

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

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

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

#include <mpi.h>

#include <vector>

#include "ovsyannikov_n_num_mistm_in_two_str/common/include/common.hpp"

namespace ovsyannikov_n_num_mistm_in_two_str {

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

bool OvsyannikovNNumMistmInTwoStrMPI::ValidationImpl() {
int proc_rank = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &proc_rank);
if (proc_rank == 0) {
return GetInput().first.size() == GetInput().second.size();
}
return true;
}

bool OvsyannikovNNumMistmInTwoStrMPI::PreProcessingImpl() {
GetOutput() = 0;
return true;
}

bool OvsyannikovNNumMistmInTwoStrMPI::RunImpl() {
int proc_rank = 0;
int proc_num = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &proc_rank);
MPI_Comm_size(MPI_COMM_WORLD, &proc_num);

int total_len = 0;
if (proc_rank == 0) {
total_len = static_cast<int>(GetInput().first.size());
}

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

if (total_len == 0) {
return true;
}

std::vector<int> counts(proc_num);
std::vector<int> displs(proc_num);

int tail = total_len % proc_num;
int accum = 0;
for (int i = 0; i < proc_num; i++) {
counts[i] = (total_len / proc_num) + (i < tail ? 1 : 0);
displs[i] = accum;
accum += counts[i];
}

int my_count = counts[proc_rank];
std::vector<char> local_str_1(my_count);
std::vector<char> local_str_2(my_count);

const char *send_buf_1 = nullptr;
const char *send_buf_2 = nullptr;

if (proc_rank == 0) {
send_buf_1 = GetInput().first.data();
send_buf_2 = GetInput().second.data();
}

MPI_Scatterv(send_buf_1, counts.data(), displs.data(), MPI_CHAR, local_str_1.data(), my_count, MPI_CHAR, 0,
MPI_COMM_WORLD);

MPI_Scatterv(send_buf_2, counts.data(), displs.data(), MPI_CHAR, local_str_2.data(), my_count, MPI_CHAR, 0,
MPI_COMM_WORLD);

int priv_err_cnt = 0;
for (int i = 0; i < my_count; ++i) {
if (local_str_1[i] != local_str_2[i]) {
priv_err_cnt++;
}
}

int total_err_cnt = 0;
MPI_Allreduce(&priv_err_cnt, &total_err_cnt, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
GetOutput() = total_err_cnt;

return true;
}

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

} // namespace ovsyannikov_n_num_mistm_in_two_str
87 changes: 87 additions & 0 deletions tasks/ovsyannikov_n_num_mistm_in_two_str/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Количество несовпадений символов двух строк

- Студент: Овсянников Никита Владимирович, группа 3823Б1ФИ2
- Технология: SEQ, MPI
- Вариант: 27

## 1. Introduction

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

## 2. Problem Statement

У нас есть две строки одинаковой длины. Нужно пройтись по ним и посчитать, сколько раз символы на одной и той же позиции не совпадают.

**Входные данные:** Две строки (`std::string`).
**Выходные данные:** Целое число — количество несовпадений.

## 3. Baseline Algorithm (Sequential)

В последовательной версии мы просто бежим циклом от начала до конца строки.

```cpp
int mismatch_count = 0;
size_t n = seq_one.size();
for (size_t i = 0; i < n; ++i) {
if (seq_one[i] != seq_two[i]) {
mismatch_count++;
}
}
```
Сложность линейная - O(N).

## 4. Parallelization Scheme
Процесс с рангом 0 определяет размер данных. Исходные строки делятся на части по количеству процессов. Ранг 0 рассылает каждому процессу его куски двух строк (используется Scatterv). Каждый процесс независимо сравнивает символы в полученных частях. Локальные счетчики несовпадений суммируются в общий результат (Allreduce).

## 5. Implementation Details
- common: Определяет общие типы данных.
- seq: Последовательная реализация (обычный цикл).
- mpi: Параллельная реализация. Использует векторы для упаковки данных перед отправкой.
- tests: Тесты на корректность (разные случаи несовпадений) и на скорость.

## 6. Experimental Setup
- Аппаратное обеспечение: 13th Gen Intel Core i5-13500H (12 ядер: 4P + 8E, 2.60 GHz)
- ОЗУ: 16 ГБ (4266 MT/s)
- Операционная система: Windows 11
- Компилятор: MinGW-w64 (g++)
- Тип сборки: Release

## 7. Results and Discussion

### 7.1 Correctness
Корректность алгоритма подтверждена функциональными тестами. Результаты MPI-версии идентичны результатам последовательной версии на всех тестовых наборах данных (пустые строки, полное совпадение, частичное совпадение).

### 7.2 Performance

Тестирование проводилось на строках длиной 100 000 000 символов.
За базовое время ($T_{seq}$) взято время работы последовательной реализации: **0.1734 s**.

| Mode | Processes | Time (s) | Speedup | Efficiency |
|:----:|:---------:|:--------:|:-------:|:----------:|
| seq | 1 | 0.1734 | 1.00 | 100% |
| mpi | 2 | 0.3410 | 0.51 | 25.5% |
| mpi | 4 | 0.3304 | 0.52 | 13.0% |
| mpi | 8 | 0.2650 | 0.65 | 8.1% |
| mpi | 12 | 0.3064 | 0.56 | 4.7% |
| mpi | 24 | 0.8192 | 0.21 | 0.9% |

## 8. Conclusions

Анализ результатов экспериментов позволяет сделать следующие выводы:

1. **Отрицательное ускорение (Slowdown):**
Параллельная версия (MPI) во всех случаях работает медленнее последовательной (Speedup < 1). Это объясняется природой задачи:
* **Низкая вычислительная сложность:** Операция сравнения двух символов (`char == char`) выполняется процессором за один такт и экстремально быстра.
* **Доминирование накладных расходов:** Время, затрачиваемое на передачу данных через `MPI_Scatterv` и инициализацию процессов, значительно превышает время полезных вычислений. Даже оптимизация с прямым доступом к памяти (без лишних буферов) не позволяет перекрыть латентность межпроцессного взаимодействия.

2. **Динамика масштабируемости:**
* На 2-4 процессах накладные расходы высоки, ускорение составляет около 0.5.
* Наилучший результат достигнут на **8 процессах** (0.2650 s), где накладные расходы на коммуникацию лучше всего балансируются с уменьшением объема работы на каждом ядре. Однако даже этот результат уступает последовательной версии.

3. **Эффект переподписки (Oversubscription):**
При запуске на **24 процессах** наблюдается резкое падение производительности (время выросло до 0.8192 s). Это связано с тем, что количество MPI-процессов превышает количество физических потоков процессора (Intel Core i5-13500H имеет 12 ядер/16 потоков). Планировщик ОС вынужден постоянно переключать контекст (Context Switching) между процессами, что убивает производительность и кэш-локальность.

**Итог:** Технология MPI плохо подходит для задач с тривиальными вычислениями и большим объемом передаваемых данных на системах с общей памятью. Для таких задач эффективнее использовать многопоточность (OpenMP/TBB) или векторизацию (SIMD), так как они не требуют дорогостоящей пересылки данных между адресными пространствами.

## 9. References
1. Лекции и практики курса "Параллельное программирование".
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

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

namespace ovsyannikov_n_num_mistm_in_two_str {

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

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

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

#include <cstddef>

// Clang-Tidy требует явного подключения файла, где определен InType
#include "ovsyannikov_n_num_mistm_in_two_str/common/include/common.hpp"

namespace ovsyannikov_n_num_mistm_in_two_str {

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

bool OvsyannikovNNumMistmInTwoStrSEQ::ValidationImpl() {
return GetInput().first.size() == GetInput().second.size();
}

bool OvsyannikovNNumMistmInTwoStrSEQ::PreProcessingImpl() {
GetOutput() = 0;
return true;
}

bool OvsyannikovNNumMistmInTwoStrSEQ::RunImpl() {
const auto &seq_one = GetInput().first;
const auto &seq_two = GetInput().second;
int diff_cnt = 0;

size_t length = seq_one.size();
for (size_t i = 0; i < length; ++i) {
if (seq_one[i] != seq_two[i]) {
diff_cnt++;
}
}

GetOutput() = diff_cnt;
return true;
}

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

} // namespace ovsyannikov_n_num_mistm_in_two_str
7 changes: 7 additions & 0 deletions tasks/ovsyannikov_n_num_mistm_in_two_str/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/ovsyannikov_n_num_mistm_in_two_str/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
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include <gtest/gtest.h>

#include <array>
#include <string>
#include <tuple>
#include <utility>

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

namespace ovsyannikov_n_num_mistm_in_two_str {

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

protected:
void SetUp() override {
TestType params = std::get<TestType>(GetParam());
input_data_ = std::get<0>(params);
expected_ = std::get<1>(params);
}

bool CheckTestOutputData(OutType &actual_res) final {
return actual_res == expected_;
}

InType GetTestInputData() final {
return input_data_;
}

private:
InType input_data_;
OutType expected_ = 0;
};

TEST_P(OvsyannikovNRunFuncTestsProcesses, CalculateMismatches) {
ExecuteTest(GetParam());
}

const std::array<TestType, 10> kTestParam = {
{std::make_tuple(std::make_pair("", ""), 0, "EmptyStrings"),
std::make_tuple(std::make_pair("a", "a"), 0, "SingleCharMatch"),
std::make_tuple(std::make_pair("a", "b"), 1, "SingleCharMismatch"),
std::make_tuple(std::make_pair("hello", "hello"), 0, "IdenticalStrings"),
std::make_tuple(std::make_pair("abc", "def"), 3, "AllMismatch"),
std::make_tuple(std::make_pair("apple", "apply"), 1, "LastCharMismatch"),
std::make_tuple(std::make_pair("Case", "case"), 1, "CaseSensitivity"),
std::make_tuple(std::make_pair("12345", "12355"), 1, "NumbersMismatch"),
std::make_tuple(std::make_pair("two words", "two_words"), 1, "SpaceVsUnderscore"),
std::make_tuple(std::make_pair("longstringtest", "longstringbest"), 1, "LongStringOneMismatch")}};

const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask<OvsyannikovNNumMistmInTwoStrMPI, InType>(
kTestParam, PPC_SETTINGS_ovsyannikov_n_num_mistm_in_two_str),
ppc::util::AddFuncTask<OvsyannikovNNumMistmInTwoStrSEQ, InType>(
kTestParam, PPC_SETTINGS_ovsyannikov_n_num_mistm_in_two_str));

const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList);
const auto kPerfTestName = OvsyannikovNRunFuncTestsProcesses::PrintFuncTestName<OvsyannikovNRunFuncTestsProcesses>;
INSTANTIATE_TEST_SUITE_P(NumMistmInTwoStr, OvsyannikovNRunFuncTestsProcesses, kGtestValues, kPerfTestName);
} // namespace ovsyannikov_n_num_mistm_in_two_str
Loading
Loading