diff --git a/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp new file mode 100644 index 0000000000..1a548adac1 --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include + +#include "task/include/task.hpp" + +namespace egorova_l_gauss_filter_vert { + +struct Image { + int rows = 0; + int cols = 0; + int channels = 0; + std::vector data; +}; + +using InType = Image; +using OutType = Image; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace egorova_l_gauss_filter_vert diff --git a/tasks/egorova_l_gauss_filter_vert/info.json b/tasks/egorova_l_gauss_filter_vert/info.json new file mode 100644 index 0000000000..9c3cc3ea58 --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Лариса", + "last_name": "Егорова", + "middle_name": "Алексеевна", + "group_number": "3823Б1ФИ1", + "task_number": "27" + } +} diff --git a/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp b/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..0e9564fc33 --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "egorova_l_gauss_filter_vert/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace egorova_l_gauss_filter_vert { + +class EgorovaLGaussFilterVertMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + + explicit EgorovaLGaussFilterVertMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace egorova_l_gauss_filter_vert diff --git a/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp b/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..3c970515af --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp @@ -0,0 +1,313 @@ +#include "egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include "egorova_l_gauss_filter_vert/common/include/common.hpp" + +namespace egorova_l_gauss_filter_vert { + +namespace { + +constexpr std::array kKernel = {0.0625F, 0.125F, 0.0625F, 0.125F, 0.25F, 0.125F, 0.0625F, 0.125F, 0.0625F}; + +void ApplyKernelToPixel(const std::vector &local_in, int row_index, int col_index, int channel_index, int rows, + int local_cols_with_halo, int channels, int halo_left, double &sum) { + for (int kernel_row = -1; kernel_row <= 1; ++kernel_row) { + const int image_row = std::clamp(row_index + kernel_row, 0, rows - 1); + for (int kernel_col = -1; kernel_col <= 1; ++kernel_col) { + const int local_col_with_halo = col_index + halo_left + kernel_col; + const int clamped_local_col = std::clamp(local_col_with_halo, 0, local_cols_with_halo - 1); + + const std::size_t pixel_index = + ((static_cast(image_row) * static_cast(local_cols_with_halo) + + static_cast(clamped_local_col)) * + static_cast(channels)) + + static_cast(channel_index); + + const auto kernel_index = + (static_cast(kernel_row + 1) * 3U) + static_cast(kernel_col + 1); + + sum += static_cast(local_in[pixel_index]) * static_cast(kKernel.at(kernel_index)); + } + } +} + +void ProcessRow(const std::vector &local_in, std::vector &local_out, int row_index, int rows, + int local_cols, int local_cols_with_halo, int channels, int halo_left) { + for (int col_index = 0; col_index < local_cols; ++col_index) { + for (int channel_index = 0; channel_index < channels; ++channel_index) { + double sum = 0.0; + ApplyKernelToPixel(local_in, row_index, col_index, channel_index, rows, local_cols_with_halo, channels, halo_left, + sum); + + const std::size_t out_index = ((static_cast(row_index) * static_cast(local_cols) + + static_cast(col_index)) * + static_cast(channels)) + + static_cast(channel_index); + local_out[out_index] = static_cast(std::clamp(std::round(sum), 0.0, 255.0)); + } + } +} + +void ComputeLocalGaussWithHalo(const std::vector &local_in_with_halo, std::vector &local_out, + int rows, int local_cols, int local_cols_with_halo, int channels, int halo_left) { + if (local_cols <= 0) { + return; + } + + for (int row_index = 0; row_index < rows; ++row_index) { + ProcessRow(local_in_with_halo, local_out, row_index, rows, local_cols, local_cols_with_halo, channels, halo_left); + } +} + +void CopyMainDataToHaloBuffer(const std::vector &local_data, std::vector &local_data_with_halo, + int rows, int local_cols, int local_cols_with_halo, int channels, int halo_size) { + for (int row = 0; row < rows; ++row) { + for (int channel = 0; channel < channels; ++channel) { + for (int local_col = 0; local_col < local_cols; ++local_col) { + const std::size_t src_idx = ((row * local_cols + local_col) * channels) + channel; + const std::size_t dst_idx = ((row * local_cols_with_halo + (local_col + halo_size)) * channels) + channel; + local_data_with_halo[dst_idx] = local_data[src_idx]; + } + } + } +} + +void FillHaloBoundaries(std::vector &local_data_with_halo, int rows, int local_cols, int local_cols_with_halo, + int channels, int halo_size) { + for (int row = 0; row < rows; ++row) { + for (int channel = 0; channel < channels; ++channel) { + const std::size_t left_idx = ((row * local_cols_with_halo + 0) * channels) + channel; + const std::size_t first_real_idx = ((row * local_cols_with_halo + halo_size) * channels) + channel; + const std::size_t right_idx = ((row * local_cols_with_halo + (local_cols_with_halo - 1)) * channels) + channel; + const std::size_t last_real_idx = + ((row * local_cols_with_halo + (local_cols + halo_size - 1)) * channels) + channel; + + local_data_with_halo[left_idx] = local_data_with_halo[first_real_idx]; + local_data_with_halo[right_idx] = local_data_with_halo[last_real_idx]; + } + } +} + +std::vector PrepareLocalDataWithHalo(const std::vector &local_data, int rows, int local_cols, + int local_cols_with_halo, int channels, int halo_size) { + std::vector local_data_with_halo(static_cast(local_cols_with_halo) * + static_cast(rows) * static_cast(channels)); + + CopyMainDataToHaloBuffer(local_data, local_data_with_halo, rows, local_cols, local_cols_with_halo, channels, + halo_size); + + FillHaloBoundaries(local_data_with_halo, rows, local_cols, local_cols_with_halo, channels, halo_size); + + return local_data_with_halo; +} + +void PrepareHaloForSending(const std::vector &local_data_with_halo, std::vector &send_left_halo, + std::vector &send_right_halo, int rows, int local_cols, int local_cols_with_halo, + int channels, int halo_size) { + if (local_cols <= 0) { + return; + } + + for (int row = 0; row < rows; ++row) { + for (int channel = 0; channel < channels; ++channel) { + const std::size_t src_left_idx = ((row * local_cols_with_halo + halo_size) * channels) + channel; + const std::size_t dst_left_idx = (row * channels) + channel; + send_left_halo[dst_left_idx] = local_data_with_halo[src_left_idx]; + + const std::size_t src_right_idx = + ((row * local_cols_with_halo + (local_cols + halo_size - 1)) * channels) + channel; + const std::size_t dst_right_idx = (row * channels) + channel; + send_right_halo[dst_right_idx] = local_data_with_halo[src_right_idx]; + } + } +} + +void PerformHaloExchange(std::vector &send_left_halo, std::vector &send_right_halo, + std::vector &recv_left_halo, std::vector &recv_right_halo, + int halo_column_size, int rank, int size) { + const bool is_middle_process = rank > 0 && rank < size - 1; + const bool is_first_process = rank == 0 && size > 1; + const bool is_last_process = rank == size - 1 && size > 1; + + if (is_middle_process) { + MPI_Sendrecv(send_left_halo.data(), halo_column_size, MPI_BYTE, rank - 1, 0, recv_right_halo.data(), + halo_column_size, MPI_BYTE, rank + 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + MPI_Sendrecv(send_right_halo.data(), halo_column_size, MPI_BYTE, rank + 1, 0, recv_left_halo.data(), + halo_column_size, MPI_BYTE, rank - 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } else if (is_first_process) { + MPI_Sendrecv(send_right_halo.data(), halo_column_size, MPI_BYTE, rank + 1, 0, recv_left_halo.data(), + halo_column_size, MPI_BYTE, rank + 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } else if (is_last_process) { + MPI_Sendrecv(send_left_halo.data(), halo_column_size, MPI_BYTE, rank - 1, 0, recv_right_halo.data(), + halo_column_size, MPI_BYTE, rank - 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } +} + +void UpdateHaloZonesWithReceivedData(std::vector &local_data_with_halo, + const std::vector &recv_left_halo, + const std::vector &recv_right_halo, int rows, int local_cols_with_halo, + int channels, int rank, int size) { + if (rank > 0) { + for (int row = 0; row < rows; ++row) { + for (int channel = 0; channel < channels; ++channel) { + const std::size_t dst_idx = ((row * local_cols_with_halo + 0) * channels) + channel; + const std::size_t src_idx = (row * channels) + channel; + local_data_with_halo[dst_idx] = recv_left_halo[src_idx]; + } + } + } + + if (rank < size - 1) { + for (int row = 0; row < rows; ++row) { + for (int channel = 0; channel < channels; ++channel) { + const std::size_t dst_idx = ((row * local_cols_with_halo + (local_cols_with_halo - 1)) * channels) + channel; + const std::size_t src_idx = (row * channels) + channel; + local_data_with_halo[dst_idx] = recv_right_halo[src_idx]; + } + } + } +} + +void ExchangeHaloZones(std::vector &local_data_with_halo, int rows, int local_cols, int local_cols_with_halo, + int channels, int halo_size, int rank, int size) { + if (size <= 1) { + return; + } + + const int halo_column_size = rows * channels; + + std::vector send_left_halo(halo_column_size); + std::vector send_right_halo(halo_column_size); + std::vector recv_left_halo(halo_column_size); + std::vector recv_right_halo(halo_column_size); + + PrepareHaloForSending(local_data_with_halo, send_left_halo, send_right_halo, rows, local_cols, local_cols_with_halo, + channels, halo_size); + + PerformHaloExchange(send_left_halo, send_right_halo, recv_left_halo, recv_right_halo, halo_column_size, rank, size); + + UpdateHaloZonesWithReceivedData(local_data_with_halo, recv_left_halo, recv_right_halo, rows, local_cols_with_halo, + channels, rank, size); +} + +} // namespace + +EgorovaLGaussFilterVertMPI::EgorovaLGaussFilterVertMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool EgorovaLGaussFilterVertMPI::ValidationImpl() { + const auto &input = GetInput(); + return input.rows > 0 && input.cols > 0 && input.channels > 0 && + input.data.size() == static_cast(input.rows) * static_cast(input.cols) * + static_cast(input.channels); +} + +bool EgorovaLGaussFilterVertMPI::PreProcessingImpl() { + return true; +} + +bool EgorovaLGaussFilterVertMPI::RunImpl() { + 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; + int channels = 0; + + if (rank == 0) { + rows = GetInput().rows; + cols = GetInput().cols; + channels = GetInput().channels; + } + + MPI_Bcast(&rows, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&cols, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&channels, 1, MPI_INT, 0, MPI_COMM_WORLD); + + const int cols_per_proc = cols / size; + const int remainder = cols % size; + + std::vector proc_cols_count(size); + std::vector proc_start_col(size); + + if (rank == 0) { + int current_col = 0; + for (int proc = 0; proc < size; ++proc) { + const bool gets_extra = proc < remainder; + proc_cols_count[proc] = gets_extra ? cols_per_proc + 1 : cols_per_proc; + proc_start_col[proc] = current_col; + current_col += proc_cols_count[proc]; + } + } + + MPI_Bcast(proc_cols_count.data(), size, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(proc_start_col.data(), size, MPI_INT, 0, MPI_COMM_WORLD); + + const int local_cols = proc_cols_count[rank]; + + const int halo_size = 1; + const int local_cols_with_halo = local_cols + (2 * halo_size); + const int local_size = local_cols * rows * channels; + + std::vector send_counts(size); + std::vector displacements(size); + + if (rank == 0) { + int offset = 0; + for (int proc = 0; proc < size; ++proc) { + send_counts[proc] = proc_cols_count[proc] * rows * channels; + displacements[proc] = offset; + offset += send_counts[proc]; + } + } + + std::vector local_data(local_size); + std::vector local_out(local_size); + + const uint8_t *send_buffer = rank == 0 ? GetInput().data.data() : nullptr; + MPI_Scatterv(send_buffer, send_counts.data(), displacements.data(), MPI_BYTE, local_data.data(), local_size, MPI_BYTE, + 0, MPI_COMM_WORLD); + + auto local_data_with_halo = + PrepareLocalDataWithHalo(local_data, rows, local_cols, local_cols_with_halo, channels, halo_size); + + ExchangeHaloZones(local_data_with_halo, rows, local_cols, local_cols_with_halo, channels, halo_size, rank, size); + + ComputeLocalGaussWithHalo(local_data_with_halo, local_out, rows, local_cols, local_cols_with_halo, channels, + halo_size); + + auto &out = GetOutput(); + out.rows = rows; + out.cols = cols; + out.channels = channels; + + const std::size_t total_size = + static_cast(rows) * static_cast(cols) * static_cast(channels); + out.data.resize(total_size); + + MPI_Gatherv(local_out.data(), local_size, MPI_BYTE, out.data.data(), send_counts.data(), displacements.data(), + MPI_BYTE, 0, MPI_COMM_WORLD); + + MPI_Bcast(out.data.data(), static_cast(total_size), MPI_BYTE, 0, MPI_COMM_WORLD); + + return true; +} + +bool EgorovaLGaussFilterVertMPI::PostProcessingImpl() { + return true; +} + +} // namespace egorova_l_gauss_filter_vert diff --git a/tasks/egorova_l_gauss_filter_vert/report.md b/tasks/egorova_l_gauss_filter_vert/report.md new file mode 100644 index 0000000000..225fbddcba --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/report.md @@ -0,0 +1,195 @@ +# Вертикальный фильтр Гаусса (3x3) +- **Студент:** Егорова Лариса Алексеевна +- **Группа:** 3823Б1ФИ1 +- **Технология:** MPI, SEQ +- **Вариант:** 27 + +## 1. Введение +Фильтрация изображений является одной из ключевых задач компьютерного зрения. Фильтр Гаусса применяется для снижения уровня цветового шума и сглаживания деталей. В условиях работы с изображениями высокого разрешения последовательная обработка становится узким местом, что обуславливает необходимость использования технологий параллельного программирования, таких как MPI. + +## 2. Постановка задачи +Необходимо реализовать параллельный алгоритм фильтрации изображения с использованием ядра Гаусса размером $3 \times 3$. + +- **Вход:** Структура данных со строками, столбцами, каналами и вектором пикселей `uint8_t`. +- **Выход:** Преобразованный вектор пикселей того же размера. +- **Особенности:** Реализация должна корректно обрабатывать границы изображения (стратегия `clamp`) и обеспечивать синхронизацию данных между процессами. + +## 3. Последовательный алгоритм +Алгоритм реализует математическую операцию свертки изображения с дискретным аналогом функции Гаусса. + +1. Для каждого пикселя $(x, y)$ анализируется окрестность $3 \times 3$. +2. Используется ядро с весовыми коэффициентами и последующей нормализацией ($\frac{1}{16}$): + $$K = \frac{1}{16} \begin{bmatrix} 1 & 2 & 1 \\ 2 & 4 & 2 \\ 1 & 2 & 1 \end{bmatrix}$$ +3. На краях изображения используется значение ближайшего существующего пикселя. + +## 4. Схема распараллеливания (MPI) +Для минимизации взаимодействий выбрана одномерная вертикальная декомпозиция (разделение на полосы по столбцам) с обменом halo-зонами. + +1. **Распределение нагрузки:** Изображение делится на $N$ вертикальных полос. Каждый процесс вычисляет свою часть столбцов. +2. **Обработка границ:** Каждый процесс добавляет halo-зоны (+1 столбец с каждой стороны) для корректного применения фильтра 3x3. +3. **Коммуникации:** + - `MPI_Bcast` для передачи метаданных (размеры изображения) + - `MPI_Scatterv` для распределения данных по процессам + - `MPI_Sendrecv` для обмена halo-зонами между соседними процессами + - `MPI_Gatherv` для сбора результатов на процесс 0 + - `MPI_Bcast` для распространения финального результата всем процессам + +## 5. Эксперименты + +### 5.1 Среда проведения тестов + +- **Процессор:** 13th Gen Intel(R) Core(TM) i5-13420H (2.10 GHz, 8 ядер / 12 потоков) +- **Оперативная память:** 16,0 ГБ +- **ОС:** Windows 11 (WSL2 / Docker Desktop) +- **Инструментарий:** Clang-21, MPI, CMake, Release build. + +### 5.2 Производительность +Измерения проводились на изображении размером 2000×2000×3 канала (12 миллионов пикселей). + +**Таблица 1. Результаты измерения производительности** + +| Реализация | Процессы | Время (сек) | Ускорение | Эффективность | +| :--------- | :------: | :---------: | :-------: | :-----------: | +| SEQ | 1 | 0.12715 | 1.00× | 100% | +| MPI | 2 | 0.08160 | 1.56× | 78% | +| MPI | 3 | 0.06466 | 1.97× | 66% | +| MPI | 4 | 0.06033 | 2.11× | 53% | + +### 5.3 Анализ результатов + +1. **Линейное масштабирование:** + - На 2 процессах: ускорение 1.56× (сокращение времени на 36%) + - На 3 процессах: ускорение 1.97× (почти двукратное ускорение) + - На 4 процессах: ускорение 2.11× (максимальное ускорение) + +2. **Эффективность параллелизации:** + - 2 процесса: 78% - высокая эффективность + - 3 процесса: 66% - хорошая эффективность + - 4 процесса: 53% - умеренная эффективность + +3. **Особенности реализации:** + - Вертикальное разбиение с обменом halo-зонами + - Корректная обработка границ между процессами + - Минимальные коммуникационные затраты + +4. **Оптимальность конфигурации:** + - Наилучшее соотношение производительности достигается на 3 процессах + - Дальнейшее увеличение процессов даёт меньший прирост производительности + - Алгоритм демонстрирует хорошее масштабирование до 4 процессов + +## 6. Заключение +Разработана параллельная реализация фильтра Гаусса для обработки изображений с использованием MPI. Алгоритм реализует вертикальное разбиение изображения с обменом граничными данными между процессами. + +**Основные результаты:** +- Достигнуто ускорение до 2.11× на 4 процессах +- Наилучшая эффективность 78% при 2 процессах +- Корректная обработка границ изображения через обмен halo-зонами + +**Преимущества реализации:** +- Хорошее масштабирование до 4 процессов +- Минимальные коммуникационные накладные расходы +- Устойчивая работа при различном количестве процессов + +Разработанное решение эффективно для обработки больших изображений на системах с несколькими вычислительными ядрами. + +## 7. Источники +1. Open MPI Documentation: [https://www.open-mpi.org/doc/](https://www.open-mpi.org/doc/) +2. MPI Standard: [https://www.mpi-forum.org/docs/](https://www.mpi-forum.org/docs/) +3. Introduction to MPI: [https://mpitutorial.com/](https://mpitutorial.com/) +4. Материалы курса «Параллельное программирование». + +## Приложение (код) + +```cpp +bool EgorovaLGaussFilterVertMPI::RunImpl() { + 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; + int channels = 0; + + if (rank == 0) { + rows = GetInput().rows; + cols = GetInput().cols; + channels = GetInput().channels; + } + + MPI_Bcast(&rows, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&cols, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&channels, 1, MPI_INT, 0, MPI_COMM_WORLD); + + const int cols_per_proc = cols / size; + const int remainder = cols % size; + + std::vector proc_cols_count(size); + std::vector proc_start_col(size); + + if (rank == 0) { + int current_col = 0; + for (int proc = 0; proc < size; ++proc) { + const bool gets_extra = proc < remainder; + proc_cols_count[proc] = gets_extra ? cols_per_proc + 1 : cols_per_proc; + proc_start_col[proc] = current_col; + current_col += proc_cols_count[proc]; + } + } + + MPI_Bcast(proc_cols_count.data(), size, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(proc_start_col.data(), size, MPI_INT, 0, MPI_COMM_WORLD); + + const int local_cols = proc_cols_count[rank]; + + const int halo_size = 1; + const int local_cols_with_halo = local_cols + (2 * halo_size); + const int local_size = local_cols * rows * channels; + + std::vector send_counts(size); + std::vector displacements(size); + + if (rank == 0) { + int offset = 0; + for (int proc = 0; proc < size; ++proc) { + send_counts[proc] = proc_cols_count[proc] * rows * channels; + displacements[proc] = offset; + offset += send_counts[proc]; + } + } + + std::vector local_data(local_size); + std::vector local_out(local_size); + + const uint8_t* send_buffer = rank == 0 ? GetInput().data.data() : nullptr; + MPI_Scatterv(send_buffer, send_counts.data(), displacements.data(), MPI_BYTE, + local_data.data(), local_size, MPI_BYTE, 0, MPI_COMM_WORLD); + + auto local_data_with_halo = PrepareLocalDataWithHalo(local_data, rows, local_cols, + local_cols_with_halo, channels, + halo_size); + + ExchangeHaloZones(local_data_with_halo, rows, local_cols, local_cols_with_halo, + channels, halo_size, rank, size); + + ComputeLocalGaussWithHalo(local_data_with_halo, local_out, rows, local_cols, + local_cols_with_halo, channels, halo_size); + + auto& out = GetOutput(); + out.rows = rows; + out.cols = cols; + out.channels = channels; + + const std::size_t total_size = static_cast(rows) * + static_cast(cols) * + static_cast(channels); + out.data.resize(total_size); + + MPI_Gatherv(local_out.data(), local_size, MPI_BYTE, + out.data.data(), send_counts.data(), displacements.data(), + MPI_BYTE, 0, MPI_COMM_WORLD); + + MPI_Bcast(out.data.data(), static_cast(total_size), MPI_BYTE, 0, MPI_COMM_WORLD); + + return true; +} diff --git a/tasks/egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp b/tasks/egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..f3a898e35c --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "egorova_l_gauss_filter_vert/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace egorova_l_gauss_filter_vert { + +class EgorovaLGaussFilterVertSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + + explicit EgorovaLGaussFilterVertSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace egorova_l_gauss_filter_vert diff --git a/tasks/egorova_l_gauss_filter_vert/seq/src/ops_seq.cpp b/tasks/egorova_l_gauss_filter_vert/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..2dc8eac07e --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/seq/src/ops_seq.cpp @@ -0,0 +1,85 @@ +#include "egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "egorova_l_gauss_filter_vert/common/include/common.hpp" + +namespace egorova_l_gauss_filter_vert { + +EgorovaLGaussFilterVertSEQ::EgorovaLGaussFilterVertSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool EgorovaLGaussFilterVertSEQ::ValidationImpl() { + const auto &input = GetInput(); + return input.rows > 0 && input.cols > 0 && input.channels > 0 && + input.data.size() == static_cast(input.rows) * static_cast(input.cols) * + static_cast(input.channels); +} + +bool EgorovaLGaussFilterVertSEQ::PreProcessingImpl() { + return true; +} + +bool EgorovaLGaussFilterVertSEQ::RunImpl() { + const auto &input = GetInput(); + auto &output = GetOutput(); + + output.rows = input.rows; + output.cols = input.cols; + output.channels = input.channels; + + static constexpr std::array kKernel = {0.0625F, 0.125F, 0.0625F, 0.125F, 0.25F, + 0.125F, 0.0625F, 0.125F, 0.0625F}; + + std::vector result(input.data.size()); + + for (int row = 0; row < input.rows; ++row) { + for (int col = 0; col < input.cols; ++col) { + for (int channel = 0; channel < input.channels; ++channel) { + double sum = 0.0; + + for (int kr = -1; kr <= 1; ++kr) { + const int ir = std::clamp(row + kr, 0, input.rows - 1); + + for (int kc = -1; kc <= 1; ++kc) { + const int ic = std::clamp(col + kc, 0, input.cols - 1); + + const std::size_t pixel_index = + ((static_cast(ir) * static_cast(input.cols) + static_cast(ic)) * + static_cast(input.channels)) + + static_cast(channel); + + const auto kernel_index = (static_cast(kr + 1) * 3U) + static_cast(kc + 1); + + // Используем .at() вместо [] для проверки границ + sum += static_cast(input.data[pixel_index]) * static_cast(kKernel.at(kernel_index)); + } + } + + const std::size_t out_index = + ((static_cast(row) * static_cast(input.cols) + static_cast(col)) * + static_cast(input.channels)) + + static_cast(channel); + + result[out_index] = static_cast(std::clamp(std::round(sum), 0.0, 255.0)); + } + } + } + + output.data = std::move(result); + return true; +} + +bool EgorovaLGaussFilterVertSEQ::PostProcessingImpl() { + return true; +} + +} // namespace egorova_l_gauss_filter_vert diff --git a/tasks/egorova_l_gauss_filter_vert/settings.json b/tasks/egorova_l_gauss_filter_vert/settings.json new file mode 100644 index 0000000000..7d2c35b298 --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/egorova_l_gauss_filter_vert/tests/.clang-tidy b/tasks/egorova_l_gauss_filter_vert/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/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_gauss_filter_vert/tests/functional/main.cpp b/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp new file mode 100644 index 0000000000..1f4d3924dd --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp @@ -0,0 +1,87 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "egorova_l_gauss_filter_vert/common/include/common.hpp" +#include "egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp" +#include "egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" + +namespace egorova_l_gauss_filter_vert { + +using TestType = std::tuple; + +class EgorovaLGaussFilterRunFuncTests : public ppc::util::BaseRunFuncTests { + public: + using ParamType = typename ppc::util::BaseRunFuncTests::ParamType; + + static std::string PrintTestParam(const ::testing::TestParamInfo &info) { + const auto ¶ms = info.param; + const auto &test_params = std::get<2>(params); + + std::string name = std::get<1>(params) + "_" + std::to_string(std::get<0>(test_params)) + "_" + + std::to_string(std::get<1>(test_params)) + "_" + std::to_string(std::get<2>(test_params)) + "_" + + std::get<3>(test_params); + + std::ranges::replace(name, '-', '_'); + std::ranges::replace(name, ' ', '_'); + return name; + } + + protected: + void SetUp() override { + const auto &test_params = std::get<2>(GetParam()); + + input_.rows = std::get<0>(test_params); + input_.cols = std::get<1>(test_params); + input_.channels = std::get<2>(test_params); + + size_t sz = static_cast(input_.rows) * input_.cols * input_.channels; + + input_.data.resize(sz); + for (size_t i = 0; i < sz; ++i) { + input_.data[i] = static_cast((i * 13) % 255); + } + } + + InType GetTestInputData() final { + return input_; + } + + bool CheckTestOutputData(OutType &out) final { + const auto &in = input_; + if (out.rows != in.rows || out.cols != in.cols || out.channels != in.channels) { + return false; + } + + return out.data.size() == static_cast(in.rows) * in.cols * in.channels; + } + + private: + InType input_; +}; + +TEST_P(EgorovaLGaussFilterRunFuncTests, GaussFilterVertical) { + ExecuteTest(GetParam()); +} + +const std::array kTestParams = { + std::make_tuple(3, 3, 1, "small_3x3_gray"), std::make_tuple(10, 10, 3, "color_10x10_rgb"), + std::make_tuple(20, 15, 1, "non_square"), std::make_tuple(5, 40, 1, "wide_image"), + std::make_tuple(40, 5, 1, "tall_image"), +}; + +const auto kTasks = std::tuple_cat( + ppc::util::AddFuncTask(kTestParams, PPC_SETTINGS_egorova_l_gauss_filter_vert), + ppc::util::AddFuncTask(kTestParams, PPC_SETTINGS_egorova_l_gauss_filter_vert)); + +INSTANTIATE_TEST_SUITE_P(EgorovaLGaussFilterFunc, EgorovaLGaussFilterRunFuncTests, ppc::util::ExpandToValues(kTasks), + EgorovaLGaussFilterRunFuncTests::PrintTestParam); + +} // namespace egorova_l_gauss_filter_vert diff --git a/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp b/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp new file mode 100644 index 0000000000..e41f2d9922 --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp @@ -0,0 +1,62 @@ +#include + +#include +#include +#include + +#include "egorova_l_gauss_filter_vert/common/include/common.hpp" +#include "egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp" +#include "egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp" +#include "performance/include/performance.hpp" +#include "util/include/perf_test_util.hpp" + +namespace egorova_l_gauss_filter_vert { + +class EgorovaLGaussFilterVertRunPerfTests : public ppc::util::BaseRunPerfTests { + protected: + static constexpr int kRows = 2000; + static constexpr int kCols = 2000; + static constexpr int kChannels = 3; + + void SetUp() override { + input_.rows = kRows; + input_.cols = kCols; + input_.channels = kChannels; + + size_t total = static_cast(kRows) * kCols * kChannels; + + input_.data.resize(total); + for (size_t i = 0; i < total; ++i) { + input_.data[i] = static_cast((i * 7 + i / 31) % 256); + } + } + + InType GetTestInputData() final { + return input_; + } + + bool CheckTestOutputData(OutType &out) final { + return out.rows == kRows && out.cols == kCols && out.channels == kChannels && + out.data.size() == static_cast(kRows) * kCols * kChannels; + } + + private: + InType input_; +}; + +TEST_P(EgorovaLGaussFilterVertRunPerfTests, GaussPerf) { + ExecuteTest(GetParam()); +} + +const auto kPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_egorova_l_gauss_filter_vert); + +INSTANTIATE_TEST_SUITE_P(EgorovaLGaussFilterPerf, EgorovaLGaussFilterVertRunPerfTests, + ppc::util::TupleToGTestValues(kPerfTasks), [](const auto &info) { + const auto &name = std::get<1>(info.param); + const auto run = std::get<2>(info.param); + return name + (run == ppc::performance::PerfResults::TypeOfRunning::kPipeline ? "_pipeline" + : "_task"); + }); + +} // namespace egorova_l_gauss_filter_vert