Skip to content

Commit 871bb69

Browse files
Поташник Максим. Технология SEQ-MPI. Подсчет частоты символа в строке. Вариант 23. (#71)
## Описание Была сделана задача параллелизма на примере задачи подсчета частоты символа в строке <!-- Пожалуйста, предоставьте подробное описание вашей реализации, включая: - основные детали решения (описание выбранного алгоритма) - применение технологии параллелизма (если применимо) --> - **Задача**: Подсчет частоты символа в строке - **Вариант**: 23 - **Технология**: SEQ-MPI - **Описание**: Последовательный алгоритм представляет собой проход от начала до конца строки, прибавляя к переменной-счётчику 1 каждый раз, когда проходим через нужный символ. В параллельной версии, каждый процесс проходит по выделенной ему части строки и считает промежуточный результат. Для этого в процессе с рангом 0 строка делится на блоки, которые рассылаются по остальным процессам. На каждом процессе считается результат для принятого блока. В конце все промежуточные результаты с разных процессов суммируются в итоговый результат. Отчет содержит описание системы, на которой проводились эксперименты, результаты и вывод. --- ## Чек-лист - [x] **Статус CI**: Все CI-задачи (сборка, тесты, генерация отчёта) успешно проходят на моей ветке в моем форке - [x] **Директория и именование задачи**: Я создал директорию с именем `<фамилия>_<первая_буква_имени>_<короткое_название_задачи>` - [x] **Полное описание задачи**: Я предоставил полное описание задачи в теле pull request - [x] **clang-format**: Мои изменения успешно проходят `clang-format` локально в моем форке (нет ошибок форматирования) - [x] **clang-tidy**: Мои изменения успешно проходят `clang-tidy` локально в моем форке (нет предупреждений/ошибок) - [x] **Функциональные тесты**: Все функциональные тесты успешно проходят локально на моей машине - [x] **Тесты производительности**: Все тесты производительности успешно проходят локально на моей машине - [x] **Ветка**: Я работаю в ветке, названной точно так же, как директория моей задачи (например, `nesterov_a_vector_sum`), а не в `master` - [x] **Правдивое содержание**: Я подтверждаю, что все сведения, указанные в этом pull request, являются точными и достоверными
1 parent 7553809 commit 871bb69

11 files changed

Lines changed: 451 additions & 0 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <tuple>
5+
6+
#include "task/include/task.hpp"
7+
8+
namespace potashnik_m_char_freq {
9+
10+
using InType = std::tuple<std::string, char>;
11+
using OutType = int;
12+
using TestType = int; // string size
13+
using BaseTask = ppc::task::Task<InType, OutType>;
14+
15+
} // namespace potashnik_m_char_freq
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"student": {
3+
"first_name": "Максим",
4+
"last_name": "Поташник",
5+
"middle_name": "Ярославович",
6+
"group_number": "3823Б1ФИ3",
7+
"task_number": "1"
8+
}
9+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
3+
#include "potashnik_m_char_freq/common/include/common.hpp"
4+
#include "task/include/task.hpp"
5+
6+
namespace potashnik_m_char_freq {
7+
8+
class PotashnikMCharFreqMPI : public BaseTask {
9+
public:
10+
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
11+
return ppc::task::TypeOfTask::kMPI;
12+
}
13+
explicit PotashnikMCharFreqMPI(const InType &in);
14+
15+
private:
16+
bool ValidationImpl() override;
17+
bool PreProcessingImpl() override;
18+
bool RunImpl() override;
19+
bool PostProcessingImpl() override;
20+
};
21+
22+
} // namespace potashnik_m_char_freq
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#include "potashnik_m_char_freq/mpi/include/ops_mpi.hpp"
2+
3+
#include <mpi.h>
4+
5+
#include <string>
6+
#include <vector>
7+
8+
#include "potashnik_m_char_freq/common/include/common.hpp"
9+
10+
namespace potashnik_m_char_freq {
11+
12+
PotashnikMCharFreqMPI::PotashnikMCharFreqMPI(const InType &in) {
13+
SetTypeOfTask(GetStaticTypeOfTask());
14+
15+
int rank = 0;
16+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
17+
if (rank == 0) {
18+
GetInput() = in;
19+
}
20+
21+
GetOutput() = 0;
22+
}
23+
24+
bool PotashnikMCharFreqMPI::ValidationImpl() {
25+
int rank = 0;
26+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
27+
if (rank != 0) {
28+
return true;
29+
}
30+
return !std::get<0>(GetInput()).empty();
31+
}
32+
33+
bool PotashnikMCharFreqMPI::PreProcessingImpl() {
34+
return true;
35+
}
36+
37+
bool PotashnikMCharFreqMPI::RunImpl() {
38+
int world_size = 0;
39+
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
40+
int rank = 0;
41+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
42+
43+
std::string str;
44+
char chr = 0;
45+
int string_size = 0;
46+
47+
if (rank == 0) {
48+
str = std::get<0>(GetInput());
49+
chr = std::get<1>(GetInput());
50+
string_size = static_cast<int>(str.size());
51+
}
52+
53+
MPI_Bcast(&string_size, 1, MPI_INT, 0, MPI_COMM_WORLD);
54+
MPI_Bcast(&chr, 1, MPI_CHAR, 0, MPI_COMM_WORLD);
55+
56+
int block_size = string_size / world_size;
57+
int remainder = string_size % world_size;
58+
59+
std::vector<int> local_sizes(world_size, 0);
60+
std::vector<int> local_start_positions(world_size, 0);
61+
62+
if (rank == 0) {
63+
for (int i = 0; i < world_size; i++) {
64+
if (remainder > i) {
65+
local_sizes[i] = block_size + 1;
66+
} else {
67+
local_sizes[i] = block_size;
68+
}
69+
}
70+
71+
for (int i = 1; i < world_size; i++) {
72+
local_start_positions[i] = local_start_positions[i - 1] + local_sizes[i - 1];
73+
}
74+
}
75+
76+
int cur_count = 0;
77+
MPI_Scatter(local_sizes.data(), 1, MPI_INT, &cur_count, 1, MPI_INT, 0, MPI_COMM_WORLD);
78+
79+
std::string cur_str(cur_count, '\0');
80+
if (rank == 0) {
81+
std::vector<char> temp_str(str.begin(), str.end()); // Dont work without const
82+
MPI_Scatterv(temp_str.data(), local_sizes.data(), local_start_positions.data(), MPI_CHAR, cur_str.data(), cur_count,
83+
MPI_CHAR, 0, MPI_COMM_WORLD);
84+
;
85+
} else {
86+
MPI_Scatterv(nullptr, nullptr, nullptr, MPI_CHAR, cur_str.data(), cur_count, MPI_CHAR, 0,
87+
MPI_COMM_WORLD); // Just recieving data
88+
}
89+
90+
int cur_res = 0;
91+
for (char c : cur_str) {
92+
if (c == chr) {
93+
cur_res++;
94+
}
95+
}
96+
97+
int total_res = 0;
98+
MPI_Allreduce(&cur_res, &total_res, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
99+
GetOutput() = total_res;
100+
101+
return true;
102+
}
103+
104+
bool PotashnikMCharFreqMPI::PostProcessingImpl() {
105+
return true;
106+
}
107+
108+
} // namespace potashnik_m_char_freq
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Подсчет частоты символа в строке
2+
3+
- Student: Поташник Максим Ярославович, group 3823Б1ФИ3
4+
- Technology: SEQ | MPI
5+
- Variant: 23
6+
7+
## 1. Introduction
8+
9+
Подсчет частоты символа в строке является часто встречающейся операцией во многих задачах, от чего возникает необходимость в ускорении процесса его выполнения.
10+
11+
Ожидается, что при использовании технологии mpi произойдёт ускорение по сравнению с последовательной версией.
12+
13+
## 2. Problem statement
14+
15+
Нужно подсчитать число вхождений символа в строку.
16+
17+
### Входные данные:
18+
Строка произвольной длины и символ.
19+
20+
### Выходные данные:
21+
Одно целое неотрицательное число - число вхождений символа в строку.
22+
23+
### 3. Baseline Algorithm (Sequential)
24+
25+
Заведём переменную для подсчёта числа вхождений, инициализируем её нулём. После этого последовательно проходим по строке, и каждый раз встречая искомый символ, увеличиваем переменную на 1.
26+
27+
### 4. Parallelization Scheme
28+
29+
В процессе с рангом 0 распределяем данные:
30+
Делим строку нацело на столько блоков, сколько выполняется процессов. Может оказаться, что несколько символов останутся не распределёнными (при невозможности деления нацело). Тогда считаем остаток и распределяем его по блокам. Отправляем блоки на другие процессы.
31+
32+
После этого каждый процесс считает промежуточный результат для выделенного ему блока. В конце работы всех процессов, промежуточные результаты всех процессов складываются с помощью функции MPI_Allreduce и записываются в ответ.
33+
34+
### 5. Experimental Setup
35+
36+
- Hardware/OS: 12th gen Intel(R) Core(TM) i5-12450H, 8 ядер, 16 GB RAM, Windows 11 x64
37+
- Toolchain: compiler, version, build type (Release/RelWithDebInfo)
38+
- Cmake 3.28.3
39+
- Компилятор: g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
40+
- Использовался Docker-контейнер.
41+
- Режим сборки: Release.
42+
- Data: Для замера производительности использовалась строка размером 100.000.000, генерируемая из произвольных символов детерминированно (При одинаковых условиях генерируются одинаковые строки для корректности perf тестов).
43+
44+
## 6. Results and Discussion
45+
46+
### 6.1 Correctness
47+
Корректность работы проверена с помощью тестов Google Test на строках размерами: 1, 5, 10, 20, 100, 1000, 2000, 5000, 10000, 20000.
48+
49+
### 6.2 Performance
50+
| Mode | Count | Time, s | Speedup | Efficiency |
51+
|-------------|-------|---------|---------|------------|
52+
| seq | 1 | 0.068 | 1.00 | N/A |
53+
| mpi | 2 | 0.168 | 0.40 | 20.0% |
54+
| mpi | 4 | 0.149 | 0.45 | 11.25% |
55+
56+
## 7. Conclusions
57+
58+
Из-за накладных расходов на распределение данных между процессами, работа mpi версии медленне, чем работа последовательной версии. Это обосновывается тем, что только на рассыл строки размером n по процессам уходит n операций по работе с памятью, что уже будет работать дольше, чем seq версия программы.
59+
60+
## 8. References
61+
62+
1. "Параллельное программирование для кластерных систем" ННГУ им. Лобачевского, ИИТММ
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
3+
#include "potashnik_m_char_freq/common/include/common.hpp"
4+
#include "task/include/task.hpp"
5+
6+
namespace potashnik_m_char_freq {
7+
8+
class PotashnikMCharFreqSEQ : public BaseTask {
9+
public:
10+
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
11+
return ppc::task::TypeOfTask::kSEQ;
12+
}
13+
explicit PotashnikMCharFreqSEQ(const InType &in);
14+
15+
private:
16+
bool ValidationImpl() override;
17+
bool PreProcessingImpl() override;
18+
bool RunImpl() override;
19+
bool PostProcessingImpl() override;
20+
};
21+
22+
} // namespace potashnik_m_char_freq
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include "potashnik_m_char_freq/seq/include/ops_seq.hpp"
2+
3+
#include <string>
4+
5+
#include "potashnik_m_char_freq/common/include/common.hpp"
6+
7+
namespace potashnik_m_char_freq {
8+
9+
PotashnikMCharFreqSEQ::PotashnikMCharFreqSEQ(const InType &in) {
10+
SetTypeOfTask(GetStaticTypeOfTask());
11+
GetInput() = in;
12+
GetOutput() = 0;
13+
}
14+
15+
bool PotashnikMCharFreqSEQ::ValidationImpl() {
16+
return !std::get<0>(GetInput()).empty();
17+
}
18+
19+
bool PotashnikMCharFreqSEQ::PreProcessingImpl() {
20+
return true;
21+
}
22+
23+
bool PotashnikMCharFreqSEQ::RunImpl() {
24+
auto &input = GetInput();
25+
std::string str = std::get<0>(input);
26+
char chr = std::get<1>(input);
27+
28+
int string_size = static_cast<int>(str.size());
29+
int res = 0;
30+
for (int i = 0; i < string_size; i++) {
31+
if (str[i] == chr) {
32+
res++;
33+
}
34+
}
35+
GetOutput() = res;
36+
37+
return true;
38+
}
39+
40+
bool PotashnikMCharFreqSEQ::PostProcessingImpl() {
41+
return true;
42+
}
43+
44+
} // namespace potashnik_m_char_freq
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"tasks_type": "processes",
3+
"tasks": {
4+
"mpi": "enabled",
5+
"seq": "enabled"
6+
}
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
InheritParentConfig: true
2+
3+
Checks: >
4+
-modernize-loop-convert,
5+
-cppcoreguidelines-avoid-goto,
6+
-cppcoreguidelines-avoid-non-const-global-variables,
7+
-misc-use-anonymous-namespace,
8+
-modernize-use-std-print,
9+
-modernize-type-traits
10+
11+
CheckOptions:
12+
- key: readability-function-cognitive-complexity.Threshold
13+
value: 50 # Relaxed for tests
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#include <gtest/gtest.h>
2+
#include <stb/stb_image.h>
3+
4+
#include <array>
5+
#include <cstddef>
6+
#include <string>
7+
#include <tuple>
8+
9+
#include "potashnik_m_char_freq/common/include/common.hpp"
10+
#include "potashnik_m_char_freq/mpi/include/ops_mpi.hpp"
11+
#include "potashnik_m_char_freq/seq/include/ops_seq.hpp"
12+
#include "util/include/func_test_util.hpp"
13+
#include "util/include/util.hpp"
14+
15+
namespace potashnik_m_char_freq {
16+
17+
class PotashnikMCharFreqFuncTests : public ppc::util::BaseRunFuncTests<InType, OutType, TestType> {
18+
public:
19+
static std::string PrintTestParam(const TestType &test_param) {
20+
return std::to_string(test_param);
21+
}
22+
23+
protected:
24+
void SetUp() override {
25+
TestType params = std::get<static_cast<std::size_t>(ppc::util::GTestParamIndex::kTestParams)>(GetParam());
26+
std::string str;
27+
char chr = 0;
28+
29+
int seed = params;
30+
31+
// Generating character
32+
chr = static_cast<char>('a' + (seed % 26));
33+
34+
// Generating string
35+
for (int i = 0; i < params; i++) {
36+
char c = static_cast<char>('a' + ((i * 7 + 13 + seed / 2) % 26));
37+
str += c;
38+
}
39+
40+
input_data_ = std::make_tuple(str, chr);
41+
}
42+
43+
bool CheckTestOutputData(OutType &output_data) final {
44+
int res = 0;
45+
std::string str = std::get<0>(input_data_);
46+
char chr = std::get<1>(input_data_);
47+
48+
int string_size = static_cast<int>(str.size());
49+
for (int i = 0; i < string_size; i++) {
50+
if (str[i] == chr) {
51+
res++;
52+
}
53+
}
54+
55+
return (res == output_data);
56+
}
57+
58+
InType GetTestInputData() final {
59+
return input_data_;
60+
}
61+
62+
private:
63+
InType input_data_;
64+
};
65+
66+
namespace {
67+
68+
TEST_P(PotashnikMCharFreqFuncTests, MatmulFromPic) {
69+
ExecuteTest(GetParam());
70+
}
71+
72+
const std::array<TestType, 10> kTestParam = {1, 5, 10, 20, 100, 1000, 2000, 5000, 10000, 20000};
73+
74+
const auto kTestTasksList = std::tuple_cat(
75+
ppc::util::AddFuncTask<PotashnikMCharFreqMPI, InType>(kTestParam, PPC_SETTINGS_potashnik_m_char_freq),
76+
ppc::util::AddFuncTask<PotashnikMCharFreqSEQ, InType>(kTestParam, PPC_SETTINGS_potashnik_m_char_freq));
77+
78+
const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList);
79+
80+
const auto kPerfTestName = PotashnikMCharFreqFuncTests::PrintFuncTestName<PotashnikMCharFreqFuncTests>;
81+
82+
INSTANTIATE_TEST_SUITE_P(PicMatrixTests, PotashnikMCharFreqFuncTests, kGtestValues, kPerfTestName);
83+
84+
} // namespace
85+
86+
} // namespace potashnik_m_char_freq

0 commit comments

Comments
 (0)