# Минимальное значение элементов матрицы
- Студент: Каур Андрей Михайлович
- Технология: SEQ | MPI
- Вариант: __
## 1. Введение
Поиск минимального элемента матрицы является одной из базовых операций линейной алгебры и важным компонентом во множестве алгоритмов обработки данных, компьютерной графики и др.
В рамках этого исследования проводится сравнительный анализ производительности последовательной и параллельной MPI-реализаций данного алгоритма.
## 2. Постановка задачи
**Цель работы:**
Реализовать последовательную и MPI-параллельную версии алгоритма поиска минимального элемента матрицы, провести их сравнение и анализ эффективности.
**Определение задачи:**
Для матрицы `A` размера `m × n` необходимо определить значение: `min(A) = min(A[i][j])`, где `0 <= i < n`, `0 <= j < m`.
**Ограничения:**
- Матрица должна быть представлена как вектор целых чисел.
- Размеры матрицы могут быть большими.
- Корректность должна сохраняться при любых значениях элементов.
- Результаты SEQ и MPI версий должны совпадать.
## 3. Алгоритм (Последовательная версия)
**Входные данные:** количество строк `rows`, количество колонок `columns`, вектор размера `rows * columns`.
**Выходные данные:** целое число - минимальный элемент матрицы.
**Алгоритм**:
1. Получить входной вектор `matrix`.
2. Присвоить переменной `min_val` значение первого элемента вектора.
3. В цикле пройтись по всем оставшимся элементам, начиная со второго:
- Записать в `min_val` минимальное значение из текущего элемента и `min_val`.
4. Вернуть значение `min_val`.
**Сложность**: O(n), где `n` - размер вектора `matrix`.
### Код последовательной версии алгоритма
```cpp
bool KaurAMinMatrixSEQ::RunImpl() {
auto &matrix = std::get<2>(GetInput());
int min_val = matrix[0];
for (size_t i = 1; i < matrix.size(); i++) {
min_val = std::min(min_val, matrix[i]);
}
GetOutput() = min_val ;
return true ;
}Алгоритм параллельного вычисления минимального элемента матрицы реализует распределение данных между процессами с последующей редукцией результатов. Процессы работают с определёнными блоками элементов матрицы, что обеспечивает равномерную нагрузку.
- Инициализация: Все процессы запускаются в коммуникаторе MPI_COMM_WORLD, определяется общее количество процессов и ранг текущего процесса.
- Распределение данных: Нулевой процесс загружает входную матрицу и вычисляет параметры для распределения данных:
- Определяет размер каждого блока:
part_size = matrix_size / world_size. - Распределяет остаточные элементы: первые
remainderпроцессов получают по одному дополнительному элементу, если размер матрицы нацело не делится на количество процессов. - Формирует векторы
sendcounts(размеры блоков) иdispls(смещения в матрице).
- Определяет размер каждого блока:
- Широковещательная рассылка: Вектор
sendcountsрассылается всем процессам черезMPI_Bcast, позволяя каждому процессу определить размер своего локального буфера. - Распределение данных: Матрица распределяется между процессами при помощи функции
MPI_Scatterv. - Локальные вычисления: Каждый процесс находит минимальный элемент в своем локальном буфере.
- Глобальная редукция: Локальные минимумы со всех процессов объединяются с помощью операции
MPI_Allreduceс параметромMPI_MIN. - Формирование итога: Глобальный минимум становится доступным на всех процессах одновременно и сохраняется в выходные данные.
bool KaurAMinMatrixMPI::RunImpl() {
int world_size = 1;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
int world_rank = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
std::vector<int> matrix;
std::vector<int> sendcounts(world_size);
std::vector<int> displs(world_size);
std::vector<int> local_matrix;
if (world_rank == 0) {
auto &input = GetInput();
matrix = std::get<2>(input);
size_t matrix_size = matrix.size();
size_t part_size = matrix_size / world_size;
size_t remainder = matrix_size % world_size;
int offset = 0;
for (size_t i = 0; std::cmp_less(i, world_size); ++i) {
sendcounts[i] = static_cast<int>(part_size) + (i < remainder ? 1 : 0);
displs[i] = offset;
offset += sendcounts[i];
}
}
MPI_Bcast(sendcounts.data(), world_size, MPI_INT, 0, MPI_COMM_WORLD);
int local_size = sendcounts[world_rank];
local_matrix.resize(local_size);
MPI_Scatterv(world_rank == 0 ? matrix.data() : nullptr, sendcounts.data(), displs.data(), MPI_INT,
local_matrix.data(), local_size, MPI_INT, 0, MPI_COMM_WORLD);
int local_min = INT_MAX;
for (int elem : local_matrix) {
local_min = std::min(local_min, elem);
}
int global_min = 0;
MPI_Allreduce(&local_min, &global_min, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD);
GetOutput() = global_min;
return true;
}| Параметр | Значение |
|---|---|
| ОС | Windows 11 Pro 25H2 |
| Процессор | AMD Ryzen 5 5600X (6 ядер, 12 потоков, 3.70 ГГц) |
| ОЗУ | 16 ГБ DDR4 3400 МГц |
| Компилятор | MSVC 14.43.34808 |
| Версия MPI | Microsoft MPI 10.1.12498.52 |
-
Функциональные тесты:
- Используют заранее подготовленные матрицы размеров от 3×3 до 10×10 с известным минимальным элементом, размещенным в центре матрицы.
- Значения элементов матрицы генерируются случайным образом в диапазоне от минимального элемента до 250.
- Тесты проверяют корректность работы на различных формах матриц (квадратные, прямоугольные, единичные).
-
Тесты производительности:
- Используют матрицу
5000×5000(25 миллионов элементов). - Элементы вычисляются по формуле
i * i + j. - Минимальный элемент имеет фиксированное значение
-1_000_000и размещается в центре матрицы. - Тесты выполняются в двух режимах:
pipelineиtask_run.
- Используют матрицу
Для сравнения производительности использовалась матрица размером 5000×5000.
Вычисления производились по следующим формулам:
Ускорение = Время_последовательное / Время_параллельноеЭффективность = (Ускорение / Количество_процессов) × 100%
| Режим выполнения | Количество процессов | Время выполнения (сек) | Ускорение | Эффективность |
|---|---|---|---|---|
| Последовательный | ||||
| pipeline | 1 | 0.01138 | 1.00x | - |
| task_run | 1 | 0.01138 | 1.00x | - |
| MPI (2 процесса) | ||||
| pipeline | 2 | 0.03687 | 0.31x | 15.5% |
| task_run | 2 | 0.03709 | 0.31x | 15.5% |
| MPI (4 процесса) | ||||
| pipeline | 4 | 0.03823 | 0.30x | 7.5% |
| task_run | 4 | 0.03550 | 0.32x | 8.0% |
| MPI (6 процессов) | ||||
| pipeline | 6 | 0.03614 | 0.31x | 5.2% |
| task_run | 6 | 0.03730 | 0.31x | 5.2% |
-
Результаты функционального тестирования
- Все функциональные тесты успешно пройдены.
- Все реализации (SEQ и MPI) работают корректно и выдают идентичные результаты для всех тестовых наборов.
-
Результаты сравнения производительности
- Параллельные MPI-версии работают медленнее последовательной при всех конфигурациях процессов.
- Максимальное ускорение достигнуто при использовании 4 процессов в режиме
task_run(0.32x от последовательной версии). - Минимальное время выполнения среди MPI-версий: 0.03550 сек (4 процесса, режим
task_run). - Эффективность использования процессов снижается с 15.5% при 2 процессах до 5.2% при 6 процессах.
-
Разработанные алгоритмы корректно решают поставленную задачу, что подтверждается успешным прохождением всех функциональных тестов.
-
Параллельная реализация с использованием MPI показала снижение производительности по сравнению с последовательной версией. MPI-версии работают в среднем в 3.2-3.3 раза медленнее, что связано с высокими накладными расходами на межпроцессное взаимодействие (передача данных, синхронизация, редукция) при относительно небольшой вычислительной нагрузке задачи поиска минимума.
-
При увеличении числа процессов эффективность снижается с 15.5% (2 процесса) до 5.2% (6 процессов). Это объясняется ростом коммуникационных издержек между процессами при неизменном объеме вычислений на процесс.
-
Для данной задачи (поиск минимума в матрице 5000×5000) оптимальным является использование 2-4 процессов, однако даже в этом случае параллельная версия уступает последовательной.
-
Результаты демонстрируют, что не все задачи выигрывают от распараллеливания. Задачи с низкой вычислительной сложностью и интенсивным обменом данными могут показывать отрицательное ускорение из-за преобладания накладных расходов над полезными вычислениями.
- Сысоев А. В. Лекции по курсу «Параллельное программирование для кластерных систем».
- Официальная документация Microsoft MPI — https://learn.microsoft.com/ru-ru/message-passing-interface
- Документация Open MPI — https://www.open-mpi.org/doc/
- C++ Reference — https://en.cppreference.com