Skip to content
Open
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/sokolov_k_min_val_matrix/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 k_sokolov_min_val_matrix {

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

} // namespace k_sokolov_min_val_matrix
9 changes: 9 additions & 0 deletions tasks/sokolov_k_min_val_matrix/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"student": {
"first_name": "Кирилл",
"group_number": "3823Б1ПР4",
"last_name": "Соколов",
"middle_name": "Денисович",
"task_number": "1"
}
}
29 changes: 29 additions & 0 deletions tasks/sokolov_k_min_val_matrix/mpi/include/ops_mpi.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once

#include <vector>

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

namespace k_sokolov_min_val_matrix {

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

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

std::vector<int> matrix_;
int rows_ = 0;
int cols_ = 0;
int min_val_ = 0;
};

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

#include <mpi.h>

#include <algorithm>
#include <cstddef>
#include <limits>
#include <vector>

#include "sokolov_k_min_val_matrix/common/include/common.hpp"

namespace k_sokolov_min_val_matrix {

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

bool SokolovKMinValMatrixMPI::ValidationImpl() {
return GetInput() > 0;
}

bool SokolovKMinValMatrixMPI::PreProcessingImpl() {
int n = GetInput();
if (n <= 0) {
rows_ = 0;
cols_ = 0;
return true;
}
rows_ = n;
cols_ = n;

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

if (rank == 0) {
matrix_.resize(static_cast<std::size_t>(n) * n);
for (int i = 0; i < n * n; i++) {
matrix_[i] = i + 1;
}
}

return true;
}

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

int total = rows_ * cols_;

std::vector<int> send_counts(size);
std::vector<int> displs(size);

int base_count = total / size;
int remainder = total % size;

for (int i = 0; i < size; i++) {
send_counts[i] = base_count + (i < remainder ? 1 : 0);
displs[i] = (i == 0) ? 0 : displs[i - 1] + send_counts[i - 1];
}

std::vector<int> local_data(send_counts[rank]);
MPI_Scatterv(matrix_.data(), send_counts.data(), displs.data(), MPI_INT, local_data.data(), send_counts[rank],
MPI_INT, 0, MPI_COMM_WORLD);

int local_min = std::numeric_limits<int>::max();
if (!local_data.empty()) {
local_min = *std::ranges::min_element(local_data);
}

int global_min = 0;
MPI_Allreduce(&local_min, &global_min, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD);
min_val_ = global_min;

return true;
}

bool SokolovKMinValMatrixMPI::PostProcessingImpl() {
GetOutput() = min_val_;
return true;
}

} // namespace k_sokolov_min_val_matrix
239 changes: 239 additions & 0 deletions tasks/sokolov_k_min_val_matrix/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# Минимальное значение элементов матрицы

- Студент: Соколов Кирилл Денисович, группа 3823Б1ПР4
- Технология: SEQ, MPI
- Вариант: 14

## 1. Введение

Данная лабораторная работа посвящена поиску минимального элемента в матрице с использованием технологии MPI.
Целью является разработка последовательного и параллельного алгоритмов, сравнение их производительности и анализ
масштабируемости.

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

## 2. Постановка задачи

**Определение.** По заданной квадратной матрице размера N x N требуется найти минимальный элемент среди всех ее элементов.

**Входные данные:** целое число N - размер стороны квадратной матрицы (N > 0).

**Выходные данные:** целое число - минимальное значение среди всех элементов матрицы.

**Ограничения:** матрица генерируется детерминированно (элементы от 1 до N*N по возрастанию индекса),
минимальный элемент всегда равен 1.

## 3. Базовый алгоритм

Последовательный алгоритм состоит из следующих этапов:

1. **Валидация:** проверка, что входное значение N положительно.

2. **Предобработка:** формирование матрицы размера N x N, хранящейся в виде одномерного вектора. Элементы
заполняются по правилу: matrix[i] = i + 1 (индексация с нуля).

3. **Вычисление:** проход по всем элементам с помощью std::ranges::min_element для поиска минимального значения.
Сложность O(N^2).

4. **Постобработка:** запись найденного минимума в выход.

Алгоритм тривиален и не требует дополнительных структур данных. Используется линейный просмотр всех N*N элементов.

## 4. Схема распараллеливания (MPI)

Используется горизонтальная схема разбиения: матрица хранится как одномерный массив, который делится на
непрерывные порции между процессами по количеству элементов.

**Распределение данных:**

- Процесс ранга 0 (root) генерирует полную матрицу в PreProcessingImpl.
- В RunImpl данные распределяются с помощью MPI_Scatterv: каждый процесс получает свою порцию элементов (при
неравном делении первые remainder процессов получают на один элемент больше).

**Схема обмена:**

1. Root вызывает MPI_Scatterv, передавая каждому процессу send_counts[i] элементов начиная с смещения displs[i].
2. Каждый процесс находит локальный минимум в своей порции (local_min).
3. MPI_Allreduce с операцией MPI_MIN собирает глобальный минимум на всех процессах.
4. Результат записывается в выход.

**Топология:** двунаправленная связь через коммуникатор MPI_COMM_WORLD. Все процессы участвуют в коллективных
операциях Scatterv и Allreduce.

**Роли рангов:** root (0) владеет исходными данными и участвует в scatter; все ранги выполняют поиск локального
минимума и участвуют в редукции.

## 5. Детали реализации

**Структура кода:**

- `common/include/common.hpp` - общие типы (InType, OutType, BaseTask)
- `seq/include/ops_seq.hpp`, `seq/src/ops_seq.cpp` - последовательная реализация SokolovKMinValMatrixSEQ
- `mpi/include/ops_mpi.hpp`, `mpi/src/ops_mpi.cpp` - параллельная реализация SokolovKMinValMatrixMPI
- `tests/functional/main.cpp` - функциональные тесты
- `tests/performance/main.cpp` - тесты производительности

**Ключевые предположения:** матрица квадратная; при пустой матрице (n <= 0) локальный минимум не вычисляется,
для процессов без данных используется INT_MAX, который не влияет на MPI_MIN при наличии хотя бы одного
валидного элемента.

**Использование памяти:** на root - O(N^2) для хранения матрицы; на каждом процессе - O(N^2 / P) для локальной
порции, где P - число процессов.

## 6. Экспериментальная установка

- **CPU:** Intel Core i5-10400kf
- **Ядра/Потоки:** 6/12
- **ОС:** Windows 10
- **Компилятор:** MSVC 14.44
- **Тип сборки:** Release
- **MPI реализация:** MS-MPI 10.0
- **CMake:** 4.2.0-rc1
- **Tестирование**: Google Test
- **Данные:** матрица 5000 x 5000 (25 млн элементов), генерируется в PreProcessing по формуле
matrix[i] = i + 1

## 7. Результаты

### 7.1 Проверка корректности

Корректность проверяется:

- Функциональными тестами на размерах 1, 2, 3, 5, 7, 10, 50 для SEQ и MPI. Ожидаемый минимум - 1.
- Standalone-тестами на граничные случаи: отклонение нулевого и отрицательного ввода, проверка результата на
матрицах 1x1, 2x2, 100x100, 200x200.
- Совпадением результатов SEQ и MPI при одинаковых входных данных.

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

Базовое время: task_run 0.0564 с, pipeline 0.0959 с.

**task_run**:

| Процессов | Время, с | Ускорение | Эффективность |
|-----------|----------|-----------|---------------|
| 2 | 0.0429 | 1.32 | 65.8% |
| 4 | 0.0294 | 1.92 | 47.9% |
| 6 | 0.0243 | 2.32 | 38.7% |
| 8 | 0.0219 | 2.57 | 32.2% |

**task_pipeline**:

| Процессов | Время, с | Ускорение | Эффективность |
|-----------|----------|-----------|---------------|
| 2 | 0.0850 | 1.13 | 56.4% |
| 4 | 0.0721 | 1.33 | 33.2% |
| 6 | 0.0649 | 1.48 | 24.6% |
| 8 | 0.0628 | 1.53 | 19.1% |

## 8. Выводы

- Реализованы последовательный и параллельный (MPI) алгоритмы поиска минимального элемента в матрице.
- Параллельный алгоритм использует MPI_Scatterv для распределения данных и MPI_Allreduce (MPI_MIN) для сбора результата.
- Task_run измеряет только вычислительное ядро и обычно показывает лучшее ускорение, чем pipeline, где
учитываются накладные расходы на генерацию данных и коммуникацию.
- Ограничения: при большом числе процессов по сравнению с размером матрицы доля коммуникации растет, эффективность падает.

## 9. Источники

1. MPI Forum. MPI: A Message-Passing Interface Standard. Version 4.0. <https://www.mpi-forum.org/docs/>
2. Gropp W., Lusk E., Skjellum A. Using MPI: Portable Parallel Programming with the Message Passing Interface.
3rd ed. MIT Press, 2014.
3. Антонов А.С. Параллельное программирование с использованием технологии MPI: учебное пособие. М.: Изд-во МГУ,
2004.
4. Воеводин В.В., Воеводин Вл.В. Параллельные вычисления. СПб.: БХВ-Петербург, 2002.
5. Гергель В.П. Теория и практика параллельных вычислений: учебное пособие. М.: БИНОМ. Лаборатория знаний, 2007.

## Приложение. Фрагменты кода

### Последовательный алгоритм: PreProcessingImpl и RunImpl

```cpp
bool SokolovKMinValMatrixSEQ::PreProcessingImpl() {
int n = GetInput();
if (n <= 0) {
rows_ = 0;
cols_ = 0;
return true;
}
rows_ = n;
cols_ = n;
matrix_.resize(static_cast<std::size_t>(n) * n);
for (int i = 0; i < n * n; i++) {
matrix_[i] = i + 1;
}
return true;
}

bool SokolovKMinValMatrixSEQ::RunImpl() {
if (matrix_.empty()) {
return true;
}
min_val_ = *std::ranges::min_element(matrix_);
return true;
}
```

### Параллельный алгоритм (MPI): PreProcessingImpl

```cpp
bool SokolovKMinValMatrixMPI::PreProcessingImpl() {
int n = GetInput();
if (n <= 0) {
rows_ = 0;
cols_ = 0;
return true;
}
rows_ = n;
cols_ = n;

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

if (rank == 0) {
matrix_.resize(static_cast<std::size_t>(n) * n);
for (int i = 0; i < n * n; i++) {
matrix_[i] = i + 1;
}
}
return true;
}
```

### Параллельный алгоритм (MPI): RunImpl - распределение и редукция

```cpp
bool SokolovKMinValMatrixMPI::RunImpl() {
int rank = 0;
int size = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);

int total = rows_ * cols_;
std::vector<int> send_counts(size);
std::vector<int> displs(size);

int base_count = total / size;
int remainder = total % size;
for (int i = 0; i < size; i++) {
send_counts[i] = base_count + (i < remainder ? 1 : 0);
displs[i] = (i == 0) ? 0 : displs[i - 1] + send_counts[i - 1];
}

std::vector<int> local_data(send_counts[rank]);
MPI_Scatterv(matrix_.data(), send_counts.data(), displs.data(), MPI_INT,
local_data.data(), send_counts[rank], MPI_INT, 0, MPI_COMM_WORLD);

int local_min = std::numeric_limits<int>::max();
if (!local_data.empty()) {
local_min = *std::ranges::min_element(local_data);
}

int global_min = 0;
MPI_Allreduce(&local_min, &global_min, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD);
min_val_ = global_min;
return true;
}
```
Loading
Loading