Skip to content

Commit 36265d0

Browse files
Маслова Ульяна. Технология SEQ-MPI. Подсчет частоты символа в строке. Вариант 23. (#59)
## Описание - **Задача**: Подсчет частоты символа в строке. - **Вариант**: 23 - **Технология**: SEQ, MPI - **Описание**: Реализовано последовательное и параллельное решение задачи подсчета частоты символа в строке. Последовательная версия проходится по строке, прибавляя к счетчику 1 при нахождении нужного символа. Параллельная версия в свою очередь разбивает строку на части, каждую из которых отдает одному из процессов, для каждого блока находится локальное решение, которое далее суммируется с остальными. Отчет содержит описание алгоритмов, результаты экспериментов, а также выводы о рентабельности распараллеливания в данной задаче. --- ## Чек-лист <!-- Пожалуйста, убедитесь, что следующие пункты выполнены **до** отправки pull request'а и запроса его ревью: --> - [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, являются точными и достоверными <!-- ПРИМЕЧАНИЕ: Ложные сведения в этом чек-листе могут привести к отклонению PR и получению нулевого балла за соответствующую задачу. -->
1 parent 211f806 commit 36265d0

12 files changed

Lines changed: 506 additions & 0 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <string>
5+
#include <tuple>
6+
#include <utility>
7+
8+
#include "task/include/task.hpp"
9+
10+
namespace maslova_u_char_frequency_count {
11+
12+
using InType = std::pair<std::string, char>;
13+
using OutType = size_t;
14+
using TestType = std::tuple<InType, OutType, std::string>;
15+
using BaseTask = ppc::task::Task<InType, OutType>;
16+
17+
} // namespace maslova_u_char_frequency_count
23 Bytes
Loading
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ФИ2",
7+
"task_number": "23"
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 "maslova_u_char_frequency_count/common/include/common.hpp"
4+
#include "task/include/task.hpp"
5+
6+
namespace maslova_u_char_frequency_count {
7+
8+
class MaslovaUCharFrequencyCountMPI : public BaseTask {
9+
public:
10+
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
11+
return ppc::task::TypeOfTask::kMPI;
12+
}
13+
explicit MaslovaUCharFrequencyCountMPI(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 maslova_u_char_frequency_count
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#include "maslova_u_char_frequency_count/mpi/include/ops_mpi.hpp"
2+
3+
#include <mpi.h>
4+
5+
#include <algorithm>
6+
#include <climits>
7+
#include <cstddef>
8+
#include <cstdint>
9+
#include <string>
10+
#include <utility>
11+
#include <vector>
12+
13+
#include "maslova_u_char_frequency_count/common/include/common.hpp"
14+
15+
namespace maslova_u_char_frequency_count {
16+
17+
MaslovaUCharFrequencyCountMPI::MaslovaUCharFrequencyCountMPI(const InType &in) {
18+
SetTypeOfTask(GetStaticTypeOfTask());
19+
GetInput() = in;
20+
GetOutput() = 0;
21+
}
22+
23+
bool MaslovaUCharFrequencyCountMPI::ValidationImpl() {
24+
int rank = 0;
25+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
26+
int flag = 0; // 0 - всё ок, 1 - ошибка
27+
if (rank == 0) {
28+
if (GetInput().first.size() > static_cast<size_t>(INT_MAX)) {
29+
flag = 1;
30+
}
31+
}
32+
MPI_Bcast(&flag, 1, MPI_INT, 0, MPI_COMM_WORLD);
33+
return (flag == 0);
34+
}
35+
36+
bool MaslovaUCharFrequencyCountMPI::PreProcessingImpl() {
37+
return true;
38+
}
39+
40+
bool MaslovaUCharFrequencyCountMPI::RunImpl() {
41+
int rank = 0;
42+
int proc_size = 0;
43+
MPI_Comm_rank(MPI_COMM_WORLD, &rank); // id процесса
44+
MPI_Comm_size(MPI_COMM_WORLD, &proc_size); // количество процессов
45+
46+
std::string input_string;
47+
char input_char = 0;
48+
size_t input_str_size = 0;
49+
50+
if (rank == 0) {
51+
input_string = GetInput().first;
52+
input_char = GetInput().second;
53+
input_str_size = input_string.size(); // получили данные
54+
}
55+
56+
uint64_t size_for_mpi = 0;
57+
if (rank == 0) {
58+
size_for_mpi = static_cast<uint64_t>(input_str_size); // явное приведение перед передачей
59+
}
60+
61+
MPI_Bcast(&size_for_mpi, 1, MPI_UINT64_T, 0, MPI_COMM_WORLD); // отправляем размер строки
62+
63+
if (rank != 0) {
64+
input_str_size = static_cast<size_t>(size_for_mpi); // возращаем обратно для удобного использования в дальнейшем
65+
}
66+
67+
if (input_str_size == 0) {
68+
GetOutput() = 0; // ставим для всех процессов
69+
return true;
70+
}
71+
72+
MPI_Bcast(&input_char, 1, MPI_CHAR, 0, MPI_COMM_WORLD); // отправляем нужный символ
73+
74+
std::vector<int> send_counts(proc_size); // здесь размеры всех порций
75+
std::vector<int> displs(proc_size); // смещения
76+
if (rank == 0) {
77+
size_t part = input_str_size / proc_size;
78+
size_t rem = input_str_size % proc_size;
79+
for (size_t i = 0; std::cmp_less(i, proc_size); ++i) {
80+
send_counts[i] = static_cast<int>(part + (i < rem ? 1 : 0)); // общий размер, включающий остаток, если он входит
81+
}
82+
displs[0] = 0;
83+
for (size_t i = 1; std::cmp_less(i, proc_size); ++i) {
84+
displs[i] = displs[i - 1] + send_counts[i - 1];
85+
}
86+
}
87+
88+
MPI_Bcast(send_counts.data(), proc_size, MPI_INT, 0, MPI_COMM_WORLD); // отправляем размеры порций
89+
std::vector<char> local_str(send_counts[rank]);
90+
MPI_Scatterv((rank == 0) ? input_string.data() : nullptr, send_counts.data(), displs.data(), MPI_CHAR,
91+
local_str.data(), static_cast<int>(local_str.size()), MPI_CHAR, 0, MPI_COMM_WORLD // распределяем данные
92+
);
93+
94+
size_t local_count = std::count(local_str.begin(), local_str.end(), input_char);
95+
auto local_count_for_mpi = static_cast<uint64_t>(local_count);
96+
uint64_t global_count = 0;
97+
MPI_Allreduce(&local_count_for_mpi, &global_count, 1, MPI_UINT64_T, MPI_SUM,
98+
MPI_COMM_WORLD); // собрали данные со всех процессов
99+
100+
GetOutput() = static_cast<size_t>(global_count); // вывели результат, при этом приведя его к нужному нам типу
101+
102+
return true;
103+
}
104+
105+
bool MaslovaUCharFrequencyCountMPI::PostProcessingImpl() {
106+
return true;
107+
}
108+
109+
} // namespace maslova_u_char_frequency_count
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Подсчет частоты символа в строке
2+
3+
- Student: Маслова Ульяна Александровна, group 3823Б1ФИ2
4+
- Technology: SEQ | MPI
5+
- Variant: 23
6+
7+
## 1. Introduction
8+
Проблема: Последовательный подсчет частоты символов в строках большого размера является медленным.
9+
Задача: Ускорить этот процесс с помощью параллельных вычислений на MPI.
10+
Ожидаемый результат: Значительное сокращение времени выполнения по сравнению с последовательной версией.
11+
12+
## 2. Problem Statement
13+
Нужно найти число вхождений символа input_char в строке input_str.
14+
- InPut: Пара (std::string, char).
15+
- OutPut: Целое число (size_t).
16+
17+
## 3. Baseline Algorithm (Sequential)
18+
Проход по строке в цикле с увеличением счетчика при нахождении искомого символа. Алгоритм имеет линейную временную сложность O(N), где N — длина строки.
19+
20+
## 4. Parallelization Scheme
21+
Процесс с рангом 0 делит исходную строку на P (число процессов) частей. Далее ранг 0 рассылает каждому процессу его фрагмент строки. Каждый процесс независимо считает символы в своей части. Локальные счетчики суммируются на ранге 0.
22+
23+
## 5. Implementation Details
24+
- common: Определяет общие типы данных (InType, OutType).
25+
- seq: Содержит простую последовательную реализацию алгоритма.
26+
- mpi: Содержит параллельную MPI-реализацию алгоритма.
27+
- tests: Включает два набора тестов: functional для проверки корректности и performance для замера скорости.
28+
Максимальная длинна строки, которая может быть обработана программой - 2<sup>31</sup> - 1, что составляет 2 147 483 647 символов.
29+
30+
## 6. Experimental Setup
31+
- Аппаратное обеспечение: AMD Ryzen 7 7840HS (8 ядер, 16 логических процессоров, базовая частота 3,80 ГГц)
32+
- ОЗУ — 16 ГБ
33+
- Операционная система: Windows 11
34+
- Компилятор: g++
35+
- Тип сборки: Release
36+
37+
## 7. Results and Discussion
38+
39+
### 7.1 Correctness
40+
Корректность проверялась на строках различной длинны, а также различного типа (пустые, из одного слова, только из букв, смешанные и т.д.)
41+
42+
### 7.2 Performance
43+
44+
Тест на данных, состоящих из 100 000 000 символов:
45+
46+
| Mode | Count | Time, s | Speedup | Efficiency |
47+
|------|-------|-----------|---------|------------|
48+
| seq | 1 | 0.07242 | 1.00 | N/A |
49+
| mpi | 2 | 0.04439 | 1.63 | 81.5% |
50+
| mpi | 4 | 0.03100 | 2.34 | 58.5% |
51+
| mpi | 8 | 0.03050 | 2.37 | 29.6% |
52+
53+
## 8. Conclusions
54+
Мы видим значительное повышение производительности. С увеличением числа процессов время выполнения сокращается, в связи с этим мы имеем ускорение 2.34 на 4 процессах. В свою очередь эффективность падает с ростом числа процессов, так как накладные расходы на коммуникацию MPI на 8 процессах начинают перевешивать выгоду от параллелизма.
55+
56+
## 9. References
57+
1. Лекции и практики курса "Параллельное программирование"
58+
59+
## Appendix (Optional)
60+
```cpp
61+
bool MaslovaUCharFrequencyCountMPI::RunImpl() {
62+
int rank = 0;
63+
int proc_size = 0;
64+
MPI_Comm_rank(MPI_COMM_WORLD, &rank); // id процесса
65+
MPI_Comm_size(MPI_COMM_WORLD, &proc_size); // количество процессов
66+
67+
std::string input_string;
68+
char input_char = 0;
69+
size_t input_str_size = 0;
70+
71+
if (rank == 0) {
72+
input_string = GetInput().first;
73+
input_char = GetInput().second;
74+
input_str_size = input_string.size(); // получили данные
75+
}
76+
77+
uint64_t size_for_mpi = 0;
78+
if (rank == 0) {
79+
size_for_mpi = static_cast<uint64_t>(input_str_size); // явное приведение перед передачей
80+
}
81+
82+
MPI_Bcast(&size_for_mpi, 1, MPI_UINT64_T, 0, MPI_COMM_WORLD); // отправляем размер строки
83+
84+
if (rank != 0) {
85+
input_str_size = static_cast<size_t>(size_for_mpi); // возращаем обратно для удобного использования в дальнейшем
86+
}
87+
88+
if (input_str_size == 0) {
89+
GetOutput() = 0; // ставим для всех процессов
90+
return true;
91+
}
92+
93+
MPI_Bcast(&input_char, 1, MPI_CHAR, 0, MPI_COMM_WORLD); // отправляем нужный символ
94+
95+
std::vector<int> send_counts(proc_size); // здесь размеры всех порций
96+
std::vector<int> displs(proc_size); // смещения
97+
if (rank == 0) {
98+
size_t part = input_str_size / proc_size;
99+
size_t rem = input_str_size % proc_size;
100+
for (size_t i = 0; std::cmp_less(i, proc_size); ++i) {
101+
send_counts[i] = static_cast<int>(part + (i < rem ? 1 : 0)); // общий размер, включающий остаток, если он входит
102+
}
103+
displs[0] = 0;
104+
for (size_t i = 1; std::cmp_less(i, proc_size); ++i) {
105+
displs[i] = displs[i - 1] + send_counts[i - 1];
106+
}
107+
}
108+
109+
MPI_Bcast(send_counts.data(), proc_size, MPI_INT, 0, MPI_COMM_WORLD); // отправляем размеры порций
110+
std::vector<char> local_str(send_counts[rank]);
111+
MPI_Scatterv((rank == 0) ? input_string.data() : nullptr, send_counts.data(), displs.data(), MPI_CHAR,
112+
local_str.data(), static_cast<int>(local_str.size()), MPI_CHAR, 0, MPI_COMM_WORLD // распределяем данные
113+
);
114+
115+
size_t local_count = std::count(local_str.begin(), local_str.end(), input_char);
116+
auto local_count_for_mpi = static_cast<uint64_t>(local_count);
117+
uint64_t global_count = 0;
118+
MPI_Allreduce(&local_count_for_mpi, &global_count, 1, MPI_UINT64_T, MPI_SUM,
119+
MPI_COMM_WORLD); // собрали данные со всех процессов
120+
121+
GetOutput() = static_cast<size_t>(global_count); // вывели результат, при этом приведя его к нужному нам типу
122+
123+
return true;
124+
}
125+
126+
bool MaslovaUCharFrequencyCountMPI::PostProcessingImpl() {
127+
return true;
128+
}
129+
```
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
3+
#include "maslova_u_char_frequency_count/common/include/common.hpp"
4+
#include "task/include/task.hpp"
5+
6+
namespace maslova_u_char_frequency_count {
7+
8+
class MaslovaUCharFrequencyCountSEQ : public BaseTask {
9+
public:
10+
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
11+
return ppc::task::TypeOfTask::kSEQ;
12+
}
13+
explicit MaslovaUCharFrequencyCountSEQ(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 maslova_u_char_frequency_count
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include "maslova_u_char_frequency_count/seq/include/ops_seq.hpp"
2+
3+
#include <climits>
4+
#include <cstddef>
5+
#include <string>
6+
7+
#include "maslova_u_char_frequency_count/common/include/common.hpp"
8+
9+
namespace maslova_u_char_frequency_count {
10+
11+
MaslovaUCharFrequencyCountSEQ::MaslovaUCharFrequencyCountSEQ(const InType &in) {
12+
SetTypeOfTask(GetStaticTypeOfTask());
13+
GetInput() = in;
14+
GetOutput() = 0;
15+
}
16+
17+
bool MaslovaUCharFrequencyCountSEQ::ValidationImpl() {
18+
return GetInput().first.size() <= static_cast<size_t>(INT_MAX);
19+
}
20+
21+
bool MaslovaUCharFrequencyCountSEQ::PreProcessingImpl() {
22+
return true;
23+
}
24+
25+
bool MaslovaUCharFrequencyCountSEQ::RunImpl() {
26+
std::string &input_string = GetInput().first;
27+
char input_char = GetInput().second; // получили данные
28+
size_t frequency_count = 0;
29+
30+
for (const char c : input_string) {
31+
if (c == input_char) {
32+
frequency_count++;
33+
}
34+
}
35+
36+
GetOutput() = frequency_count; // отправили данные
37+
return true;
38+
}
39+
40+
bool MaslovaUCharFrequencyCountSEQ::PostProcessingImpl() {
41+
return true;
42+
}
43+
44+
} // namespace maslova_u_char_frequency_count
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

0 commit comments

Comments
 (0)