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/potashnik_m_char_freq/common/include/common.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include <string>
#include <tuple>

#include "task/include/task.hpp"

namespace potashnik_m_char_freq {

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

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

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

namespace potashnik_m_char_freq {

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

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

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

#include <mpi.h>

#include <string>
#include <vector>

#include "potashnik_m_char_freq/common/include/common.hpp"

namespace potashnik_m_char_freq {

PotashnikMCharFreqMPI::PotashnikMCharFreqMPI(const InType &in) {
SetTypeOfTask(GetStaticTypeOfTask());

int rank = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if (rank == 0) {
GetInput() = in;
}

GetOutput() = 0;
}

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

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

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

std::string str;
char chr = 0;
int string_size = 0;

if (rank == 0) {
str = std::get<0>(GetInput());
chr = std::get<1>(GetInput());
string_size = static_cast<int>(str.size());
}

MPI_Bcast(&string_size, 1, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Bcast(&chr, 1, MPI_CHAR, 0, MPI_COMM_WORLD);

int block_size = string_size / world_size;
int remainder = string_size % world_size;

std::vector<int> local_sizes(world_size, 0);
std::vector<int> local_start_positions(world_size, 0);

if (rank == 0) {
for (int i = 0; i < world_size; i++) {
if (remainder > i) {
local_sizes[i] = block_size + 1;
} else {
local_sizes[i] = block_size;
}
}

for (int i = 1; i < world_size; i++) {
local_start_positions[i] = local_start_positions[i - 1] + local_sizes[i - 1];
}
}

int cur_count = 0;
MPI_Scatter(local_sizes.data(), 1, MPI_INT, &cur_count, 1, MPI_INT, 0, MPI_COMM_WORLD);

std::string cur_str(cur_count, '\0');
if (rank == 0) {
std::vector<char> temp_str(str.begin(), str.end()); // Dont work without const
MPI_Scatterv(temp_str.data(), local_sizes.data(), local_start_positions.data(), MPI_CHAR, cur_str.data(), cur_count,
MPI_CHAR, 0, MPI_COMM_WORLD);
;
} else {
MPI_Scatterv(nullptr, nullptr, nullptr, MPI_CHAR, cur_str.data(), cur_count, MPI_CHAR, 0,
MPI_COMM_WORLD); // Just recieving data
}

int cur_res = 0;
for (char c : cur_str) {
if (c == chr) {
cur_res++;
}
}

int total_res = 0;
MPI_Allreduce(&cur_res, &total_res, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
GetOutput() = total_res;

return true;
}

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

} // namespace potashnik_m_char_freq
62 changes: 62 additions & 0 deletions tasks/potashnik_m_char_freq/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Подсчет частоты символа в строке

- Student: Поташник Максим Ярославович, group 3823Б1ФИ3
- Technology: SEQ | MPI
- Variant: 23

## 1. Introduction

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

Ожидается, что при использовании технологии mpi произойдёт ускорение по сравнению с последовательной версией.

## 2. Problem statement

Нужно подсчитать число вхождений символа в строку.

### Входные данные:
Строка произвольной длины и символ.

### Выходные данные:
Одно целое неотрицательное число - число вхождений символа в строку.

### 3. Baseline Algorithm (Sequential)

Заведём переменную для подсчёта числа вхождений, инициализируем её нулём. После этого последовательно проходим по строке, и каждый раз встречая искомый символ, увеличиваем переменную на 1.

### 4. Parallelization Scheme

В процессе с рангом 0 распределяем данные:
Делим строку нацело на столько блоков, сколько выполняется процессов. Может оказаться, что несколько символов останутся не распределёнными (при невозможности деления нацело). Тогда считаем остаток и распределяем его по блокам. Отправляем блоки на другие процессы.

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

### 5. Experimental Setup

- Hardware/OS: 12th gen Intel(R) Core(TM) i5-12450H, 8 ядер, 16 GB RAM, Windows 11 x64
- Toolchain: compiler, version, build type (Release/RelWithDebInfo)
- Cmake 3.28.3
- Компилятор: g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
- Использовался Docker-контейнер.
- Режим сборки: Release.
- Data: Для замера производительности использовалась строка размером 100.000.000, генерируемая из произвольных символов детерминированно (При одинаковых условиях генерируются одинаковые строки для корректности perf тестов).

## 6. Results and Discussion

### 6.1 Correctness
Корректность работы проверена с помощью тестов Google Test на строках размерами: 1, 5, 10, 20, 100, 1000, 2000, 5000, 10000, 20000.

### 6.2 Performance
| Mode | Count | Time, s | Speedup | Efficiency |
|-------------|-------|---------|---------|------------|
| seq | 1 | 0.068 | 1.00 | N/A |
| mpi | 2 | 0.168 | 0.40 | 20.0% |
| mpi | 4 | 0.149 | 0.45 | 11.25% |

## 7. Conclusions

Из-за накладных расходов на распределение данных между процессами, работа mpi версии медленне, чем работа последовательной версии. Это обосновывается тем, что только на рассыл строки размером n по процессам уходит n операций по работе с памятью, что уже будет работать дольше, чем seq версия программы.

## 8. References

1. "Параллельное программирование для кластерных систем" ННГУ им. Лобачевского, ИИТММ
22 changes: 22 additions & 0 deletions tasks/potashnik_m_char_freq/seq/include/ops_seq.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

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

namespace potashnik_m_char_freq {

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

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

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

#include <string>

#include "potashnik_m_char_freq/common/include/common.hpp"

namespace potashnik_m_char_freq {

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

bool PotashnikMCharFreqSEQ::ValidationImpl() {
return !std::get<0>(GetInput()).empty();
}

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

bool PotashnikMCharFreqSEQ::RunImpl() {
auto &input = GetInput();
std::string str = std::get<0>(input);
char chr = std::get<1>(input);

int string_size = static_cast<int>(str.size());
int res = 0;
for (int i = 0; i < string_size; i++) {
if (str[i] == chr) {
res++;
}
}
GetOutput() = res;

return true;
}

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

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

#include <array>
#include <cstddef>
#include <string>
#include <tuple>

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

namespace potashnik_m_char_freq {

class PotashnikMCharFreqFuncTests : 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::string str;
char chr = 0;

int seed = params;

// Generating character
chr = static_cast<char>('a' + (seed % 26));

// Generating string
for (int i = 0; i < params; i++) {
char c = static_cast<char>('a' + ((i * 7 + 13 + seed / 2) % 26));
str += c;
}

input_data_ = std::make_tuple(str, chr);
}

bool CheckTestOutputData(OutType &output_data) final {
int res = 0;
std::string str = std::get<0>(input_data_);
char chr = std::get<1>(input_data_);

int string_size = static_cast<int>(str.size());
for (int i = 0; i < string_size; i++) {
if (str[i] == chr) {
res++;
}
}

return (res == output_data);
}

InType GetTestInputData() final {
return input_data_;
}

private:
InType input_data_;
};

namespace {

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

const std::array<TestType, 10> kTestParam = {1, 5, 10, 20, 100, 1000, 2000, 5000, 10000, 20000};

const auto kTestTasksList = std::tuple_cat(
ppc::util::AddFuncTask<PotashnikMCharFreqMPI, InType>(kTestParam, PPC_SETTINGS_potashnik_m_char_freq),
ppc::util::AddFuncTask<PotashnikMCharFreqSEQ, InType>(kTestParam, PPC_SETTINGS_potashnik_m_char_freq));

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

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

INSTANTIATE_TEST_SUITE_P(PicMatrixTests, PotashnikMCharFreqFuncTests, kGtestValues, kPerfTestName);

} // namespace

} // namespace potashnik_m_char_freq
Loading
Loading