diff --git a/tasks/egorova_l_find_max_val_col_matrix/common/include/common.hpp b/tasks/egorova_l_find_max_val_col_matrix/common/include/common.hpp new file mode 100644 index 000000000..09cabbfc8 --- /dev/null +++ b/tasks/egorova_l_find_max_val_col_matrix/common/include/common.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace egorova_l_find_max_val_col_matrix { +// задала подходящие под мою задачу типы данных +// на вход принимаю матрицу заданную парой векторов +// на выход передается вектор максимальных значений столбцов матрицы +using InType = std::vector>; +using OutType = std::vector; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace egorova_l_find_max_val_col_matrix diff --git a/tasks/egorova_l_find_max_val_col_matrix/info.json b/tasks/egorova_l_find_max_val_col_matrix/info.json new file mode 100644 index 000000000..56a8132d4 --- /dev/null +++ b/tasks/egorova_l_find_max_val_col_matrix/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Егорова", + "last_name": "Лариса", + "middle_name": "Алексеевна", + "group_number": "3823Б1ФИ1", + "task_number": "16" + } +} diff --git a/tasks/egorova_l_find_max_val_col_matrix/mpi/include/ops_mpi.hpp b/tasks/egorova_l_find_max_val_col_matrix/mpi/include/ops_mpi.hpp new file mode 100644 index 000000000..0324cb45b --- /dev/null +++ b/tasks/egorova_l_find_max_val_col_matrix/mpi/include/ops_mpi.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "egorova_l_find_max_val_col_matrix/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace egorova_l_find_max_val_col_matrix { + +class EgorovaLFindMaxValColMatrixMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit EgorovaLFindMaxValColMatrixMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + // Объявления вспомогательных методов + bool RunMPIAlgorithm(); + std::vector GetLocalMatrixPart(int rank, int size, int rows, int cols, int start_col, int local_cols_count); + void SendMatrixPartsToOtherRanks(int size, int rows, int cols); + std::vector PrepareMatrixPartForRank(int dest_rank, int size, int rows, int cols); + static std::vector CalculateLocalMaxima(const std::vector &local_matrix_part, int rows, + int local_cols_count); + static std::vector GatherResults(const std::vector &local_max, int size, int cols); +}; + +} // namespace egorova_l_find_max_val_col_matrix diff --git a/tasks/egorova_l_find_max_val_col_matrix/mpi/src/ops_mpi.cpp b/tasks/egorova_l_find_max_val_col_matrix/mpi/src/ops_mpi.cpp new file mode 100644 index 000000000..b81fa166e --- /dev/null +++ b/tasks/egorova_l_find_max_val_col_matrix/mpi/src/ops_mpi.cpp @@ -0,0 +1,202 @@ +#include "egorova_l_find_max_val_col_matrix/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include + +#include "egorova_l_find_max_val_col_matrix/common/include/common.hpp" + +namespace egorova_l_find_max_val_col_matrix { + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wnull-dereference" +#endif + +EgorovaLFindMaxValColMatrixMPI::EgorovaLFindMaxValColMatrixMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = std::vector(0); +} + +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + +bool EgorovaLFindMaxValColMatrixMPI::ValidationImpl() { + const auto &matrix = GetInput(); + + if (matrix.empty()) { + return true; + } + + if (matrix[0].empty()) { + return true; + } + + const std::size_t cols = matrix[0].size(); + return std::ranges::all_of(matrix, [cols](const auto &row) { return row.size() == cols; }); +} + +bool EgorovaLFindMaxValColMatrixMPI::PreProcessingImpl() { + return true; +} + +bool EgorovaLFindMaxValColMatrixMPI::RunImpl() { + const auto &matrix = GetInput(); + + if (matrix.empty() || matrix[0].empty()) { + GetOutput() = std::vector(); + return true; + } + + return RunMPIAlgorithm(); +} + +bool EgorovaLFindMaxValColMatrixMPI::RunMPIAlgorithm() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + int rows = 0; + int cols = 0; + if (rank == 0) { + rows = static_cast(GetInput().size()); + cols = static_cast(GetInput()[0].size()); + } + + MPI_Bcast(&rows, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&cols, 1, MPI_INT, 0, MPI_COMM_WORLD); + + // Распределение столбцов по процессам + const int cols_per_proc = cols / size; + const int remainder = cols % size; + int start_col = 0; + int local_cols_count = 0; + + if (rank < remainder) { + start_col = rank * (cols_per_proc + 1); + local_cols_count = cols_per_proc + 1; + } else { + start_col = (remainder * (cols_per_proc + 1)) + ((rank - remainder) * cols_per_proc); + local_cols_count = cols_per_proc; + } + + // Получение локальной части матрицы + std::vector local_matrix_part = GetLocalMatrixPart(rank, size, rows, cols, start_col, local_cols_count); + std::vector local_max = CalculateLocalMaxima(local_matrix_part, rows, local_cols_count); + std::vector all_max = GatherResults(local_max, size, cols); + + GetOutput() = all_max; + MPI_Barrier(MPI_COMM_WORLD); + return true; +} + +std::vector EgorovaLFindMaxValColMatrixMPI::GetLocalMatrixPart(int rank, int size, int rows, int cols, + int start_col, int local_cols_count) { + std::vector local_part(static_cast(rows) * static_cast(local_cols_count)); + + if (rank == 0) { + const auto &matrix = GetInput(); + + // Процесс 0 заполняет свою локальную часть + for (int ii = 0; ii < rows; ++ii) { + for (int local_idx = 0; local_idx < local_cols_count; ++local_idx) { + const int global_col = start_col + local_idx; + local_part[(static_cast(ii) * static_cast(local_cols_count)) + local_idx] = + matrix[ii][global_col]; + } + } + + // Отправка частей матрицы другим процессам + SendMatrixPartsToOtherRanks(size, rows, cols); + } else { + // Получение данных от процесса 0 + MPI_Recv(local_part.data(), static_cast(local_part.size()), MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } + + return local_part; +} + +void EgorovaLFindMaxValColMatrixMPI::SendMatrixPartsToOtherRanks(int size, int rows, int cols) { + for (int dest_rank = 1; dest_rank < size; ++dest_rank) { + std::vector dest_part = PrepareMatrixPartForRank(dest_rank, size, rows, cols); + + // Отправка данных процессу-получателю + MPI_Send(dest_part.data(), static_cast(dest_part.size()), MPI_INT, dest_rank, 0, MPI_COMM_WORLD); + } +} + +std::vector EgorovaLFindMaxValColMatrixMPI::PrepareMatrixPartForRank(int dest_rank, int size, int rows, int cols) { + // Вычисление диапазона столбцов для процесса-получателя + const int cols_per_proc = cols / size; + const int remainder = cols % size; + + int dest_start_col = 0; + int dest_cols_count = 0; + + if (dest_rank < remainder) { + dest_start_col = dest_rank * (cols_per_proc + 1); + dest_cols_count = cols_per_proc + 1; + } else { + dest_start_col = (remainder * (cols_per_proc + 1)) + ((dest_rank - remainder) * cols_per_proc); + dest_cols_count = cols_per_proc; + } + + // Подготовка данных для отправки + const auto &matrix = GetInput(); + std::vector dest_part(static_cast(rows) * static_cast(dest_cols_count)); + + for (int ii = 0; ii < rows; ++ii) { + for (int jj = 0; jj < dest_cols_count; ++jj) { + const int global_col = dest_start_col + jj; + dest_part[(static_cast(ii) * static_cast(dest_cols_count)) + jj] = + matrix[ii][global_col]; + } + } + + return dest_part; +} + +std::vector EgorovaLFindMaxValColMatrixMPI::CalculateLocalMaxima(const std::vector &local_matrix_part, + int rows, int local_cols_count) { + std::vector local_max(local_cols_count, std::numeric_limits::min()); + + for (int local_idx = 0; local_idx < local_cols_count; ++local_idx) { + for (int row = 0; row < rows; ++row) { + const int value = local_matrix_part[(row * local_cols_count) + local_idx]; + local_max[local_idx] = std::max(value, local_max[local_idx]); + } + } + + return local_max; +} + +std::vector EgorovaLFindMaxValColMatrixMPI::GatherResults(const std::vector &local_max, int size, int cols) { + const int cols_per_proc = cols / size; + const int remainder = cols % size; + + std::vector all_max(cols, std::numeric_limits::min()); + std::vector recv_counts(size); + std::vector displs(size); + + for (int ii = 0; ii < size; ++ii) { + recv_counts[ii] = (ii < remainder) ? (cols_per_proc + 1) : cols_per_proc; + displs[ii] = (ii == 0) ? 0 : displs[ii - 1] + recv_counts[ii - 1]; + } + + MPI_Allgatherv(local_max.data(), static_cast(local_max.size()), MPI_INT, all_max.data(), recv_counts.data(), + displs.data(), MPI_INT, MPI_COMM_WORLD); + + return all_max; +} + +bool EgorovaLFindMaxValColMatrixMPI::PostProcessingImpl() { + return true; +} + +} // namespace egorova_l_find_max_val_col_matrix diff --git a/tasks/egorova_l_find_max_val_col_matrix/report.md b/tasks/egorova_l_find_max_val_col_matrix/report.md new file mode 100644 index 000000000..a723e1025 --- /dev/null +++ b/tasks/egorova_l_find_max_val_col_matrix/report.md @@ -0,0 +1,365 @@ +# Нахождение наиболее отличающихся по значению соседних элементов вектора + +- Студент: Егорова Лариса Алексеевна, группа 3823Б1ФИ1 +- Технология: SEQ | MPI +- Вариант: 16 + +## 1. Введение +Данная лабораторная работа посвящена решению задачи поиска максимальных элементов в столбцах матрицы. Цель работы: разработать и реализовать два подхода последовательный алгоритм и параллельная реализация с использованием библиотеки MPI для решения задачи, а затем сравнить их и сделать выводы. + +## 2. Постановка задачи +Для заданной матрицы размером M×N необходимо найти вектор максимальных значений для каждого столбца. +Формально: для каждого столбца j найти max(a[i][j]) для всех i от 0 до M-1. + +Входные данные: матрица целых чисел +Выходные данные: вектор максимальных значений для каждого столбца + +Особые случаи: + +- Пустая матрица → пустой вектор +- Матрица с нулевыми столбцами → пустой вектор +- Некорректная матрица (разные длины строк) → ошибка валидации + +## 3. Базовый алгоритм (Последовательный) +Последовательный алгоритм проходит по каждому столбцу матрицы и находит максимальное значение: + +for (j от 0 до N-1): + max_val = MIN_INT + for (i от 0 до M-1): + if matrix[i][j] > max_val: + max_val = matrix[i][j] + result[j] = max_val + +Результатом выполнения алгоритма будет вектор максимальных значений result. + +Сложность алгоритма: O(M×N) + + +## 4. Схема распараллеливания + +### 4.1 Общая архитектура +Алгоритм использует декомпозицию по столбцам — каждый процесс получает свой набор столбцов для независимой обработки. Такой подход обеспечивает максимальное распараллеливание при минимальных коммуникационных затратах. + +### 4.2 Распределение данных +Столбцы распределяются по процессам с адаптивным балансированием нагрузки. Если количество столбцов N не делится нацело на число процессов P, первые R процессов (R = N mod P) получают на один столбец больше. Это гарантирует, что разница в нагрузке между процессами не превышает одного столбца. + +Исходная матрица преобразуется в плоский массив с построчным хранением для эффективной передачи через MPI. + +### 4.3 Коммуникационная схема +Процесс выполняется в три этапа: +- Распространение данных — процесс 0 рассылает размеры матрицы и данные всем процессам через MPI_Bcast +- Параллельные вычисления — каждый процесс находит максимумы в своих столбцах независимо +- Сбор результатов — все процессы обмениваются результатами через MPI_Allgatherv + +### 4.4 Преимущества и ограничения +Преимущества: +- минимальные коммуникации (только 2 передачи) +- идеальная балансировка нагрузки +- простота реализации +- масштабируемость + +Ограничения: +- каждый процесс хранит полную копию матрицы +- ограничение по памяти на одном узле + + +## 5. Детали реализации +Структура кода: +- ..\MPI\ppc-2025-processes-informatics\tasks\egorova_l_find_max_val_col_matrix\seq\src\ops_seq.cpp — последовательная реализация +- ..\MPI\ppc-2025-processes-informatics\tasks\egorova_l_find_max_val_col_matrix\mpi\src\ops_mpi.cpp — MPI реализация + +Тесты: +- ..\MPI\ppc-2025-processes-informatics\tasks\egorova_l_find_max_val_col_matrix\tests\functional\main.cpp — функциональные +- ..\MPI\ppc-2025-processes-informatics\tasks\egorova_l_find_max_val_col_matrix\tests\performance\main.cpp — на производительность + +## 6. Экспериментальная установка + +### 6.1 Оборудование и ПО +- CPU: AMD Ryzen 5 2600 Six-Core Processor 3.40 GHz (6 ядер, 12 потоков) +- RAM: 16GB DDR4 +- ОС: Windows 10 Pro +- Тип сборки: Release + +### 6.2 Функциональное тестирование +Для проверки корректности реализации проведено комплексное функциональное тестирование: + +Базовые случаи: +- Пустая матрица — проверка обработки граничного условия +- Матрица с нулевыми столбцами — валидация обработки вырожденных случаев +- Матрица 1×1 — тестирование минимального размера + +Структурные тесты: +- Один столбец — проверка обработки векторного случая +- Одна строка — тестирование горизонтальной матрицы +- Квадратные матрицы 3×3 и 5×5 — стандартные сценарии + +Семантические тесты: +- Отрицательные значения — проверка корректности сравнения +- Одинаковые значения — тестирование стабильности при равенстве элементов +- Нулевые значения — валидация работы с нулями + +Сценарии расположения максимумов: +- Максимум в первом столбце — проверка граничных условий +- Максимум в последнем столбце — тестирование конечных элементов +- Максимумы в разных столбцах — комплексная проверка распределения + +Неквадратные матрицы: +- Прямоугольные матрицы 2×4 и 3×2 — тестирование асимметричных случаев +- Большая матрица 10×1 — проверка обработки длинных столбцов +- Матрица 1×10 — тестирование длинных строк + +### 6.3 Тестирование производительности +Методология тестирования: + +- Размер тестовой матрицы: 5000×5000 элементов (25 миллионов значений) и 10000х10000 (100 миллионов значений) +- Тип данных: 32-битные целые числа (int) +- Количество процессов: 1, 2, 4, 8 +- Количество запусков: 5 для каждого конфигурации + +Генерация тестовых данных: +- Используется алгоритм детерминированного заполнения +- Последовательное увеличение значений для предсказуемости результатов +- Гарантированное наличие уникальных максимальных значений в каждом столбце + +Метрики производительности: +- Время выполнения (секунды) +- Ускорение относительно последовательной версии +- Эффективность параллелизации +- Потребление памяти + +## 7. Результаты и обсуждение + +### 7.1 Корректность +Реализованные последовательная и MPI-версии алгоритма успешно прошли все функциональные тесты. Проверка корректности осуществлялась через сравнение результатов обеих реализаций с эталонными значениями, вычисленными аналитически. Особое внимание уделялось обработке граничных случаев: пустых матриц, матриц с нулевыми столбцами, матриц минимального размера 1×1. Все тесты подтвердили идентичность результатов параллельной и последовательной версий. + +### 7.2 Проведение тестов +Для оценки производительности алгоритма поиска максимальных значений по столбцам матрицы были проведены тесты на двух размерах матриц: 5000×5000 (25 миллионов значений) и 10000×10000 (100 миллионов значений). Результаты представлены в таблицах: + +1. Матрица 5000×5000 +|Режим |Процессы |Время, с |Ускорение |Эффективность| +|-------|---------|---------|-----------|-------------| +|seq |1 |0.22558 |1.00 |N/A | +|mpi |2 |0.20185 |1.12 |56.0% | +|mpi |4 |0.23712 |0.95 |23.8% | +|mpi |8 |0.29534 |0.76 |9.5% | + +2. Матрица 10000x10000 +|Режим |Процессы |Время, с |Ускорение |Эффективность| +|-------|---------|---------|-----------|-------------| +|seq |1 |0.26388 |1.00 |N/A | +|mpi |2 |0.21299 |1.24 |61.95% | +|mpi |4 |0.25952 |1.02 |25.4% | +|mpi |8 |0.30015 |0.88 |10.99% | + +Ускорение вычислялось по формуле (seq_time / mpi_time) +Эффективность вычислялась по формуле (Ускорение / N) * 100%, где N - количество процессов + +### 7.3 Анализ результатов + +#### 7.3.1 Влияние размера задачи на производительность +Проведя тесты стало заметно значительное улучшение эффективности параллелизации с ростом размера матрицы: + +Для матрицы 5000×5000: +- Лучшая эффективность: 55.87% на 2 процессах + +Для матрицы 10000×10000: +- Лучшая эффективность: 61.95% на 2 процессах (+6.08%) +- Ускорение улучшилось с 1.12× до 1.24× + +#### 7.3.2 Оптимальные конфигурации +Для обеих размерностей наилучшая производительность достигается при использовании 2 процессов MPI: +Матрица 5000×5000: время 0.20186 с, ускорение 1.12× +Матрица 10000×10000: время 0.21299 с, ускорение 1.24× + +#### 7.3.3 Динамика масштабируемости +Обе размерности демонстрируют схожую динамику: + +- Пик эффективности на 2 процессах +- Резкое снижение эффективности при 4 и более процессах +- Наилучшее соотношение вычислений и коммуникаций при 2 процессах + +#### 7.3.4 Выводы по производительности: +Алгоритм демонстрирует хорошую масштабируемость для средних и больших размеров задач, что подтверждается ростом эффективности с 55.87% до 61.95% при увеличении размера матрицы. + +Оптимальная конфигурация — 2 процесса MPI, что обеспечивает баланс между вычислительной нагрузкой и коммуникационными затратами. + +Ограниченная масштабируемость при большом количестве процессов обусловлена преобладанием коммуникационных операций (MPI_Allgatherv) над вычислительными при распределении на 4 и более процессов. + +Практическая применимость: разработанная реализация эффективна для обработки матриц средних и больших размеров, где вычислительная сложность преобладает над накладными расходами параллелизации. + +## 8. Заключение +Был разработан алгоритм для поиска максимальных значений в столбцах матрицы. Реализованные последовательная и параллельная MPI-реализации успешно проходят все функциональные тесты, включая обработку граничных случаев (пустые матрицы, матрицы с нулевыми столбцами) и работу с различными типами данных. + +Проведенное исследование производительности на матрицах размером 5000×5000 и 10000×10000 показало, что MPI-реализация демонстрирует положительное ускорение по сравнению с последовательной версией. Наилучшие результаты достигаются при использовании 2 процессов: + +Для матрицы 5000×5000: ускорение 1.12×, эффективность 55.87% + +Для матрицы 10000×10000: ускорение 1.24×, эффективность 61.95% + +## 9. Источники +- Лекции Сысоева Александра Владимировича +- Практические занятия Нестерова Александра Юрьевича и Оболенского Арсения Андреевича +- Open MPI Documentation. https://www.open-mpi.org/doc/ +- Introduction to Parallel Computing. https://computing.llnl.gov/tutorials/parallel_comp/ +- MPI Tutorial for Beginners. https://mpitutorial.com/ +- C++ Standard Library Reference. https://en.cppreference.com/ +- "Основы MPI программирования". https://habr.com/ru/articles/121925/ + +## Приложение +```cpp +//MPI +bool EgorovaLFindMaxValColMatrixMPI::RunImpl() { + const auto &matrix = GetInput(); + + if (matrix.empty() || matrix[0].empty()) { + GetOutput() = std::vector(); + return true; + } + + return RunMPIAlgorithm(); +} + +bool EgorovaLFindMaxValColMatrixMPI::RunMPIAlgorithm() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + int rows = 0; + int cols = 0; + if (rank == 0) { + rows = static_cast(GetInput().size()); + cols = static_cast(GetInput()[0].size()); + } + + MPI_Bcast(&rows, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&cols, 1, MPI_INT, 0, MPI_COMM_WORLD); + + // Распределение столбцов по процессам + const int cols_per_proc = cols / size; + const int remainder = cols % size; + int start_col = 0; + int local_cols_count = 0; + + if (rank < remainder) { + start_col = rank * (cols_per_proc + 1); + local_cols_count = cols_per_proc + 1; + } else { + start_col = (remainder * (cols_per_proc + 1)) + ((rank - remainder) * cols_per_proc); + local_cols_count = cols_per_proc; + } + + // Получение локальной части матрицы + std::vector local_matrix_part = GetLocalMatrixPart(rank, size, rows, cols, start_col, local_cols_count); + std::vector local_max = CalculateLocalMaxima(local_matrix_part, rows, local_cols_count); + std::vector all_max = GatherResults(local_max, size, cols); + + GetOutput() = all_max; + MPI_Barrier(MPI_COMM_WORLD); + return true; +} + +std::vector EgorovaLFindMaxValColMatrixMPI::GetLocalMatrixPart(int rank, int size, int rows, int cols, + int start_col, int local_cols_count) { + std::vector local_part(static_cast(rows) * static_cast(local_cols_count)); + + if (rank == 0) { + const auto &matrix = GetInput(); + + // Процесс 0 заполняет свою локальную часть + for (int ii = 0; ii < rows; ++ii) { + for (int local_idx = 0; local_idx < local_cols_count; ++local_idx) { + const int global_col = start_col + local_idx; + local_part[(static_cast(ii) * static_cast(local_cols_count)) + local_idx] = + matrix[ii][global_col]; + } + } + + // Отправка частей матрицы другим процессам + SendMatrixPartsToOtherRanks(size, rows, cols); + } else { + // Получение данных от процесса 0 + MPI_Recv(local_part.data(), static_cast(local_part.size()), MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } + + return local_part; +} + +void EgorovaLFindMaxValColMatrixMPI::SendMatrixPartsToOtherRanks(int size, int rows, int cols) { + for (int dest_rank = 1; dest_rank < size; ++dest_rank) { + std::vector dest_part = PrepareMatrixPartForRank(dest_rank, size, rows, cols); + + // Отправка данных процессу-получателю + MPI_Send(dest_part.data(), static_cast(dest_part.size()), MPI_INT, dest_rank, 0, MPI_COMM_WORLD); + } +} + +std::vector EgorovaLFindMaxValColMatrixMPI::PrepareMatrixPartForRank(int dest_rank, int size, int rows, int cols) { + // Вычисление диапазона столбцов для процесса-получателя + const int cols_per_proc = cols / size; + const int remainder = cols % size; + + int dest_start_col = 0; + int dest_cols_count = 0; + + if (dest_rank < remainder) { + dest_start_col = dest_rank * (cols_per_proc + 1); + dest_cols_count = cols_per_proc + 1; + } else { + dest_start_col = (remainder * (cols_per_proc + 1)) + ((dest_rank - remainder) * cols_per_proc); + dest_cols_count = cols_per_proc; + } + + // Подготовка данных для отправки + const auto &matrix = GetInput(); + std::vector dest_part(static_cast(rows) * static_cast(dest_cols_count)); + + for (int ii = 0; ii < rows; ++ii) { + for (int jj = 0; jj < dest_cols_count; ++jj) { + const int global_col = dest_start_col + jj; + dest_part[(static_cast(ii) * static_cast(dest_cols_count)) + jj] = + matrix[ii][global_col]; + } + } + + return dest_part; +} + +std::vector EgorovaLFindMaxValColMatrixMPI::CalculateLocalMaxima(const std::vector &local_matrix_part, + int rows, int local_cols_count) { + std::vector local_max(local_cols_count, std::numeric_limits::min()); + + for (int local_idx = 0; local_idx < local_cols_count; ++local_idx) { + for (int row = 0; row < rows; ++row) { + const int value = local_matrix_part[(row * local_cols_count) + local_idx]; + local_max[local_idx] = std::max(value, local_max[local_idx]); + } + } + + return local_max; +} + +std::vector EgorovaLFindMaxValColMatrixMPI::GatherResults(const std::vector &local_max, int size, int cols) { + const int cols_per_proc = cols / size; + const int remainder = cols % size; + + std::vector all_max(cols, std::numeric_limits::min()); + std::vector recv_counts(size); + std::vector displs(size); + + for (int ii = 0; ii < size; ++ii) { + recv_counts[ii] = (ii < remainder) ? (cols_per_proc + 1) : cols_per_proc; + displs[ii] = (ii == 0) ? 0 : displs[ii - 1] + recv_counts[ii - 1]; + } + + MPI_Allgatherv(local_max.data(), static_cast(local_max.size()), MPI_INT, all_max.data(), recv_counts.data(), + displs.data(), MPI_INT, MPI_COMM_WORLD); + + return all_max; +} + +bool EgorovaLFindMaxValColMatrixMPI::PostProcessingImpl() { + return true; +} + +} // namespace egorova_l_find_max_val_col_matrix diff --git a/tasks/egorova_l_find_max_val_col_matrix/seq/include/ops_seq.hpp b/tasks/egorova_l_find_max_val_col_matrix/seq/include/ops_seq.hpp new file mode 100644 index 000000000..bec18f5ec --- /dev/null +++ b/tasks/egorova_l_find_max_val_col_matrix/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "egorova_l_find_max_val_col_matrix/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace egorova_l_find_max_val_col_matrix { + +class EgorovaLFindMaxValColMatrixSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit EgorovaLFindMaxValColMatrixSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace egorova_l_find_max_val_col_matrix diff --git a/tasks/egorova_l_find_max_val_col_matrix/seq/src/ops_seq.cpp b/tasks/egorova_l_find_max_val_col_matrix/seq/src/ops_seq.cpp new file mode 100644 index 000000000..cd355fe9d --- /dev/null +++ b/tasks/egorova_l_find_max_val_col_matrix/seq/src/ops_seq.cpp @@ -0,0 +1,73 @@ +#include "egorova_l_find_max_val_col_matrix/seq/include/ops_seq.hpp" + +#include +#include +#include +#include + +#include "egorova_l_find_max_val_col_matrix/common/include/common.hpp" + +namespace egorova_l_find_max_val_col_matrix { + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wnull-dereference" +#endif + +EgorovaLFindMaxValColMatrixSEQ::EgorovaLFindMaxValColMatrixSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = std::vector(0); +} + +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + +bool EgorovaLFindMaxValColMatrixSEQ::ValidationImpl() { + const auto &matrix = GetInput(); + + if (matrix.empty()) { + return true; + } + + if (matrix[0].empty()) { + return true; + } + + const std::size_t cols = matrix[0].size(); + return std::ranges::all_of(matrix, [cols](const auto &row) { return row.size() == cols; }); +} + +bool EgorovaLFindMaxValColMatrixSEQ::PreProcessingImpl() { + return true; +} + +bool EgorovaLFindMaxValColMatrixSEQ::RunImpl() { + const auto &matrix = GetInput(); + + // Оставляем только проверку на пустоту + if (matrix.empty() || matrix[0].empty()) { + GetOutput() = std::vector(); + return true; + } + + const std::size_t rows = matrix.size(); + const std::size_t cols = matrix[0].size(); + std::vector result(cols, std::numeric_limits::min()); + + for (std::size_t jj = 0; jj < cols; ++jj) { + for (std::size_t ii = 0; ii < rows; ++ii) { + result[jj] = std::max(matrix[ii][jj], result[jj]); + } + } + + GetOutput() = result; + return true; +} + +bool EgorovaLFindMaxValColMatrixSEQ::PostProcessingImpl() { + return true; +} + +} // namespace egorova_l_find_max_val_col_matrix diff --git a/tasks/egorova_l_find_max_val_col_matrix/settings.json b/tasks/egorova_l_find_max_val_col_matrix/settings.json new file mode 100644 index 000000000..7d2c35b29 --- /dev/null +++ b/tasks/egorova_l_find_max_val_col_matrix/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/egorova_l_find_max_val_col_matrix/tests/.clang-tidy b/tasks/egorova_l_find_max_val_col_matrix/tests/.clang-tidy new file mode 100644 index 000000000..ef43b7aa8 --- /dev/null +++ b/tasks/egorova_l_find_max_val_col_matrix/tests/.clang-tidy @@ -0,0 +1,13 @@ +InheritParentConfig: true + +Checks: > + -modernize-loop-convert, + -cppcoreguidelines-avoid-goto, + -cppcoreguidelines-avoid-non-const-global-variables, + -misc-use-anonymous-namespace, + -modernize-use-std-print, + -modernize-type-traits + +CheckOptions: + - key: readability-function-cognitive-complexity.Threshold + value: 50 # Relaxed for tests diff --git a/tasks/egorova_l_find_max_val_col_matrix/tests/functional/main.cpp b/tasks/egorova_l_find_max_val_col_matrix/tests/functional/main.cpp new file mode 100644 index 000000000..abff18137 --- /dev/null +++ b/tasks/egorova_l_find_max_val_col_matrix/tests/functional/main.cpp @@ -0,0 +1,185 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "egorova_l_find_max_val_col_matrix/common/include/common.hpp" +#include "egorova_l_find_max_val_col_matrix/mpi/include/ops_mpi.hpp" +#include "egorova_l_find_max_val_col_matrix/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace egorova_l_find_max_val_col_matrix { + +class EgorovaLRunFuncTestsProcesses : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::to_string(std::get<0>(test_param)) + "_" + std::get<1>(test_param); + } + + protected: + void SetUp() override { + auto test_params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + int test_type = std::get<0>(test_params); + + switch (test_type) { + case 0: // Пустая матрица + input_data_ = {}; + break; + + case 1: // Нулевая матрица (1x0) + input_data_ = {{}}; + break; + + case 2: // Один столбец + input_data_ = {{5}, {-3}, {10}, {1}}; + break; + + case 3: // Одна строка + input_data_ = {{8, -2, 15, 0, -7}}; + break; + + case 4: // Квадратная матрица 3x3 + input_data_ = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; + break; + + case 5: // Неквадратная матрица (2x4) + input_data_ = {{10, 20, 30, 40}, {50, 60, 70, 80}}; + break; + + case 6: // Матрица с отрицательными значениями + input_data_ = {{-5, -2, -10}, {-1, -8, -3}, {-7, -4, -6}}; + break; + + case 7: // Матрица с одинаковыми значениями + input_data_ = {{5, 5, 5}, {5, 5, 5}, {5, 5, 5}}; + break; + + case 8: // Матрица с максимальным элементом в разных столбцах + input_data_ = {{1, 100, 2}, {50, 3, 200}, {10, 4, 5}}; + break; + + case 9: // Большая матрица 5x5 + input_data_ = { + {15, 8, 22, 4, 19}, {7, 25, 11, 3, 14}, {9, 2, 30, 17, 6}, {12, 5, 1, 28, 10}, {20, 13, 16, 21, 0}}; + break; + + case 10: // Матрица с одним элементом + input_data_ = {{42}}; + break; + + case 11: // Матрица где максимум в первом столбце + input_data_ = {{99, 1, 2}, {88, 3, 4}, {77, 5, 6}}; + break; + + case 12: // Матрица где максимум в последнем столбце + input_data_ = {{1, 2, 100}, {3, 4, 90}, {5, 6, 80}}; + break; + + case 13: // Матрица с нулевыми значениями + input_data_ = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + break; + + case 14: // Одна строка большая + input_data_ = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}; + break; + + case 15: // Один столбец большой + input_data_ = {{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}}; + break; + + case 16: // Смешанные значения + input_data_ = {{-1, 5, -3}, {2, -8, 10}, {7, 0, -5}}; + break; + + case 17: // Матрица для неравномерного распределения столбцов + input_data_ = {{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}, {11, 12, 13, 14, 15}}; + break; + + default: + break; + } + } + + bool CheckTestOutputData(OutType &output_data) final { + const auto &matrix = GetTestInputData(); + + // Для пустых матриц ожидаем пустой результат + if (matrix.empty() || matrix[0].empty()) { + return output_data.empty(); + } + + // Для обычных матриц проверяем корректность результата + if (output_data.size() != matrix[0].size()) { + return false; + } + + std::vector expected(matrix[0].size(), std::numeric_limits::min()); + for (std::size_t jj = 0; jj < matrix[0].size(); ++jj) { + for (std::size_t ii = 0; ii < matrix.size(); ++ii) { + expected[jj] = std::max(matrix[ii][jj], expected[jj]); + } + } + + for (std::size_t ii = 0; ii < output_data.size(); ++ii) { + if (output_data[ii] != expected[ii]) { + return false; + } + } + + return true; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +namespace { + +TEST_P(EgorovaLRunFuncTestsProcesses, FindMaxValColMatrix) { + ExecuteTest(GetParam()); +} + +// тип теста, описание +const std::array kTestParam = {std::make_tuple(0, "empty_matrix"), + std::make_tuple(1, "zero_matrix"), + std::make_tuple(2, "single_column"), + std::make_tuple(3, "single_row"), + std::make_tuple(4, "square_3x3"), + std::make_tuple(5, "non_square_2x4"), + std::make_tuple(6, "negative_values"), + std::make_tuple(7, "same_values"), + std::make_tuple(8, "max_in_different_cols"), + std::make_tuple(9, "large_5x5"), + std::make_tuple(10, "single_element"), + std::make_tuple(11, "max_in_first_col"), + std::make_tuple(12, "max_in_last_col"), + std::make_tuple(13, "zero_values"), + std::make_tuple(14, "single_row_large"), + std::make_tuple(15, "single_column_large"), + std::make_tuple(16, "mixed_positive_negative"), + std::make_tuple(17, "uneven_column_distribution")}; + +const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_egorova_l_find_max_val_col_matrix), + ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_egorova_l_find_max_val_col_matrix)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = EgorovaLRunFuncTestsProcesses::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(MatrixTests, EgorovaLRunFuncTestsProcesses, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace egorova_l_find_max_val_col_matrix diff --git a/tasks/egorova_l_find_max_val_col_matrix/tests/performance/main.cpp b/tasks/egorova_l_find_max_val_col_matrix/tests/performance/main.cpp new file mode 100644 index 000000000..66a712aa9 --- /dev/null +++ b/tasks/egorova_l_find_max_val_col_matrix/tests/performance/main.cpp @@ -0,0 +1,75 @@ +#include + +#include +#include +#include +#include + +#include "egorova_l_find_max_val_col_matrix/common/include/common.hpp" +#include "egorova_l_find_max_val_col_matrix/mpi/include/ops_mpi.hpp" +#include "egorova_l_find_max_val_col_matrix/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace egorova_l_find_max_val_col_matrix { + +class EgorovaLRunPerfTestProcesses : public ppc::util::BaseRunPerfTests { + const std::size_t kMatrixSize_ = 5000; + InType input_data_; + + void SetUp() override { + input_data_.resize(kMatrixSize_, std::vector(kMatrixSize_)); + + int counter = 1; + for (std::size_t ii = 0; ii < kMatrixSize_; ++ii) { + for (std::size_t jj = 0; jj < kMatrixSize_; ++jj) { + input_data_[ii][jj] = counter++; + } + } + } + + bool CheckTestOutputData(OutType &output_data) final { + const auto &matrix = GetTestInputData(); + + if (matrix.empty() || output_data.empty()) { + return false; + } + + if (output_data.size() != matrix[0].size()) { + return false; + } + + std::vector expected(matrix[0].size(), std::numeric_limits::min()); + for (size_t jj = 0; jj < matrix[0].size(); ++jj) { + for (size_t ii = 0; ii < matrix.size(); ++ii) { + expected[jj] = std::max(matrix[ii][jj], expected[jj]); + } + } + + for (size_t ii = 0; ii < output_data.size(); ++ii) { + if (output_data[ii] != expected[ii]) { + return false; + } + } + return true; + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(EgorovaLRunPerfTestProcesses, EgorovaLRunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_egorova_l_find_max_val_col_matrix); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = EgorovaLRunPerfTestProcesses::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(EgorovaLRunModeTests, EgorovaLRunPerfTestProcesses, kGtestValues, kPerfTestName); + +} // namespace egorova_l_find_max_val_col_matrix