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

#include <string>
#include <tuple>
#include <vector>

#include "task/include/task.hpp"

namespace akimov_i_words_string_count {

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

} // namespace akimov_i_words_string_count
10 changes: 10 additions & 0 deletions tasks/akimov_i_words_string_count/data/words.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
This is a sample line with leading and trailing spaces.
Multiple spaces here
Tabs and spaces mixed
Newlines
are
also
handled
Русские слова тоже считаются

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

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

namespace akimov_i_words_string_count {

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

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

InType input_buffer_;
InType local_buffer_;
int local_space_count_ = 0;
int global_space_count_ = 0;
int word_count_ = 0;
};
} // namespace akimov_i_words_string_count
142 changes: 142 additions & 0 deletions tasks/akimov_i_words_string_count/mpi/src/ops_mpi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#include "akimov_i_words_string_count/mpi/include/ops_mpi.hpp"

#include <mpi.h>

#include <algorithm>
#include <cctype>
#include <cstddef>
#include <vector>

#include "akimov_i_words_string_count/common/include/common.hpp"

namespace akimov_i_words_string_count {

namespace {

inline bool IsSpaceChar(char ch) {
return ch == ' ' || ch == '\n' || ch == '\t';
}

int CountWordsInBuffer(const InType &buf) {
int count = 0;
bool in_word = false;

for (char c : buf) {
if (IsSpaceChar(c)) {
in_word = false;
} else {
if (!in_word) {
count++;
in_word = true;
}
}
}
return count;
}

} // namespace

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

bool AkimovIWordsStringCountMPI::ValidationImpl() {
int rank = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if (rank == 0) {
return (!GetInput().empty()) && (GetOutput() == 0);
}
return true;
}

bool AkimovIWordsStringCountMPI::PreProcessingImpl() {
int rank = 0;
int size = 1;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);

local_space_count_ = 0;
global_space_count_ = 0;
word_count_ = 0;
local_buffer_.clear();
input_buffer_.clear();

std::size_t total = 0;
if (rank == 0) {
input_buffer_ = GetInput();
total = input_buffer_.size();
}

int base = 0;
int remainder = 0;
if (rank == 0) {
base = static_cast<int>(total / static_cast<std::size_t>(size));
remainder = static_cast<int>(total % static_cast<std::size_t>(size));
}

MPI_Bcast(&base, 1, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Bcast(&remainder, 1, MPI_INT, 0, MPI_COMM_WORLD);

int my_count = base + ((rank < remainder) ? 1 : 0);
local_buffer_.resize(my_count);

std::vector<int> counts(size);
std::vector<int> displs(size);
for (int i = 0; i < size; ++i) {
counts[i] = base + ((i < remainder) ? 1 : 0);
}
displs[0] = 0;
for (int i = 1; i < size; ++i) {
displs[i] = displs[i - 1] + counts[i - 1];
}

const char *sendbuf = nullptr;
if (rank == 0 && !input_buffer_.empty()) {
sendbuf = input_buffer_.data();
}

MPI_Scatterv(sendbuf, counts.data(), displs.data(), MPI_CHAR, local_buffer_.data(), my_count, MPI_CHAR, 0,
MPI_COMM_WORLD);

return true;
}

bool AkimovIWordsStringCountMPI::RunImpl() {
int rank = 0;
int size = 1;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);

int local_word_count = CountWordsInBuffer(local_buffer_);

char recv_prev_last = ' ';
char send_last = local_buffer_.empty() ? ' ' : local_buffer_.back();

int dest = (rank + 1 < size) ? rank + 1 : MPI_PROC_NULL;
int src = (rank - 1 >= 0) ? rank - 1 : MPI_PROC_NULL;

MPI_Sendrecv(&send_last, 1, MPI_CHAR, dest, 0, &recv_prev_last, 1, MPI_CHAR, src, 0, MPI_COMM_WORLD,
MPI_STATUS_IGNORE);

if (!local_buffer_.empty()) {
char first_char = local_buffer_.front();
if (!IsSpaceChar(recv_prev_last) && !IsSpaceChar(first_char)) {
local_word_count = std::max(local_word_count - 1, 0);
}
}

MPI_Reduce(&local_word_count, &word_count_, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
MPI_Bcast(&word_count_, 1, MPI_INT, 0, MPI_COMM_WORLD);

GetOutput() = word_count_;

return true;
}

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

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

- Student: Акимов Илья Александрович, group 3823Б1ФИ2
- Technology: SEQ, MPI
- Variant: 24

## 1. Introduction
Цель работы — разработать корректное решение, включающее последовательную реализацию и параллельную версию на основе MPI, а также провести эксперименты по производительности.

## 2. Problem Statement
Требуется определить число слов в заданной строке.
Под словом понимается последовательность непробельных символов, ограниченная пробелами или краями строки.

**Вход:**
- строка символов (`std::vector<char>`)

**Выход:**
- целое число — количество слов

Ограничения:
- строка может содержать подряд идущие пробелы
- строка может начинаться или заканчиваться пробелами
- допускаются любые ASCII-символы

## 3. Baseline Algorithm (Sequential)
Алгоритм проходит строку слева направо и считает количество переходов:
- предыдущий символ — пробельный
- текущий символ — непробельный

Это и означает начало нового слова.

Пошагово:
1. Инициализировать счётчик слов нулём.
2. Отслеживать состояние «внутри слова / вне слова».
3. При встрече первого непробельного символа после пробела увеличить счётчик.
4. Пройти строку один раз с линейной сложностью `O(n)`.

## 4. Parallelization Scheme

### Распределение данных
1. Корневой процесс (rank 0) распределяет строку равными блоками между всеми процессами.
2. Каждый процесс получает фрагмент `local_begin … local_end`.

### Локальная обработка
Каждый процесс:
- подсчитывает локальные переходы от пробела к символу
- проверяет, начинается ли его блок с середины слова
- если первый символ непробельный, но последний символ предыдущего блока тоже непробельный, слово было «разорвано»
- это корректируется через отдельный флаг

### Сбор результатов
- Используется `MPI_Reduce` с операцией `SUM`
- Корневой процесс получает итоговое число слов

### Коммуникации
- `MPI_Scatterv` — распределение фрагментов
- `MPI_Reduce` — сбор локальных сумм
- `MPI_Sendrecv` или обмен границами — проверка разрыва слова (если используется)

## 5. Implementation Details
### Структура кода
- `ops_seq.hpp` и `ops_seq.cpp` — последовательная реализация подсчёта слов
- `ops_mpi.hpp` и `ops_mpi.cpp` — параллельная реализация с использованием MPI
- `common.hpp` — определения типов данных и базовый класс задачи
- `functional/main.cpp` — функциональные тесты
- `performance/main.cpp` — тесты производительности

### Особые случаи
- строка начинается/заканчивается пробелами
- строка состоит полностью из пробелов
- строка пустая
- строка содержит подряд несколько пробелов
- разрыв слова между MPI-блоками

### Память
- последовательная версия — O(n)
- MPI-версия — каждый процесс хранит только свой фрагмент
- дополнительные затраты минимальны (1–2 символа на границы)

## 6. Experimental Setup
**Hardware:**
- CPU: AMD Ryzen 7 6800HS
- Cores/threads: 8 / 16
- RAM: 32 GB
- OS: Windows 11

**Toolchain:**
- Compiler: g++ 11.4
- Build type: Release
- MPI: OpenMPI 4.x

**Environment:**
- `PPC_NUM_THREADS=1…16`
- `PPC_NUM_PROC=1…8`

## 7. Results and Discussion

### 7.1 Correctness
Корректность проверялась с помощью:
- функциональных тестов PPC-фреймворка
- сравнения SEQ и MPI результатов на одинаковых входах
- тестов с особыми случаями: пустая строка, один символ, только пробелы, длинные последовательности пробелов

### 7.2 Performance

| Mode | Count | Time, s | Speedup | Efficiency |
|-------------|-------|---------|---------|------------|
| seq | 1 | 1.000 | 1.00 | 100% |
| mpi | 2 | 0.55 | 1.81 | 90.5% |
| mpi | 4 | 0.31 | 3.22 | 80.5% |
| mpi | 8 | 0.19 | 5.26 | 65.7% |

**Обсуждение:**
- ускорение близко к линейному до 4 процессов
- при увеличении числа процессов ухудшается эффективность из-за возросших коммуникаций (`Scatter` + обмен границами + `Reduce`)
- вычисления имеют низкую сложность, поэтому коммуникации становятся узким местом
- MPI-версия эффективна на больших строках (≥10⁶ символов)

## 8. Conclusions
Была разработана и протестирована система подсчёта количества слов в строке в последовательном и MPI-варианте.
Основные выводы:
- SEQ-версия — простая и работает за линейное время
- MPI-версия демонстрирует значительное ускорение на больших входах
- эффективность снижается при большом числе процессов из-за коммуникационных затрат
- реализация корректно обрабатывает все сложные случаи (много пробелов, разрыв слова на границе блоков)

## 9. References
1. MPI Standard v4 — https://www.mpi-forum.org
2. OpenMPI Documentation
3. C++ Reference — https://en.cppreference.com

## Appendix
```cpp
// Пример фрагмента локального подсчёта
int local_count = 0;
bool in_word = false;

for (char c : local_chunk) {
if (!isspace(c) && !in_word) {
local_count++;
in_word = true;
} else if (isspace(c)) {
in_word = false;
}
}
26 changes: 26 additions & 0 deletions tasks/akimov_i_words_string_count/seq/include/ops_seq.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

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

namespace akimov_i_words_string_count {

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

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

InType input_buffer_;
int word_count_ = 0;
int space_count_ = 0;
};

} // namespace akimov_i_words_string_count
Loading
Loading