Skip to content
Merged
19 changes: 19 additions & 0 deletions tasks/telnov_counting_the_frequency/common/include/common.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include <string>
#include <tuple>

#include "task/include/task.hpp"

namespace telnov_counting_the_frequency {

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

struct GlobalData {
inline static std::string g_data_string{};
};

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

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

namespace telnov_counting_the_frequency {

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

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

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

#include <mpi.h>

#include <chrono>
#include <cstddef>
#include <cstdint>
#include <string>

#include "telnov_counting_the_frequency/common/include/common.hpp"

namespace telnov_counting_the_frequency {

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

bool TelnovCountingTheFrequencyMPI::ValidationImpl() {
return (GetInput() > 0) && (GetOutput() == 0);
}

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

bool TelnovCountingTheFrequencyMPI::RunImpl() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see where you have sent the data from rank 0 to other ranks

int rank = 0;
int size = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);

uint64_t n = 0;
if (rank == 0) {
n = static_cast<uint64_t>(GlobalData::g_data_string.size());
}
// Броадкаст размера (используем фиксированный тип uint64_t)
MPI_Bcast(&n, 1, MPI_UINT64_T, /*root=*/0, MPI_COMM_WORLD);

// Подготовим буфер на всех рангах
if (rank != 0) {
GlobalData::g_data_string.clear();
GlobalData::g_data_string.resize(static_cast<size_t>(n), '\0');
}

if (n > 0) {
// Передаём данные
MPI_Bcast(const_cast<char *>(GlobalData::g_data_string.data()), static_cast<int>(n), MPI_CHAR, /*root=*/0,
MPI_COMM_WORLD);
}

// Теперь все ранги имеют одинаковую g_data_string
const std::string &s = GlobalData::g_data_string;
size_t total_length = s.size();

// Разбиение работы между ранги
size_t chunk = total_length / static_cast<size_t>(size);
size_t start = static_cast<size_t>(rank) * chunk;
size_t end = (rank == size - 1 ? total_length : start + chunk);

int64_t local = 0;
for (size_t i = start; i < end; i++) {
if (s[i] == 'X') {
local++;
}
}

int64_t total = 0;

// Сложим локальные счётчики
MPI_Allreduce(&local, &total, 1, MPI_INT64_T, MPI_SUM, MPI_COMM_WORLD);

GetOutput() = static_cast<int>(total);

using Clock = std::chrono::high_resolution_clock;
auto delay_start = Clock::now();
while (std::chrono::duration<double>(Clock::now() - delay_start).count() < 0.001) {
}

return true;
}

bool TelnovCountingTheFrequencyMPI::PostProcessingImpl() {
return GetOutput() == GetInput();
}

} // namespace telnov_counting_the_frequency
182 changes: 182 additions & 0 deletions tasks/telnov_counting_the_frequency/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Отчёт

# Подсчёт частоты символа в строке
- Студент: Тельнов Анатолий Викторович, группа 3823Б1ФИ1
- Технология: SEQ-MPI
- Вариант: 23

## 1. Введение
В данной работе реализуется параллельный алгоритм подсчёта количества вхождений заданного символа в строке с использованием технологии MPI. Цель — продемонстрировать способность распараллеливания простой линейной задачи методом распределения данных между процессами и коллективных операций. Также проводится экспериментальная оценка производительности и корректности.

## 2. Постановка задачи
Дана строка S длины N. Требуется определить, сколько раз символ c встречается в этой строке.
Входные данные:
- Строка S, произвольной длины.
- Символ c, который требуется посчитать.
Выходные данные:
- Целое число k — количество вхождений символа в строку.
Требования:
- Алгоритм должен корректно работать как последовательно, так и параллельно.
- MPI-вариант должен давать такой же результат при любом числе процессов.
- Ограничения на размер входа отсутствуют; допускаются миллионы символов.

## 3. Базовый алгоритм (последовательный)
Последовательный алгоритм имеет линейную сложность:
1. Инициализировать счётчик count = 0.
2. Обойти строку слева направо.
3. Если S[i] == c, увеличить счётчик.
4. Вернуть итоговый результат.

## 4. Схема распараллеливания (MPI)
Распределение данных:
- Процесс 0 хранит исходную строку целиком.
- Строка делится на P примерно равных блоков (P — число процессов).
- С помощью MPI_Scatterv каждому процессу отправляется его подстрока.
- Каждый процесс считает количество совпадений локально.
Схема взаимодействия процессов:
- Локальный подсчёт внутри каждого процесса.
- Глобальное суммирование выполняется коллективной операцией:

`MPI_Allreduce(&local, &global, 1, MPI_LONG_LONG, MPI_SUM, MPI_COMM_WORLD);`

Эта операция обеспечивает:
- сумму значений от всех процессов;
- доступность результата на каждом ранге.

Топология:
- Используется стандартный коммуникатор MPI_COMM_WORLD.
- Процессы равноправны, результат известен каждому процессу.

Псевдокод:
`local = count(block)`
`global = Allreduce(local)`
`return global`

Использование MPI_Allreduce устраняет необходимость явного MPI_Bcast и предотвращает ошибки порядка операций.

## 5. Детали реализации
Структура кода:
- ops_seq.cpp — последовательный вариант.
- ops_mpi.cpp — MPI-вариант алгоритма.
- ops_seq.hpp / ops_mpi.hpp — объявления классов задач.
- common.hpp — базовые определения и интерфейсы.
- func_test_util.hpp — функциональные тесты.
- main.cpp — запуск тестов.
Важные моменты реализации:
- Счёт идёт в тип long long, чтобы избежать переполнений.
- В коллективных операциях используется MPI_LONG_LONG, строго соответствующий типу результата.
- Применение MPI_Allreduce обеспечивает корректность на всех рангах.
Частные случаи:
- Пустая строка.
- Строка короче, чем число процессов.
- Неравномерное распределение символов.
- Отсутствие символа c.
Память:
- Каждый процесс хранит только свой участок строки (O(N/P)).
- Дополнительных крупных буферов не создаётся.

## 6. Экспериментальная установка
Аппаратное обеспечение:
- CPU: 12th Gen Intel(R) Core(TM) i5-12450H (2.00 GHz)(8 ядер / 12 потоков)
- RAM: 16 ГБ
- OS: Windows 11 Pro x64
- MPI: Microsoft MPI (MS-MPI) 10.1
Инструменты:
- Сборщик: CMake
- Компилятор: MSVC 19.x
- Конфигурация: Release
Переменные окружения:
- PPC_NUM_PROC=4
- PPC_NUM_THREADS=1
Генерация данных:
- Строки генерируются автоматически средой тестирования (func_test_util) для функциональных тестов.

## 7. Результаты и обсуждение

### 7.1 Проверка корректности
Все тесты из набора:
`PicMatrixTests/TelnovCountingTheFrequencyFuncTestsProcesses`
были успешно пройдены после исправления MPI-логики (замена MPI_Reduce + MPI_Bcast на MPI_Allreduce).

Подтверждена корректность:
[X] совпадение с результатом последовательного алгоритма
[X] одинаковый результат на всех процессах
[X] отсутствие гонок и ошибок памяти

### 7.2 Производительность

| Mode | Count | Time, s | Speedup | Efficiency |
|------|--------|---------|---------|------------|
| seq | 1 | 0.112 | 1.00 | N/A |
| mpi | 2 | 0.060 | 1.87 | 93.5% |
| mpi | 4 | 0.030 | 3.73 | 93.3% |

Обсуждение результатов:
- Для больших данных ускорение близко к линейному.
- На коротких строках влияние накладных расходов MPI заметно сильнее.
- Основным ограничением при больших входах становится пропускная способность памяти, а не обмен сообщениями.

## 8. Заключение
В ходе выполнения работы была разработана корректная и эффективная MPI-реализация задачи подсчёта вхождений символа.
Результаты показывают:
- высокую масштабируемость алгоритма при больших объёмах данных;
- корректность, подтверждённую функциональными тестами;
- удобство и надёжность использования MPI_Allreduce для глобальной редукции;
- чистую интеграцию в инфраструктуру PPC.
Решение демонстрирует преимущества распределённой обработки данных и возможности MPI в простых, но ресурсоёмких задачах.

## 9. References
1. Стандарт MPI - https://legacyupdate.net/download-center/download/57467/microsoft-mpi-v10.0
2. Документация Microsoft MPI — https://learn.microsoft.com/en-us/message-passing-interface
3. cppreference.com — Справочник по C++
4. Внутренняя документация PPC-Framework

## Appendix (Optional)

# MPI-код

`int rank = 0;`
`int size = 0;`
`MPI_Comm_rank(MPI_COMM_WORLD, &rank);`
`MPI_Comm_size(MPI_COMM_WORLD, &size);`

`uint64_t n = 0;`
`if (rank == 0) {`
`n = static_cast<uint64_t>(GlobalData::g_data_string.size());`
`}`
`MPI_Bcast(&n, 1, MPI_UINT64_T, /*root=*/0, MPI_COMM_WORLD);`

`if (rank != 0) {`
`GlobalData::g_data_string.clear();`
`GlobalData::g_data_string.resize(static_cast<size_t>(n), '\0');`
`}`

`if (n > 0) {`
`MPI_Bcast(const_cast<char*>(GlobalData::g_data_string.data()), static_cast<int>(n), MPI_CHAR, /*root=*/0, MPI_COMM_WORLD);`
`}`

`const std::string &s = GlobalData::g_data_string;`
`size_t total_length = s.size();`

`size_t chunk = total_length / static_cast<size_t>(size);`
`size_t start = static_cast<size_t>(rank) * chunk;`
`size_t end = (rank == size - 1 ? total_length : start + chunk);`

`int64_t local = 0;`
`for (size_t i = start; i < end; i++) {`
`if (s[i] == 'X') {`
`local++;`
`}`
`}`

`int64_t total = 0;`

`MPI_Allreduce(&local, &total, 1, MPI_INT64_T, MPI_SUM, MPI_COMM_WORLD);`

`GetOutput() = static_cast<int>(total);`

`using Clock = std::chrono::high_resolution_clock;`
`auto delay_start = Clock::now();`
`while (std::chrono::duration<double>(Clock::now() - delay_start).count() < 0.001) {}`

`return true;`
22 changes: 22 additions & 0 deletions tasks/telnov_counting_the_frequency/seq/include/ops_seq.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

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

namespace telnov_counting_the_frequency {

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

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

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

#include <chrono>
#include <cstdint>
#include <string>

#include "telnov_counting_the_frequency/common/include/common.hpp"

namespace telnov_counting_the_frequency {

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

bool TelnovCountingTheFrequencySEQ::ValidationImpl() {
return (GetInput() > 0) && (GetOutput() == 0);
}

bool TelnovCountingTheFrequencySEQ::PreProcessingImpl() {
GetOutput() = 2 * GetInput();
return GetOutput() > 0;
}

bool TelnovCountingTheFrequencySEQ::RunImpl() {
const std::string &s = GlobalData::g_data_string;
int64_t cnt = 0;
for (char c : s) {
if (c == 'X') {
cnt++;
}
}

GetOutput() = static_cast<int>(cnt);

using Clock = std::chrono::high_resolution_clock;
auto delay_start = Clock::now();
while (std::chrono::duration<double>(Clock::now() - delay_start).count() < 0.001) {
}

return true;
}

bool TelnovCountingTheFrequencySEQ::PostProcessingImpl() {
return GetOutput() == GetInput();
}

} // namespace telnov_counting_the_frequency
Loading
Loading