From f8dfae3aeb46c4f513d3653bad8709c0a5b21d81 Mon Sep 17 00:00:00 2001 From: LarisaEgorova Date: Fri, 26 Dec 2025 11:04:47 +0000 Subject: [PATCH 01/14] start --- .pre-commit-config.yaml | 104 +++++++++--------- .../common/include/common.hpp | 23 ++++ .../egorova_l_gauss_filter_vert/data/pic.jpg | Bin 0 -> 23 bytes tasks/egorova_l_gauss_filter_vert/info.json | 9 ++ .../mpi/include/ops_mpi.hpp | 22 ++++ .../mpi/src/ops_mpi.cpp | 93 ++++++++++++++++ tasks/egorova_l_gauss_filter_vert/report.md | 0 .../seq/include/ops_seq.hpp | 22 ++++ .../seq/src/ops_seq.cpp | 56 ++++++++++ .../egorova_l_gauss_filter_vert/settings.json | 7 ++ .../tests/.clang-tidy | 13 +++ .../tests/functional/main.cpp | 81 ++++++++++++++ .../tests/performance/main.cpp | 58 ++++++++++ 13 files changed, 436 insertions(+), 52 deletions(-) create mode 100644 tasks/egorova_l_gauss_filter_vert/common/include/common.hpp create mode 100644 tasks/egorova_l_gauss_filter_vert/data/pic.jpg create mode 100644 tasks/egorova_l_gauss_filter_vert/info.json create mode 100644 tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp create mode 100644 tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp create mode 100644 tasks/egorova_l_gauss_filter_vert/report.md create mode 100644 tasks/egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp create mode 100644 tasks/egorova_l_gauss_filter_vert/seq/src/ops_seq.cpp create mode 100644 tasks/egorova_l_gauss_filter_vert/settings.json create mode 100644 tasks/egorova_l_gauss_filter_vert/tests/.clang-tidy create mode 100644 tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp create mode 100644 tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4affbc7856..fbc52d56d9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,52 +1,52 @@ -# Pre-commit hooks for automated formatting and linting -# See https://pre-commit.com for more information -# See https://pre-commit.com/hooks.html for more hooks - -repos: - # C++ formatting with clang-format - - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v21.1.2 - hooks: - - id: clang-format - files: \.(cpp|hpp|c|h)$ - exclude: ^(3rdparty/|build.*/|install/) - args: [--style=file] - - # CMake formatting - - repo: https://github.com/cheshirekow/cmake-format-precommit - rev: v0.6.13 - hooks: - - id: cmake-format - files: \.(cmake|CMakeLists\.txt)$ - exclude: ^(3rdparty/|build.*/|install/) - - # Ruff Python linter - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.14.4 - hooks: - - id: ruff - args: [--fix] - - id: ruff-format - - # Flake8 Python style/lint checker (supplemental to Ruff) - - repo: https://github.com/pycqa/flake8 - rev: 7.3.0 - hooks: - - id: flake8 - - # YAML linting - - repo: https://github.com/adrienverge/yamllint.git - rev: v1.37.1 - hooks: - - id: yamllint - - # Shell script linting with shellcheck (no docker dependency) - - repo: https://github.com/shellcheck-py/shellcheck-py - rev: v0.10.0.1 - hooks: - - id: shellcheck - files: \.sh$ - -# Configuration -default_stages: [pre-commit] -fail_fast: false +# Pre-commit hooks for automated formatting and linting +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks + +repos: + # C++ formatting with clang-format + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v21.1.2 + hooks: + - id: clang-format + files: \.(cpp|hpp|c|h)$ + exclude: ^(3rdparty/|build.*/|install/) + args: [--style=file] + + # CMake formatting + - repo: https://github.com/cheshirekow/cmake-format-precommit + rev: v0.6.13 + hooks: + - id: cmake-format + files: \.(cmake|CMakeLists\.txt)$ + exclude: ^(3rdparty/|build.*/|install/) + + # Ruff Python linter + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.14.4 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format + + # Flake8 Python style/lint checker (supplemental to Ruff) + - repo: https://github.com/pycqa/flake8 + rev: 7.3.0 + hooks: + - id: flake8 + + # YAML linting + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.37.1 + hooks: + - id: yamllint + + # Shell script linting with shellcheck (no docker dependency) + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.10.0.1 + hooks: + - id: shellcheck + files: \.sh$ + +# Configuration +default_stages: [pre-commit] +fail_fast: false 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..323af08af5 --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include +#include +#include "task/include/task.hpp" + +namespace egorova_l_gauss_filter_vert { + +struct Image { + std::vector data; + int rows = 0; + int cols = 0; + int channels = 1; +}; + +using InType = Image; +using OutType = Image; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace egorova_l_gauss_filter_vert \ No newline at end of file diff --git a/tasks/egorova_l_gauss_filter_vert/data/pic.jpg b/tasks/egorova_l_gauss_filter_vert/data/pic.jpg new file mode 100644 index 0000000000000000000000000000000000000000..637624238c89d914613ed301968bffbf462bc110 GIT binary patch literal 23 bcmWGA<1$h(;xaNd<@(RSzyQYo|NjR7KDY +#include + +namespace egorova_l_gauss_filter_vert { + +EgorovaLGaussFilterVertMPI::EgorovaLGaussFilterVertMPI(const InType &in) : BaseTask() { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; // Используем GetInput() для доступа к input_ +} + +bool EgorovaLGaussFilterVertMPI::ValidationImpl() { + auto& input = GetInput(); // Получаем ссылку на входные данные + return input.rows >= 3 && input.cols >= 3; +} + +bool EgorovaLGaussFilterVertMPI::PreProcessingImpl() { return true; } + +bool EgorovaLGaussFilterVertMPI::RunImpl() { + int rank, size; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + auto& full_img = GetInput(); // Используем GetInput() + auto& output = GetOutput(); // Используем GetOutput() + + int r = full_img.rows; + int c = full_img.cols; + int ch = full_img.channels; + + int q = c / size; + int rem = c % size; + int local_cols = q + (rank < rem ? 1 : 0); + int offset = rank * q + std::min(rank, rem); + + int left_h = (rank > 0) ? 1 : 0; + int right_h = (rank < size - 1) ? 1 : 0; + int total_lc = local_cols + left_h + right_h; + + std::vector local_in(r * total_lc * ch); + std::vector local_out(r * local_cols * ch); + + for (int i = 0; i < r; ++i) { + std::copy(full_img.data.begin() + (i * c + offset - left_h) * ch, + full_img.data.begin() + (i * c + offset + local_cols + right_h) * ch, + local_in.begin() + (i * total_lc * ch)); + } + + const float ker[3][3] = {{0.0625f, 0.125f, 0.0625f}, {0.125f, 0.25f, 0.125f}, {0.0625f, 0.125f, 0.0625f}}; + + for (int y = 0; y < r; ++y) { + for (int x = 0; x < local_cols; ++x) { + int in_x = x + left_h; + for (int k = 0; k < ch; ++k) { + float sum = 0.0f; + for (int ky = -1; ky <= 1; ++ky) { + for (int kx = -1; kx <= 1; ++kx) { + int py = std::clamp(y + ky, 0, r - 1); + int px = in_x + kx; + sum += local_in[(py * total_lc + px) * ch + k] * ker[ky + 1][kx + 1]; + } + } + local_out[(y * local_cols + x) * ch + k] = static_cast(sum); + } + } + } + + std::vector counts(size), displs(size); + for (int i = 0; i < size; ++i) { + counts[i] = (q + (i < rem ? 1 : 0)) * ch; + displs[i] = (i * q + std::min(i, rem)) * ch; + } + + // Создаем output и копируем в него результат + output.rows = r; + output.cols = c; + output.channels = ch; + output.data.resize(r * c * ch); + + for (int i = 0; i < r; ++i) { + MPI_Gatherv(local_out.data() + i * local_cols * ch, local_cols * ch, MPI_BYTE, + output.data.data() + i * c * ch, counts.data(), displs.data(), + MPI_BYTE, 0, MPI_COMM_WORLD); + } + + // Для процесса с рангом 0 результат готов, для других процессов output пуст + // Это нормально для MPI реализации + + return true; +} + +bool EgorovaLGaussFilterVertMPI::PostProcessingImpl() { return true; } +} \ No newline at end of file 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..e69de29bb2 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..3a431554b2 --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#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 \ No newline at end of file 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..8f794fd5c4 --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/seq/src/ops_seq.cpp @@ -0,0 +1,56 @@ +#include "egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp" +#include + +namespace egorova_l_gauss_filter_vert { + +EgorovaLGaussFilterVertSEQ::EgorovaLGaussFilterVertSEQ(const InType &in) : BaseTask() { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; // Используем GetInput() для доступа к input_ +} + +bool EgorovaLGaussFilterVertSEQ::ValidationImpl() { + auto& input = GetInput(); // Получаем ссылку на входные данные + return input.rows >= 3 && input.cols >= 3; +} + +bool EgorovaLGaussFilterVertSEQ::PreProcessingImpl() { return true; } + +bool EgorovaLGaussFilterVertSEQ::RunImpl() { + auto& in = GetInput(); // Получаем ссылку на входные данные + auto& out = GetOutput(); // Получаем ссылку на выходные данные + + std::vector result_data(in.data.size()); + + const float ker[3][3] = { + {0.0625f, 0.125f, 0.0625f}, + {0.125f, 0.25f, 0.125f}, + {0.0625f, 0.125f, 0.0625f} + }; + + for (int y = 0; y < in.rows; ++y) { + for (int x = 0; x < in.cols; ++x) { + for (int c = 0; c < in.channels; ++c) { + float sum = 0.0f; + for (int ky = -1; ky <= 1; ++ky) { + for (int kx = -1; kx <= 1; ++kx) { + int py = std::clamp(y + ky, 0, in.rows - 1); + int px = std::clamp(x + kx, 0, in.cols - 1); + sum += in.data[(py * in.cols + px) * in.channels + c] * ker[ky + 1][kx + 1]; + } + } + result_data[(y * in.cols + x) * in.channels + c] = static_cast(sum); + } + } + } + + // Сохраняем результат в output + out.rows = in.rows; + out.cols = in.cols; + out.channels = in.channels; + out.data = std::move(result_data); + + return true; +} + +bool EgorovaLGaussFilterVertSEQ::PostProcessingImpl() { return true; } +} \ No newline at end of file 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..d68523c24e --- /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..29fe6235d7 --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp @@ -0,0 +1,81 @@ +#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 { + +// ПЕРЕИМЕНОВАНО: EgorovaLRunFuncTestsProcesses -> EgorovaLGaussFilterRunFuncTests +class EgorovaLGaussFilterRunFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::to_string(std::get<0>(test_param)) + "_" + + std::to_string(std::get<1>(test_param)) + "_" + + std::to_string(std::get<2>(test_param)) + "_" + + std::get<3>(test_param); + } + + protected: + void SetUp() override { + auto test_params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + + int r = std::get<0>(test_params); + int c = std::get<1>(test_params); + int ch = std::get<2>(test_params); + + input_data_.rows = r; + input_data_.cols = c; + input_data_.channels = ch; + input_data_.data.assign(r * c * ch, 128); + } + + bool CheckTestOutputData(OutType &output_data) final { + return output_data.rows == input_data_.rows && + output_data.cols == input_data_.cols && + output_data.data.size() == input_data_.data.size(); + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +namespace { + +// ПЕРЕИМЕНОВАНО: Используем новый класс фикстуры +TEST_P(EgorovaLGaussFilterRunFuncTests, GaussFilterVertical) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + 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_20x15"), + std::make_tuple(5, 40, 1, "wide_image"), + std::make_tuple(40, 5, 1, "tall_image") +}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_egorova_l_gauss_filter_vert), + ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_egorova_l_gauss_filter_vert) +); +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kFuncTestName = EgorovaLGaussFilterRunFuncTests::PrintFuncTestName; + +// ПЕРЕИМЕНОВАНО: GaussFilterTests и EgorovaLGaussFilterRunFuncTests +INSTANTIATE_TEST_SUITE_P(EgorovaLGaussFilterFunc, EgorovaLGaussFilterRunFuncTests, kGtestValues, kFuncTestName); + +} // namespace +} // namespace egorova_l_gauss_filter_vert \ No newline at end of file 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..c2ecfc7d3b --- /dev/null +++ b/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include + +#include "util/include/perf_test_util.hpp" + +// Заголовки вашей текущей задачи +#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" + +namespace egorova_l_gauss_filter_vert { + +class EgorovaLGaussFilterVertRunPerfTests : public ppc::util::BaseRunPerfTests { + private: + // Размеры для теста производительности + const int kRows = 2000; + const int kCols = 2000; + const int kChannels = 1; + InType input_data_; + + public: + void SetUp() override { + // Инициализация структуры Image согласно common.hpp + input_data_.rows = kRows; + input_data_.cols = kCols; + input_data_.channels = kChannels; + input_data_.data.assign(kRows * kCols * kChannels, 128); // Тестовое заполнение + } + + bool CheckTestOutputData(OutType &output_data) final { + // Простейшая проверка: выходное изображение должно иметь те же размеры + return output_data.rows == kRows && + output_data.cols == kCols && + output_data.data.size() == input_data_.data.size(); + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(EgorovaLGaussFilterVertRunPerfTests, EgorovaLGaussPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_egorova_l_gauss_filter_vert); +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = EgorovaLGaussFilterVertRunPerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(EgorovaLGaussPerf, EgorovaLGaussFilterVertRunPerfTests, kGtestValues, kPerfTestName); + +} // namespace egorova_l_gauss_filter_vert \ No newline at end of file From efc19ef0a4e40743ce1d62d13a63621a012baad6 Mon Sep 17 00:00:00 2001 From: LarisaEgorova Date: Fri, 26 Dec 2025 11:05:47 +0000 Subject: [PATCH 02/14] del pic --- tasks/egorova_l_gauss_filter_vert/data/pic.jpg | Bin 23 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tasks/egorova_l_gauss_filter_vert/data/pic.jpg diff --git a/tasks/egorova_l_gauss_filter_vert/data/pic.jpg b/tasks/egorova_l_gauss_filter_vert/data/pic.jpg deleted file mode 100644 index 637624238c89d914613ed301968bffbf462bc110..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23 bcmWGA<1$h(;xaNd<@(RSzyQYo|NjR7KDY Date: Fri, 26 Dec 2025 11:20:02 +0000 Subject: [PATCH 03/14] clang --- .../common/include/common.hpp | 11 +- .../mpi/src/ops_mpi.cpp | 121 ++++++++++-------- .../seq/src/ops_seq.cpp | 59 +++++---- .../tests/functional/main.cpp | 19 ++- .../tests/performance/main.cpp | 22 ++-- 5 files changed, 124 insertions(+), 108 deletions(-) diff --git a/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp index 323af08af5..2252e01a18 100644 --- a/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp +++ b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp @@ -1,18 +1,19 @@ #pragma once -#include #include #include #include +#include + #include "task/include/task.hpp" namespace egorova_l_gauss_filter_vert { struct Image { - std::vector data; - int rows = 0; - int cols = 0; - int channels = 1; + int rows; + int cols; + int channels; + std::vector data; }; using InType = Image; 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 index 269735ad6b..cf8bccc63c 100644 --- a/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp +++ b/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp @@ -1,93 +1,104 @@ -#include "egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp" +#include "egorova_l_find_max_val_col_matrix/mpi/include/ops_mpi.hpp" + #include #include +#include +#include +#include namespace egorova_l_gauss_filter_vert { EgorovaLGaussFilterVertMPI::EgorovaLGaussFilterVertMPI(const InType &in) : BaseTask() { SetTypeOfTask(GetStaticTypeOfTask()); - GetInput() = in; // Используем GetInput() для доступа к input_ + GetInput() = in; } bool EgorovaLGaussFilterVertMPI::ValidationImpl() { - auto& input = GetInput(); // Получаем ссылку на входные данные - return input.rows >= 3 && input.cols >= 3; + const auto &in = GetInput(); + return in.rows > 0 && in.cols > 0 && in.channels > 0; } bool EgorovaLGaussFilterVertMPI::PreProcessingImpl() { return true; } +void EgorovaLGaussFilterVertMPI::ApplyFilter(const std::vector &local_in, + std::vector &local_out, + int rows, int local_cols, int total_lc, int ch) { + const std::array, 3> ker = {{ + {0.0625F, 0.125F, 0.0625F}, {0.125F, 0.25F, 0.125F}, {0.0625F, 0.125F, 0.0625F} + }}; + + for (int yy = 0; yy < rows; ++yy) { + for (int xx = 0; xx < local_cols; ++xx) { + for (int cc = 0; cc < ch; ++cc) { + float sum = 0.0F; + for (int ky = -1; ky <= 1; ++ky) { + for (int kx = -1; kx <= 1; ++kx) { + int py = std::clamp(yy + ky, 0, rows - 1); + int px = xx + kx + 1; + sum += static_cast(local_in[((py * total_lc) + px) * ch + cc]) * ker[static_cast(ky + 1)][static_cast(kx + 1)]; + } + } + local_out[((yy * local_cols) + xx) * ch + cc] = static_cast(sum); + } + } + } +} + bool EgorovaLGaussFilterVertMPI::RunImpl() { - int rank, size; + int rank = 0; + int size = 0; MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); - auto& full_img = GetInput(); // Используем GetInput() - auto& output = GetOutput(); // Используем GetOutput() - - int r = full_img.rows; - int c = full_img.cols; + const auto &full_img = GetInput(); + int rr = full_img.rows; + int cc = full_img.cols; int ch = full_img.channels; - int q = c / size; - int rem = c % size; - int local_cols = q + (rank < rem ? 1 : 0); - int offset = rank * q + std::min(rank, rem); + int qq = cc / size; + int rem = cc % size; + int local_cols = qq + (rank < rem ? 1 : 0); + int offset = (rank * qq) + std::min(rank, rem); int left_h = (rank > 0) ? 1 : 0; int right_h = (rank < size - 1) ? 1 : 0; int total_lc = local_cols + left_h + right_h; - std::vector local_in(r * total_lc * ch); - std::vector local_out(r * local_cols * ch); + std::vector local_in(static_cast(rr) * total_lc * ch); + std::vector local_out(static_cast(rr) * local_cols * ch); - for (int i = 0; i < r; ++i) { - std::copy(full_img.data.begin() + (i * c + offset - left_h) * ch, - full_img.data.begin() + (i * c + offset + local_cols + right_h) * ch, - local_in.begin() + (i * total_lc * ch)); + for (int ii = 0; ii < rr; ++ii) { + std::copy(full_img.data.begin() + static_cast(((ii * cc) + offset - left_h) * ch), + full_img.data.begin() + static_cast(((ii * cc) + offset + local_cols + right_h) * ch), + local_in.begin() + static_cast((ii * total_lc * ch))); } - const float ker[3][3] = {{0.0625f, 0.125f, 0.0625f}, {0.125f, 0.25f, 0.125f}, {0.0625f, 0.125f, 0.0625f}}; + ApplyFilter(local_in, local_out, rr, local_cols, total_lc, ch); - for (int y = 0; y < r; ++y) { - for (int x = 0; x < local_cols; ++x) { - int in_x = x + left_h; - for (int k = 0; k < ch; ++k) { - float sum = 0.0f; - for (int ky = -1; ky <= 1; ++ky) { - for (int kx = -1; kx <= 1; ++kx) { - int py = std::clamp(y + ky, 0, r - 1); - int px = in_x + kx; - sum += local_in[(py * total_lc + px) * ch + k] * ker[ky + 1][kx + 1]; - } - } - local_out[(y * local_cols + x) * ch + k] = static_cast(sum); - } - } + std::vector counts(size); + std::vector displs(size); + for (int ii = 0; ii < size; ++ii) { + counts[ii] = (qq + (ii < rem ? 1 : 0)) * ch; + displs[ii] = (ii == 0) ? 0 : displs[ii - 1] + counts[ii - 1]; } - std::vector counts(size), displs(size); - for (int i = 0; i < size; ++i) { - counts[i] = (q + (i < rem ? 1 : 0)) * ch; - displs[i] = (i * q + std::min(i, rem)) * ch; + auto &output = GetOutput(); + if (rank == 0) { + output.rows = rr; + output.cols = cc; + output.channels = ch; + output.data.resize(static_cast(rr) * cc * ch); } - // Создаем output и копируем в него результат - output.rows = r; - output.cols = c; - output.channels = ch; - output.data.resize(r * c * ch); - - for (int i = 0; i < r; ++i) { - MPI_Gatherv(local_out.data() + i * local_cols * ch, local_cols * ch, MPI_BYTE, - output.data.data() + i * c * ch, counts.data(), displs.data(), - MPI_BYTE, 0, MPI_COMM_WORLD); + for (int ii = 0; ii < rr; ++ii) { + MPI_Gatherv(local_out.data() + static_cast(ii * local_cols * ch), local_cols * ch, MPI_BYTE, + output.data.data() + static_cast(ii * cc * ch), counts.data(), displs.data(), MPI_BYTE, + 0, MPI_COMM_WORLD); } - - // Для процесса с рангом 0 результат готов, для других процессов output пуст - // Это нормально для MPI реализации - + return true; } bool EgorovaLGaussFilterVertMPI::PostProcessingImpl() { return true; } -} \ No newline at end of file + +} // namespace egorova_l_gauss_filter_vert \ No newline at end of file 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 index 8f794fd5c4..44474c2187 100644 --- a/tasks/egorova_l_gauss_filter_vert/seq/src/ops_seq.cpp +++ b/tasks/egorova_l_gauss_filter_vert/seq/src/ops_seq.cpp @@ -1,56 +1,63 @@ #include "egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp" + #include +#include +#include +#include +#include +#include +#include namespace egorova_l_gauss_filter_vert { EgorovaLGaussFilterVertSEQ::EgorovaLGaussFilterVertSEQ(const InType &in) : BaseTask() { SetTypeOfTask(GetStaticTypeOfTask()); - GetInput() = in; // Используем GetInput() для доступа к input_ + GetInput() = in; } bool EgorovaLGaussFilterVertSEQ::ValidationImpl() { - auto& input = GetInput(); // Получаем ссылку на входные данные - return input.rows >= 3 && input.cols >= 3; + const auto &in = GetInput(); + return in.rows > 0 && in.cols > 0 && in.channels > 0 && + in.data.size() == static_cast(in.rows * in.cols * in.channels); } bool EgorovaLGaussFilterVertSEQ::PreProcessingImpl() { return true; } bool EgorovaLGaussFilterVertSEQ::RunImpl() { - auto& in = GetInput(); // Получаем ссылку на входные данные - auto& out = GetOutput(); // Получаем ссылку на выходные данные - + const auto &in = GetInput(); + auto &out = GetOutput(); + out.rows = in.rows; + out.cols = in.cols; + out.channels = in.channels; + std::vector result_data(in.data.size()); - const float ker[3][3] = { - {0.0625f, 0.125f, 0.0625f}, - {0.125f, 0.25f, 0.125f}, - {0.0625f, 0.125f, 0.0625f} - }; + const std::array, 3> ker = {{ + {0.0625F, 0.125F, 0.0625F}, + {0.125F, 0.25F, 0.125F}, + {0.0625F, 0.125F, 0.0625F} + }}; - for (int y = 0; y < in.rows; ++y) { - for (int x = 0; x < in.cols; ++x) { - for (int c = 0; c < in.channels; ++c) { - float sum = 0.0f; + for (int yy = 0; yy < in.rows; ++yy) { + for (int xx = 0; xx < in.cols; ++xx) { + for (int cc = 0; cc < in.channels; ++cc) { + float sum = 0.0F; for (int ky = -1; ky <= 1; ++ky) { for (int kx = -1; kx <= 1; ++kx) { - int py = std::clamp(y + ky, 0, in.rows - 1); - int px = std::clamp(x + kx, 0, in.cols - 1); - sum += in.data[(py * in.cols + px) * in.channels + c] * ker[ky + 1][kx + 1]; + int py = std::clamp(yy + ky, 0, in.rows - 1); + int px = std::clamp(xx + kx, 0, in.cols - 1); + sum += static_cast(in.data[((py * in.cols) + px) * in.channels + cc]) * ker[static_cast(ky + 1)][static_cast(kx + 1)]; } } - result_data[(y * in.cols + x) * in.channels + c] = static_cast(sum); + result_data[((yy * in.cols) + xx) * in.channels + cc] = static_cast(sum); } } } - - // Сохраняем результат в output - out.rows = in.rows; - out.cols = in.cols; - out.channels = in.channels; + out.data = std::move(result_data); - return true; } bool EgorovaLGaussFilterVertSEQ::PostProcessingImpl() { return true; } -} \ No newline at end of file + +} // namespace egorova_l_gauss_filter_vert \ No newline at end of file diff --git a/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp b/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp index 29fe6235d7..6673b40820 100644 --- a/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp +++ b/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp @@ -1,17 +1,18 @@ #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" +#include "util/include/util.hpp" namespace egorova_l_gauss_filter_vert { -// ПЕРЕИМЕНОВАНО: EgorovaLRunFuncTestsProcesses -> EgorovaLGaussFilterRunFuncTests class EgorovaLGaussFilterRunFuncTests : public ppc::util::BaseRunFuncTests { public: static std::string PrintTestParam(const TestType &test_param) { @@ -25,14 +26,14 @@ class EgorovaLGaussFilterRunFuncTests : public ppc::util::BaseRunFuncTests(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); - int r = std::get<0>(test_params); - int c = std::get<1>(test_params); + int rr = std::get<0>(test_params); + int cc = std::get<1>(test_params); int ch = std::get<2>(test_params); - input_data_.rows = r; - input_data_.cols = c; + input_data_.rows = rr; + input_data_.cols = cc; input_data_.channels = ch; - input_data_.data.assign(r * c * ch, 128); + input_data_.data.assign(static_cast(rr * cc * ch), 128); } bool CheckTestOutputData(OutType &output_data) final { @@ -51,7 +52,6 @@ class EgorovaLGaussFilterRunFuncTests : public ppc::util::BaseRunFuncTests; -// ПЕРЕИМЕНОВАНО: GaussFilterTests и EgorovaLGaussFilterRunFuncTests INSTANTIATE_TEST_SUITE_P(EgorovaLGaussFilterFunc, EgorovaLGaussFilterRunFuncTests, kGtestValues, kFuncTestName); } // namespace diff --git a/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp b/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp index c2ecfc7d3b..bec41b6a3e 100644 --- a/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp +++ b/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp @@ -1,8 +1,6 @@ #include +#include #include -#include -#include -#include #include "util/include/perf_test_util.hpp" @@ -16,24 +14,24 @@ namespace egorova_l_gauss_filter_vert { class EgorovaLGaussFilterVertRunPerfTests : public ppc::util::BaseRunPerfTests { private: // Размеры для теста производительности - const int kRows = 2000; - const int kCols = 2000; - const int kChannels = 1; + const int kRows_ = 2000; + const int kCols_ = 2000; + const int kChannels_ = 1; InType input_data_; public: void SetUp() override { // Инициализация структуры Image согласно common.hpp - input_data_.rows = kRows; - input_data_.cols = kCols; - input_data_.channels = kChannels; - input_data_.data.assign(kRows * kCols * kChannels, 128); // Тестовое заполнение + input_data_.rows = kRows_; + input_data_.cols = kCols_; + input_data_.channels = kChannels_; + input_data_.data.assign(static_cast(kRows_ * kCols_ * kChannels_), 128); // Тестовое заполнение } bool CheckTestOutputData(OutType &output_data) final { // Простейшая проверка: выходное изображение должно иметь те же размеры - return output_data.rows == kRows && - output_data.cols == kCols && + return output_data.rows == kRows_ && + output_data.cols == kCols_ && output_data.data.size() == input_data_.data.size(); } From 2caaddba8e07715ecbad3eb219d417ef0443b6a2 Mon Sep 17 00:00:00 2001 From: LarisaEgorova Date: Fri, 26 Dec 2025 12:37:14 +0000 Subject: [PATCH 04/14] clang2 --- .pre-commit-config.yaml | 104 +++---- .../common/include/common.hpp | 49 ++-- .../mpi/include/ops_mpi.hpp | 51 ++-- .../mpi/src/ops_mpi.cpp | 260 +++++++++++------- .../seq/include/ops_seq.hpp | 44 +-- .../seq/src/ops_seq.cpp | 142 +++++----- .../tests/.clang-tidy | 26 +- .../tests/functional/main.cpp | 152 +++++----- .../tests/performance/main.cpp | 103 ++++--- 9 files changed, 496 insertions(+), 435 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fbc52d56d9..4affbc7856 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,52 +1,52 @@ -# Pre-commit hooks for automated formatting and linting -# See https://pre-commit.com for more information -# See https://pre-commit.com/hooks.html for more hooks - -repos: - # C++ formatting with clang-format - - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v21.1.2 - hooks: - - id: clang-format - files: \.(cpp|hpp|c|h)$ - exclude: ^(3rdparty/|build.*/|install/) - args: [--style=file] - - # CMake formatting - - repo: https://github.com/cheshirekow/cmake-format-precommit - rev: v0.6.13 - hooks: - - id: cmake-format - files: \.(cmake|CMakeLists\.txt)$ - exclude: ^(3rdparty/|build.*/|install/) - - # Ruff Python linter - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.14.4 - hooks: - - id: ruff - args: [--fix] - - id: ruff-format - - # Flake8 Python style/lint checker (supplemental to Ruff) - - repo: https://github.com/pycqa/flake8 - rev: 7.3.0 - hooks: - - id: flake8 - - # YAML linting - - repo: https://github.com/adrienverge/yamllint.git - rev: v1.37.1 - hooks: - - id: yamllint - - # Shell script linting with shellcheck (no docker dependency) - - repo: https://github.com/shellcheck-py/shellcheck-py - rev: v0.10.0.1 - hooks: - - id: shellcheck - files: \.sh$ - -# Configuration -default_stages: [pre-commit] -fail_fast: false +# Pre-commit hooks for automated formatting and linting +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks + +repos: + # C++ formatting with clang-format + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v21.1.2 + hooks: + - id: clang-format + files: \.(cpp|hpp|c|h)$ + exclude: ^(3rdparty/|build.*/|install/) + args: [--style=file] + + # CMake formatting + - repo: https://github.com/cheshirekow/cmake-format-precommit + rev: v0.6.13 + hooks: + - id: cmake-format + files: \.(cmake|CMakeLists\.txt)$ + exclude: ^(3rdparty/|build.*/|install/) + + # Ruff Python linter + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.14.4 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format + + # Flake8 Python style/lint checker (supplemental to Ruff) + - repo: https://github.com/pycqa/flake8 + rev: 7.3.0 + hooks: + - id: flake8 + + # YAML linting + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.37.1 + hooks: + - id: yamllint + + # Shell script linting with shellcheck (no docker dependency) + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.10.0.1 + hooks: + - id: shellcheck + files: \.sh$ + +# Configuration +default_stages: [pre-commit] +fail_fast: false diff --git a/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp index 2252e01a18..61b63ed78c 100644 --- a/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp +++ b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp @@ -1,24 +1,25 @@ -#pragma once - -#include -#include -#include -#include - -#include "task/include/task.hpp" - -namespace egorova_l_gauss_filter_vert { - -struct Image { - int rows; - int cols; - int channels; - std::vector data; -}; - -using InType = Image; -using OutType = Image; -using TestType = std::tuple; -using BaseTask = ppc::task::Task; - -} // namespace egorova_l_gauss_filter_vert \ No newline at end of file +#pragma once + +#include +#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/mpi/include/ops_mpi.hpp b/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp index 7ad0d4210f..e3d276791a 100644 --- a/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp +++ b/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp @@ -1,22 +1,29 @@ -#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 \ No newline at end of file +#pragma once + +#include +#include + +#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); + + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + private: + // ОБЯЗАТЕЛЬНО: Объявление метода, который вы реализуете в .cpp + static void ApplyFilter(const std::vector &local_in, std::vector &local_out, int rows, + int local_cols, int total_lc, int ch); +}; + +} // 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 index cf8bccc63c..dd1b1ee4b1 100644 --- a/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp +++ b/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp @@ -1,104 +1,156 @@ -#include "egorova_l_find_max_val_col_matrix/mpi/include/ops_mpi.hpp" - -#include -#include -#include -#include -#include - -namespace egorova_l_gauss_filter_vert { - -EgorovaLGaussFilterVertMPI::EgorovaLGaussFilterVertMPI(const InType &in) : BaseTask() { - SetTypeOfTask(GetStaticTypeOfTask()); - GetInput() = in; -} - -bool EgorovaLGaussFilterVertMPI::ValidationImpl() { - const auto &in = GetInput(); - return in.rows > 0 && in.cols > 0 && in.channels > 0; -} - -bool EgorovaLGaussFilterVertMPI::PreProcessingImpl() { return true; } - -void EgorovaLGaussFilterVertMPI::ApplyFilter(const std::vector &local_in, - std::vector &local_out, - int rows, int local_cols, int total_lc, int ch) { - const std::array, 3> ker = {{ - {0.0625F, 0.125F, 0.0625F}, {0.125F, 0.25F, 0.125F}, {0.0625F, 0.125F, 0.0625F} - }}; - - for (int yy = 0; yy < rows; ++yy) { - for (int xx = 0; xx < local_cols; ++xx) { - for (int cc = 0; cc < ch; ++cc) { - float sum = 0.0F; - for (int ky = -1; ky <= 1; ++ky) { - for (int kx = -1; kx <= 1; ++kx) { - int py = std::clamp(yy + ky, 0, rows - 1); - int px = xx + kx + 1; - sum += static_cast(local_in[((py * total_lc) + px) * ch + cc]) * ker[static_cast(ky + 1)][static_cast(kx + 1)]; - } - } - local_out[((yy * local_cols) + xx) * ch + cc] = static_cast(sum); - } - } - } -} - -bool EgorovaLGaussFilterVertMPI::RunImpl() { - int rank = 0; - int size = 0; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - MPI_Comm_size(MPI_COMM_WORLD, &size); - - const auto &full_img = GetInput(); - int rr = full_img.rows; - int cc = full_img.cols; - int ch = full_img.channels; - - int qq = cc / size; - int rem = cc % size; - int local_cols = qq + (rank < rem ? 1 : 0); - int offset = (rank * qq) + std::min(rank, rem); - - int left_h = (rank > 0) ? 1 : 0; - int right_h = (rank < size - 1) ? 1 : 0; - int total_lc = local_cols + left_h + right_h; - - std::vector local_in(static_cast(rr) * total_lc * ch); - std::vector local_out(static_cast(rr) * local_cols * ch); - - for (int ii = 0; ii < rr; ++ii) { - std::copy(full_img.data.begin() + static_cast(((ii * cc) + offset - left_h) * ch), - full_img.data.begin() + static_cast(((ii * cc) + offset + local_cols + right_h) * ch), - local_in.begin() + static_cast((ii * total_lc * ch))); - } - - ApplyFilter(local_in, local_out, rr, local_cols, total_lc, ch); - - std::vector counts(size); - std::vector displs(size); - for (int ii = 0; ii < size; ++ii) { - counts[ii] = (qq + (ii < rem ? 1 : 0)) * ch; - displs[ii] = (ii == 0) ? 0 : displs[ii - 1] + counts[ii - 1]; - } - - auto &output = GetOutput(); - if (rank == 0) { - output.rows = rr; - output.cols = cc; - output.channels = ch; - output.data.resize(static_cast(rr) * cc * ch); - } - - for (int ii = 0; ii < rr; ++ii) { - MPI_Gatherv(local_out.data() + static_cast(ii * local_cols * ch), local_cols * ch, MPI_BYTE, - output.data.data() + static_cast(ii * cc * ch), counts.data(), displs.data(), MPI_BYTE, - 0, MPI_COMM_WORLD); - } - - return true; -} - -bool EgorovaLGaussFilterVertMPI::PostProcessingImpl() { return true; } - -} // namespace egorova_l_gauss_filter_vert \ No newline at end of file +#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 { + +EgorovaLGaussFilterVertMPI::EgorovaLGaussFilterVertMPI(const InType &in) : BaseTask() { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool EgorovaLGaussFilterVertMPI::ValidationImpl() { + const auto &in = GetInput(); + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + std::array params{}; + if (rank == 0) { + params[0] = in.rows; + params[1] = in.cols; + params[2] = in.channels; + } + MPI_Bcast(params.data(), 3, MPI_INT, 0, MPI_COMM_WORLD); + + return (params[0] > 0 && params[1] > 0 && params[2] > 0); +} + +bool EgorovaLGaussFilterVertMPI::PreProcessingImpl() { + return true; +} + +void EgorovaLGaussFilterVertMPI::ApplyFilter(const std::vector &local_in, std::vector &local_out, + int rows, int local_cols, int total_lc, int ch) { + const std::array ker = {0.0625F, 0.125F, 0.0625F, 0.125F, 0.25F, 0.125F, 0.0625F, 0.125F, 0.0625F}; + const float *ker_ptr = ker.data(); + + for (int yy = 0; yy < rows; ++yy) { + for (int xx = 0; xx < local_cols; ++xx) { + for (int cc = 0; cc < ch; ++cc) { + float sum = 0.0F; + for (int ky = -1; ky <= 1; ++ky) { + for (int kx = -1; kx <= 1; ++kx) { + int py = std::clamp(yy + ky, 0, rows - 1); + int px = xx + kx + 1; + + std::size_t in_idx = + ((((static_cast(py) * static_cast(total_lc)) + static_cast(px)) * + static_cast(ch)) + + static_cast(cc)); + + std::size_t k_idx = (static_cast(ky + 1) * 3) + static_cast(kx + 1); + sum += (static_cast(local_in[in_idx]) * ker_ptr[k_idx]); + } + } + std::size_t out_idx = + ((((static_cast(yy) * static_cast(local_cols)) + static_cast(xx)) * + static_cast(ch)) + + static_cast(cc)); + + local_out[out_idx] = static_cast(sum); + } + } + } +} + +bool EgorovaLGaussFilterVertMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + const auto &full_img = GetInput(); + int rr = 0; + int cc = 0; + int ch = 0; + + if (rank == 0) { + rr = full_img.rows; + cc = full_img.cols; + ch = full_img.channels; + } + + MPI_Bcast(&rr, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&cc, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&ch, 1, MPI_INT, 0, MPI_COMM_WORLD); + + int qq = (cc / size); + int rem = (cc % size); + int local_cols = (qq + (rank < rem ? 1 : 0)); + int offset = ((rank * qq) + std::min(rank, rem)); + + int left_h = ((rank > 0) ? 1 : 0); + int right_h = ((rank < size - 1) ? 1 : 0); + int total_lc = (local_cols + left_h + right_h); + + std::vector local_in(static_cast(rr) * total_lc * ch); + std::vector local_out(static_cast(rr) * local_cols * ch); + + std::vector shared_data; + if (rank == 0) { + shared_data = full_img.data; + } else { + shared_data.resize(static_cast(rr) * cc * ch); + } + MPI_Bcast(shared_data.data(), static_cast(shared_data.size()), MPI_BYTE, 0, MPI_COMM_WORLD); + + for (int ii = 0; ii < rr; ++ii) { + std::size_t src_start = (static_cast(ii) * cc + (offset - left_h)) * ch; + std::size_t dest_start = (static_cast(ii) * total_lc) * ch; + std::size_t count = static_cast(total_lc) * ch; + + std::copy(std::next(shared_data.begin(), static_cast(src_start)), + std::next(shared_data.begin(), static_cast(src_start + count)), + std::next(local_in.begin(), static_cast(dest_start))); + } + + ApplyFilter(local_in, local_out, rr, local_cols, total_lc, ch); + + std::vector counts(size); + std::vector displs(size); + for (int ii = 0; ii < size; ++ii) { + counts[ii] = (qq + (ii < rem ? 1 : 0)) * ch; + displs[ii] = (ii == 0) ? 0 : displs[ii - 1] + counts[ii - 1]; + } + + auto &output = GetOutput(); + output.rows = rr; + output.cols = cc; + output.channels = ch; + output.data.resize(static_cast(rr) * cc * ch); + + for (int ii = 0; ii < rr; ++ii) { + std::size_t send_offset = (static_cast(ii) * local_cols * ch); + std::size_t recv_offset = (static_cast(ii) * cc * ch); + + MPI_Allgatherv(local_out.data() + send_offset, counts[rank], MPI_BYTE, output.data.data() + recv_offset, + counts.data(), displs.data(), MPI_BYTE, 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/seq/include/ops_seq.hpp b/tasks/egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp index 3a431554b2..5c9c496250 100644 --- a/tasks/egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp +++ b/tasks/egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp @@ -1,22 +1,22 @@ -#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 \ No newline at end of file +#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 index 44474c2187..ac46452ce1 100644 --- a/tasks/egorova_l_gauss_filter_vert/seq/src/ops_seq.cpp +++ b/tasks/egorova_l_gauss_filter_vert/seq/src/ops_seq.cpp @@ -1,63 +1,79 @@ -#include "egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp" - -#include -#include -#include -#include -#include -#include -#include - -namespace egorova_l_gauss_filter_vert { - -EgorovaLGaussFilterVertSEQ::EgorovaLGaussFilterVertSEQ(const InType &in) : BaseTask() { - SetTypeOfTask(GetStaticTypeOfTask()); - GetInput() = in; -} - -bool EgorovaLGaussFilterVertSEQ::ValidationImpl() { - const auto &in = GetInput(); - return in.rows > 0 && in.cols > 0 && in.channels > 0 && - in.data.size() == static_cast(in.rows * in.cols * in.channels); -} - -bool EgorovaLGaussFilterVertSEQ::PreProcessingImpl() { return true; } - -bool EgorovaLGaussFilterVertSEQ::RunImpl() { - const auto &in = GetInput(); - auto &out = GetOutput(); - out.rows = in.rows; - out.cols = in.cols; - out.channels = in.channels; - - std::vector result_data(in.data.size()); - - const std::array, 3> ker = {{ - {0.0625F, 0.125F, 0.0625F}, - {0.125F, 0.25F, 0.125F}, - {0.0625F, 0.125F, 0.0625F} - }}; - - for (int yy = 0; yy < in.rows; ++yy) { - for (int xx = 0; xx < in.cols; ++xx) { - for (int cc = 0; cc < in.channels; ++cc) { - float sum = 0.0F; - for (int ky = -1; ky <= 1; ++ky) { - for (int kx = -1; kx <= 1; ++kx) { - int py = std::clamp(yy + ky, 0, in.rows - 1); - int px = std::clamp(xx + kx, 0, in.cols - 1); - sum += static_cast(in.data[((py * in.cols) + px) * in.channels + cc]) * ker[static_cast(ky + 1)][static_cast(kx + 1)]; - } - } - result_data[((yy * in.cols) + xx) * in.channels + cc] = static_cast(sum); - } - } - } - - out.data = std::move(result_data); - return true; -} - -bool EgorovaLGaussFilterVertSEQ::PostProcessingImpl() { return true; } - -} // namespace egorova_l_gauss_filter_vert \ No newline at end of file +#include "egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp" + +#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) : BaseTask() { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool EgorovaLGaussFilterVertSEQ::ValidationImpl() { + const auto &in = GetInput(); + return in.rows > 0 && in.cols > 0 && in.channels > 0 && + in.data.size() == (static_cast(in.rows) * in.cols * in.channels); +} + +bool EgorovaLGaussFilterVertSEQ::PreProcessingImpl() { + return true; +} + +bool EgorovaLGaussFilterVertSEQ::RunImpl() { + const auto &in = GetInput(); + auto &out = GetOutput(); + out.rows = in.rows; + out.cols = in.cols; + out.channels = in.channels; + + std::vector result_data(in.data.size()); + + const std::array ker = {0.0625F, 0.125F, 0.0625F, 0.125F, 0.25F, 0.125F, 0.0625F, 0.125F, 0.0625F}; + const float *ker_ptr = ker.data(); + + for (int yy = 0; yy < in.rows; ++yy) { + for (int xx = 0; xx < in.cols; ++xx) { + for (int cc = 0; cc < in.channels; ++cc) { + float sum = 0.0F; + for (int ky = -1; ky <= 1; ++ky) { + for (int kx = -1; kx <= 1; ++kx) { + int py = std::clamp(yy + ky, 0, in.rows - 1); + int px = std::clamp(xx + kx, 0, in.cols - 1); + + std::size_t in_idx = + ((((static_cast(py) * static_cast(in.cols)) + static_cast(px)) * + static_cast(in.channels)) + + static_cast(cc)); + + std::size_t k_idx = (static_cast(ky + 1) * 3) + static_cast(kx + 1); + + // Использование указателя на данные массива убирает предупреждение о неконстантном индексе + sum += (static_cast(in.data[in_idx]) * ker_ptr[k_idx]); + } + } + std::size_t out_idx = + ((((static_cast(yy) * static_cast(in.cols)) + static_cast(xx)) * + static_cast(in.channels)) + + static_cast(cc)); + + result_data[out_idx] = static_cast(sum); + } + } + } + + out.data = std::move(result_data); + return true; +} + +bool EgorovaLGaussFilterVertSEQ::PostProcessingImpl() { + return true; +} + +} // namespace egorova_l_gauss_filter_vert diff --git a/tasks/egorova_l_gauss_filter_vert/tests/.clang-tidy b/tasks/egorova_l_gauss_filter_vert/tests/.clang-tidy index d68523c24e..ef43b7aa8a 100644 --- a/tasks/egorova_l_gauss_filter_vert/tests/.clang-tidy +++ b/tasks/egorova_l_gauss_filter_vert/tests/.clang-tidy @@ -1,13 +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 +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 index 6673b40820..fd9cadee12 100644 --- a/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp +++ b/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp @@ -1,80 +1,72 @@ -#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" -#include "util/include/util.hpp" - -namespace egorova_l_gauss_filter_vert { - -class EgorovaLGaussFilterRunFuncTests : public ppc::util::BaseRunFuncTests { - public: - static std::string PrintTestParam(const TestType &test_param) { - return std::to_string(std::get<0>(test_param)) + "_" + - std::to_string(std::get<1>(test_param)) + "_" + - std::to_string(std::get<2>(test_param)) + "_" + - std::get<3>(test_param); - } - - protected: - void SetUp() override { - auto test_params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); - - int rr = std::get<0>(test_params); - int cc = std::get<1>(test_params); - int ch = std::get<2>(test_params); - - input_data_.rows = rr; - input_data_.cols = cc; - input_data_.channels = ch; - input_data_.data.assign(static_cast(rr * cc * ch), 128); - } - - bool CheckTestOutputData(OutType &output_data) final { - return output_data.rows == input_data_.rows && - output_data.cols == input_data_.cols && - output_data.data.size() == input_data_.data.size(); - } - - InType GetTestInputData() final { - return input_data_; - } - - private: - InType input_data_; -}; - -namespace { - -TEST_P(EgorovaLGaussFilterRunFuncTests, GaussFilterVertical) { - ExecuteTest(GetParam()); -} - -const std::array kTestParam = { - 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_20x15"), - std::make_tuple(5, 40, 1, "wide_image"), - std::make_tuple(40, 5, 1, "tall_image") -}; - -const auto kTestTasksList = std::tuple_cat( - ppc::util::AddFuncTask( - kTestParam, PPC_SETTINGS_egorova_l_gauss_filter_vert), - ppc::util::AddFuncTask( - kTestParam, PPC_SETTINGS_egorova_l_gauss_filter_vert) -); -const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); - -const auto kFuncTestName = EgorovaLGaussFilterRunFuncTests::PrintFuncTestName; - -INSTANTIATE_TEST_SUITE_P(EgorovaLGaussFilterFunc, EgorovaLGaussFilterRunFuncTests, kGtestValues, kFuncTestName); - -} // namespace -} // namespace egorova_l_gauss_filter_vert \ No newline at end of file +#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" +#include "util/include/util.hpp" + +namespace egorova_l_gauss_filter_vert { + +class EgorovaLGaussFilterRunFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::to_string(std::get<0>(test_param)) + "_" + std::to_string(std::get<1>(test_param)) + "_" + + std::to_string(std::get<2>(test_param)) + "_" + std::get<3>(test_param); + } + + protected: + void SetUp() override { + auto test_params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + + int rr = std::get<0>(test_params); + int cc = std::get<1>(test_params); + int ch = std::get<2>(test_params); + + input_data_.rows = rr; + input_data_.cols = cc; + input_data_.channels = ch; + input_data_.data.assign(static_cast(rr) * cc * ch, 128); + } + + bool CheckTestOutputData(OutType &output_data) final { + return output_data.rows == input_data_.rows && output_data.cols == input_data_.cols && + output_data.data.size() == input_data_.data.size(); + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +namespace { + +TEST_P(EgorovaLGaussFilterRunFuncTests, GaussFilterVertical) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + 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_20x15"), std::make_tuple(5, 40, 1, "wide_image"), + std::make_tuple(40, 5, 1, "tall_image")}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_egorova_l_gauss_filter_vert), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_egorova_l_gauss_filter_vert)); +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kFuncTestName = EgorovaLGaussFilterRunFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(EgorovaLGaussFilterFunc, EgorovaLGaussFilterRunFuncTests, kGtestValues, kFuncTestName); + +} // namespace +} // 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 index bec41b6a3e..6a75832ed1 100644 --- a/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp +++ b/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp @@ -1,56 +1,49 @@ -#include +#include + #include -#include - -#include "util/include/perf_test_util.hpp" - -// Заголовки вашей текущей задачи -#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" - -namespace egorova_l_gauss_filter_vert { - -class EgorovaLGaussFilterVertRunPerfTests : public ppc::util::BaseRunPerfTests { - private: - // Размеры для теста производительности - const int kRows_ = 2000; - const int kCols_ = 2000; - const int kChannels_ = 1; - InType input_data_; - - public: - void SetUp() override { - // Инициализация структуры Image согласно common.hpp - input_data_.rows = kRows_; - input_data_.cols = kCols_; - input_data_.channels = kChannels_; - input_data_.data.assign(static_cast(kRows_ * kCols_ * kChannels_), 128); // Тестовое заполнение - } - - bool CheckTestOutputData(OutType &output_data) final { - // Простейшая проверка: выходное изображение должно иметь те же размеры - return output_data.rows == kRows_ && - output_data.cols == kCols_ && - output_data.data.size() == input_data_.data.size(); - } - - InType GetTestInputData() final { - return input_data_; - } -}; - -TEST_P(EgorovaLGaussFilterVertRunPerfTests, EgorovaLGaussPerfModes) { - ExecuteTest(GetParam()); -} - -const auto kAllPerfTasks = - ppc::util::MakeAllPerfTasks( - PPC_SETTINGS_egorova_l_gauss_filter_vert); -const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); - -const auto kPerfTestName = EgorovaLGaussFilterVertRunPerfTests::CustomPerfTestName; - -INSTANTIATE_TEST_SUITE_P(EgorovaLGaussPerf, EgorovaLGaussFilterVertRunPerfTests, kGtestValues, kPerfTestName); - -} // namespace egorova_l_gauss_filter_vert \ No newline at end of file +#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/perf_test_util.hpp" + +namespace egorova_l_gauss_filter_vert { + +class EgorovaLGaussFilterVertRunPerfTests : public ppc::util::BaseRunPerfTests { + private: + const int kRows_ = 2000; + const int kCols_ = 2000; + const int kChannels_ = 1; + InType input_data_; + + public: + void SetUp() override { + input_data_.rows = kRows_; + input_data_.cols = kCols_; + input_data_.channels = kChannels_; + input_data_.data.assign(static_cast(kRows_) * kCols_ * kChannels_, 128); + } + bool CheckTestOutputData(OutType &output_data) final { + return output_data.rows == kRows_ && output_data.cols == kCols_ && + output_data.data.size() == input_data_.data.size(); + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(EgorovaLGaussFilterVertRunPerfTests, EgorovaLGaussPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_egorova_l_gauss_filter_vert); +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = EgorovaLGaussFilterVertRunPerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(EgorovaLGaussPerf, EgorovaLGaussFilterVertRunPerfTests, kGtestValues, kPerfTestName); + +} // namespace egorova_l_gauss_filter_vert From df947766ed6508dccf8876b9393f02263e6fa571 Mon Sep 17 00:00:00 2001 From: LarisaEgorova Date: Fri, 26 Dec 2025 13:36:30 +0000 Subject: [PATCH 05/14] otchet --- tasks/egorova_l_gauss_filter_vert/report.md | 151 ++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/tasks/egorova_l_gauss_filter_vert/report.md b/tasks/egorova_l_gauss_filter_vert/report.md index e69de29bb2..445d85c791 100644 --- a/tasks/egorova_l_gauss_filter_vert/report.md +++ b/tasks/egorova_l_gauss_filter_vert/report.md @@ -0,0 +1,151 @@ +# Вертикальный фильтр Гаусса (3x3) +- **Студент:** Егорова Лариса Алексеевна +- **Группа:** 3823Б1ФИ1 +- **Технология:** MPI, SEQ +- **Вариант:** 27 + +## 1. Введение +Фильтрация изображений является одной из ключевых задач компьютерного зрения и цифровой обработки сигналов. Фильтр Гаусса применяется для снижения уровня цветового шума и сглаживания деталей путем низкочастотной фильтрации. В условиях работы с изображениями высокого разрешения последовательная обработка становится узким местом, что обуславливает необходимость использования технологий параллельного программирования, таких как MPI, для распределения вычислительной нагрузки между ядрами процессора. + +## 2. Постановка задачи +Необходимо реализовать параллельный алгоритм фильтрации изображения с использованием ядра Гаусса размером $3 \times 3$. +- **Вход:** Структура данных, содержащая количество строк, столбцов, каналов и одномерный вектор пикселей `uint8_t`. +- **Выход:** Преобразованный вектор пикселей того же размера. +- **Особенности:** Реализация должна корректно обрабатывать границы изображения (используя зажатие координат `clamp`) и обеспечивать синхронизацию "призрачных зон" (halo-зоны) для непрерывности фильтрации на стыках данных разных процессов. + +## 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. Для пикселей на краях изображения применяется стратегия **Clamping**: если индекс выходит за пределы, используется значение ближайшего существующего пикселя, что предотвращает появление артефактов на границах. + +## 4. Схема распараллеливания (MPI) +Для минимизации межпроцессных взаимодействий выбрана одномерная вертикальная декомпозиция (разделение изображения на полосы по столбцам). + +1. **Распределение нагрузки:** Изображение делится на $N$ вертикальных полос, где $N$ — число процессов. Каждый процесс отвечает за вычисление своей части столбцов. +2. **Организация Halo-зоны:** Так как фильтр имеет радиус 1, каждому процессу для расчета крайних пикселей своей полосы требуется один соседний столбец от предыдущего и последующего процессов. +3. **Коллективные операции:** - `MPI_Bcast` используется для передачи метаданных и исходного массива данных. + - `MPI_Allgatherv` выполняет сборку обработанных полос обратно в единое изображение на всех узлах. + +## 5. Детали реализации +- **Управление памятью:** Использование `std::vector` и `std::array` обеспечивает безопасность работы с памятью. +- **Оптимизация:** Прямой доступ к коэффициентам ядра через метод `.data()` минимизирует накладные расходы внутри вложенных циклов свертки. Промежуточные вычисления ведутся в типе `float` для сохранения точности. +- **Статический анализ:** Код соответствует стандартам `cppcoreguidelines`, в частности, заменены все C-style массивы и исправлены потенциальные ошибки приведения типов (`narrowing conversion`). + +## 6. Настройка экспериментов +- **Процессор:** 13th Gen Intel(R) Core(TM) i5-13420H (2.10 GHz, 8 ядер / 12 потоков) +- **Оперативная память:** 16,0 ГБ +- **ОС:** Windows 10 (WSL2 / Docker Desktop) +- **Инструментарий:** Clang-21, MPI, CMake, Release build. + +## 7. Результаты и обсуждение + +### 7.1 Корректность +Корректность алгоритма верифицирована с помощью модульных тестов: +- Сравнение с последовательной версией на различных форматах данных. +- Проверка многоканальных (RGB) и полутоновых изображений. +- Валидация работы на прямоугольных (неквадратных) матрицах. + +### 7.2 Производительность +Результаты замеров времени выполнения (режим `task_run`) для изображения **2000x2000**: + +| Режим | Кол-во процессов | Время, с | Ускорение | Эффективность | +|-------|:----------------:|:--------:|:---------:|:-------------:| +| seq | 1 | 0.03016 | 1.00 | N/A | +| mpi | 1 | 0.03017 | 1.00 | 100.0% | +| mpi | 2 | 0.01386 | 2.17 | 108.5% | +| mpi | 4 | 0.01189 | 2.54 | 63.5% | + +**Анализ результатов:** +- **Суперлинейное ускорение (2 процесса):** Ускорение 2.17 (эффективность > 100%) связано с тем, что при разделении данных на части они начинают лучше утилизировать локальную кэш-память процессора ($L2/L3$), что сокращает задержки при доступе к памяти. +- **Масштабируемость (4 процесса):** Время выполнения достигает минимума (0.01189 с), однако эффективность падает до 63.5%. Это обусловлено тем, что на данном объеме данных затраты на коллективные операции обмена (`MPI_Allgatherv`) становятся сопоставимы со временем самих вычислений. + +## 8. Заключение +В ходе выполнения работы была реализована параллельная версия вертикального фильтра Гаусса. Использование MPI позволило сократить время обработки в 2.54 раза на четырех процессах. Дальнейшее повышение эффективности масштабирования возможно при обработке изображений сверхвысокого разрешения (4K и выше), где вычислительная нагрузка будет преобладать над коммуникационной. + +## 9. Источники +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); + + const auto &full_img = GetInput(); + int rr = 0; + int cc = 0; + int ch = 0; + + if (rank == 0) { + rr = full_img.rows; + cc = full_img.cols; + ch = full_img.channels; + } + + MPI_Bcast(&rr, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&cc, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&ch, 1, MPI_INT, 0, MPI_COMM_WORLD); + + int qq = (cc / size); + int rem = (cc % size); + int local_cols = (qq + (rank < rem ? 1 : 0)); + int offset = ((rank * qq) + std::min(rank, rem)); + + int left_h = ((rank > 0) ? 1 : 0); + int right_h = ((rank < size - 1) ? 1 : 0); + int total_lc = (local_cols + left_h + right_h); + + std::vector local_in(static_cast(rr) * total_lc * ch); + std::vector local_out(static_cast(rr) * local_cols * ch); + + std::vector shared_data; + if (rank == 0) { + shared_data = full_img.data; + } else { + shared_data.resize(static_cast(rr) * cc * ch); + } + MPI_Bcast(shared_data.data(), static_cast(shared_data.size()), MPI_BYTE, 0, MPI_COMM_WORLD); + + for (int ii = 0; ii < rr; ++ii) { + std::size_t src_start = (static_cast(ii) * cc + (offset - left_h)) * ch; + std::size_t dest_start = (static_cast(ii) * total_lc) * ch; + std::size_t count = static_cast(total_lc) * ch; + + std::copy(std::next(shared_data.begin(), static_cast(src_start)), + std::next(shared_data.begin(), static_cast(src_start + count)), + std::next(local_in.begin(), static_cast(dest_start))); + } + + ApplyFilter(local_in, local_out, rr, local_cols, total_lc, ch); + + std::vector counts(size); + std::vector displs(size); + for (int ii = 0; ii < size; ++ii) { + counts[ii] = (qq + (ii < rem ? 1 : 0)) * ch; + displs[ii] = (ii == 0) ? 0 : displs[ii - 1] + counts[ii - 1]; + } + + auto &output = GetOutput(); + output.rows = rr; + output.cols = cc; + output.channels = ch; + output.data.resize(static_cast(rr) * cc * ch); + + for (int ii = 0; ii < rr; ++ii) { + std::size_t send_offset = (static_cast(ii) * local_cols * ch); + std::size_t recv_offset = (static_cast(ii) * cc * ch); + + MPI_Allgatherv(local_out.data() + send_offset, counts[rank], MPI_BYTE, output.data.data() + recv_offset, + counts.data(), displs.data(), MPI_BYTE, MPI_COMM_WORLD); + } + + return true; +} From 8735cb63a90b6d2d48902970bb8d7aab53084261 Mon Sep 17 00:00:00 2001 From: LarisaEgorova Date: Fri, 26 Dec 2025 14:13:00 +0000 Subject: [PATCH 06/14] clang3 --- tasks/egorova_l_gauss_filter_vert/common/include/common.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp index 61b63ed78c..1a548adac1 100644 --- a/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp +++ b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include From 79ac78fbedfa401cd648de8f9ff0a39666dd11ab Mon Sep 17 00:00:00 2001 From: LarisaEgorova Date: Fri, 26 Dec 2025 15:56:04 +0000 Subject: [PATCH 07/14] fix --- tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp | 1 - tasks/egorova_l_gauss_filter_vert/seq/src/ops_seq.cpp | 1 - 2 files changed, 2 deletions(-) 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 index e3d276791a..b1021f6201 100644 --- a/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp +++ b/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp @@ -21,7 +21,6 @@ class EgorovaLGaussFilterVertMPI : public BaseTask { bool PostProcessingImpl() override; private: - // ОБЯЗАТЕЛЬНО: Объявление метода, который вы реализуете в .cpp static void ApplyFilter(const std::vector &local_in, std::vector &local_out, int rows, int local_cols, int total_lc, int ch); }; 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 index ac46452ce1..af191b4e90 100644 --- a/tasks/egorova_l_gauss_filter_vert/seq/src/ops_seq.cpp +++ b/tasks/egorova_l_gauss_filter_vert/seq/src/ops_seq.cpp @@ -54,7 +54,6 @@ bool EgorovaLGaussFilterVertSEQ::RunImpl() { std::size_t k_idx = (static_cast(ky + 1) * 3) + static_cast(kx + 1); - // Использование указателя на данные массива убирает предупреждение о неконстантном индексе sum += (static_cast(in.data[in_idx]) * ker_ptr[k_idx]); } } From b9038dd2f29237f278b59e303f26ad26bb0f7ca7 Mon Sep 17 00:00:00 2001 From: LarisaEgorova Date: Sat, 27 Dec 2025 23:51:28 +0000 Subject: [PATCH 08/14] fix2 --- .../common/include/common.hpp | 2 +- .../mpi/include/ops_mpi.hpp | 12 +- .../mpi/src/ops_mpi.cpp | 179 ++++++++++-------- tasks/egorova_l_gauss_filter_vert/report.md | 156 +++++++-------- .../tests/functional/main.cpp | 147 +++++++------- .../tests/performance/main.cpp | 41 ++-- 6 files changed, 275 insertions(+), 262 deletions(-) diff --git a/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp index 1a548adac1..c6e864e67a 100644 --- a/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp +++ b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp @@ -21,4 +21,4 @@ using OutType = Image; using TestType = std::tuple; using BaseTask = ppc::task::Task; -} // namespace egorova_l_gauss_filter_vert +} // namespace egorova_l_gauss_filter_vert \ No newline at end of file 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 index b1021f6201..d6692730f3 100644 --- a/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp +++ b/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp @@ -1,8 +1,12 @@ #pragma once +#include + #include #include +#include + #include "egorova_l_gauss_filter_vert/common/include/common.hpp" #include "task/include/task.hpp" @@ -23,6 +27,12 @@ class EgorovaLGaussFilterVertMPI : public BaseTask { private: static void ApplyFilter(const std::vector &local_in, std::vector &local_out, int rows, int local_cols, int total_lc, int ch); + + static void FillLocalWithHalo(const std::vector &local_data, std::vector &local_with_halo, int rows, + int local_cols, int total_lc, int left_h, int ch); + + static void ExchangeHalo(std::vector &local_with_halo, int target, int rows, int total_lc, int ch, + size_t send_col, size_t recv_col); }; -} // namespace egorova_l_gauss_filter_vert +} // namespace egorova_l_gauss_filter_vert \ No newline at end of file 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 index dd1b1ee4b1..8baba6bb14 100644 --- a/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp +++ b/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp @@ -1,28 +1,40 @@ #include "egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp" -#include - #include #include +#include #include #include -#include #include +#include + #include "egorova_l_gauss_filter_vert/common/include/common.hpp" namespace egorova_l_gauss_filter_vert { -EgorovaLGaussFilterVertMPI::EgorovaLGaussFilterVertMPI(const InType &in) : BaseTask() { +namespace { +void CalculateCountsAndDispls(int size, int qq, int rem, int rows, int ch, std::vector& counts, + std::vector& displs) { + int offset = 0; + for (int i = 0; i < size; ++i) { + int cur_cols = qq + (i < rem ? 1 : 0); + counts.at(static_cast(i)) = cur_cols * rows * ch; + displs.at(static_cast(i)) = offset; + offset += counts.at(static_cast(i)); + } +} +} // namespace + +EgorovaLGaussFilterVertMPI::EgorovaLGaussFilterVertMPI(const InType& in) : BaseTask() { SetTypeOfTask(GetStaticTypeOfTask()); GetInput() = in; } bool EgorovaLGaussFilterVertMPI::ValidationImpl() { - const auto &in = GetInput(); + const auto& in = GetInput(); int rank = 0; MPI_Comm_rank(MPI_COMM_WORLD, &rank); - std::array params{}; if (rank == 0) { params[0] = in.rows; @@ -30,19 +42,14 @@ bool EgorovaLGaussFilterVertMPI::ValidationImpl() { params[2] = in.channels; } MPI_Bcast(params.data(), 3, MPI_INT, 0, MPI_COMM_WORLD); - return (params[0] > 0 && params[1] > 0 && params[2] > 0); } -bool EgorovaLGaussFilterVertMPI::PreProcessingImpl() { - return true; -} +bool EgorovaLGaussFilterVertMPI::PreProcessingImpl() { return true; } -void EgorovaLGaussFilterVertMPI::ApplyFilter(const std::vector &local_in, std::vector &local_out, +void EgorovaLGaussFilterVertMPI::ApplyFilter(const std::vector& local_in, std::vector& local_out, int rows, int local_cols, int total_lc, int ch) { const std::array ker = {0.0625F, 0.125F, 0.0625F, 0.125F, 0.25F, 0.125F, 0.0625F, 0.125F, 0.0625F}; - const float *ker_ptr = ker.data(); - for (int yy = 0; yy < rows; ++yy) { for (int xx = 0; xx < local_cols; ++xx) { for (int cc = 0; cc < ch; ++cc) { @@ -50,107 +57,115 @@ void EgorovaLGaussFilterVertMPI::ApplyFilter(const std::vector &local_i for (int ky = -1; ky <= 1; ++ky) { for (int kx = -1; kx <= 1; ++kx) { int py = std::clamp(yy + ky, 0, rows - 1); - int px = xx + kx + 1; - - std::size_t in_idx = - ((((static_cast(py) * static_cast(total_lc)) + static_cast(px)) * - static_cast(ch)) + - static_cast(cc)); - - std::size_t k_idx = (static_cast(ky + 1) * 3) + static_cast(kx + 1); - sum += (static_cast(local_in[in_idx]) * ker_ptr[k_idx]); + int px = std::clamp(xx + kx + 1, 0, total_lc - 1); + size_t in_idx = ((static_cast(py) * total_lc + px) * ch) + cc; + size_t k_idx = (static_cast(ky + 1) * 3) + (kx + 1); + sum += (static_cast(local_in[in_idx]) * ker.at(k_idx)); } } - std::size_t out_idx = - ((((static_cast(yy) * static_cast(local_cols)) + static_cast(xx)) * - static_cast(ch)) + - static_cast(cc)); - - local_out[out_idx] = static_cast(sum); + size_t out_idx = ((static_cast(yy) * local_cols + xx) * ch) + cc; + local_out[out_idx] = static_cast(std::clamp(std::round(sum), 0.0F, 255.0F)); } } } } +void EgorovaLGaussFilterVertMPI::FillLocalWithHalo(const std::vector& local_data, + std::vector& local_with_halo, + int rows, int local_cols, int total_lc, int left_h, int ch) { + for (int row = 0; row < rows; ++row) { + size_t src_off = static_cast(row) * local_cols * ch; + size_t dst_off = (static_cast(row) * total_lc + left_h) * ch; + std::copy(local_data.begin() + static_cast(src_off), + local_data.begin() + static_cast(src_off + (static_cast(local_cols) * ch)), + local_with_halo.begin() + static_cast(dst_off)); + } +} + +void EgorovaLGaussFilterVertMPI::ExchangeHalo(std::vector& local_with_halo, int target, int rows, + int total_lc, int ch, size_t send_col, size_t recv_col) { + if (target == MPI_PROC_NULL) { + return; + } + std::vector s_buf(static_cast(rows) * ch); + std::vector r_buf(static_cast(rows) * ch); + for (int i = 0; i < rows; ++i) { + size_t s_idx = (static_cast(i) * total_lc + send_col) * ch; + std::copy(local_with_halo.begin() + static_cast(s_idx), + local_with_halo.begin() + static_cast(s_idx + ch), + s_buf.begin() + static_cast(static_cast(i) * ch)); + } + MPI_Sendrecv(s_buf.data(), static_cast(s_buf.size()), MPI_BYTE, target, 0, r_buf.data(), + static_cast(r_buf.size()), MPI_BYTE, target, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + for (int i = 0; i < rows; ++i) { + size_t d_idx = (static_cast(i) * total_lc + recv_col) * ch; + std::copy(r_buf.begin() + static_cast(static_cast(i) * ch), + r_buf.begin() + static_cast(static_cast(i + 1) * ch), + local_with_halo.begin() + static_cast(d_idx)); + } +} + bool EgorovaLGaussFilterVertMPI::RunImpl() { int rank = 0; int size = 0; MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); - const auto &full_img = GetInput(); int rr = 0; int cc = 0; int ch = 0; - if (rank == 0) { - rr = full_img.rows; - cc = full_img.cols; - ch = full_img.channels; + rr = GetInput().rows; + cc = GetInput().cols; + ch = GetInput().channels; } - MPI_Bcast(&rr, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&cc, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&ch, 1, MPI_INT, 0, MPI_COMM_WORLD); - int qq = (cc / size); - int rem = (cc % size); - int local_cols = (qq + (rank < rem ? 1 : 0)); - int offset = ((rank * qq) + std::min(rank, rem)); + int qq = cc / size; + int rem = cc % size; + int local_cols = qq + (rank < rem ? 1 : 0); - int left_h = ((rank > 0) ? 1 : 0); - int right_h = ((rank < size - 1) ? 1 : 0); - int total_lc = (local_cols + left_h + right_h); + std::vector counts(static_cast(size)); + std::vector displs(static_cast(size)); + CalculateCountsAndDispls(size, qq, rem, rr, ch, counts, displs); - std::vector local_in(static_cast(rr) * total_lc * ch); - std::vector local_out(static_cast(rr) * local_cols * ch); + std::vector local_data(static_cast(rr) * local_cols * ch); + void* send_ptr = (rank == 0) ? GetInput().data.data() : nullptr; + MPI_Scatterv(send_ptr, counts.data(), displs.data(), MPI_BYTE, local_data.data(), + counts[static_cast(rank)], MPI_BYTE, 0, MPI_COMM_WORLD); - std::vector shared_data; - if (rank == 0) { - shared_data = full_img.data; - } else { - shared_data.resize(static_cast(rr) * cc * ch); - } - MPI_Bcast(shared_data.data(), static_cast(shared_data.size()), MPI_BYTE, 0, MPI_COMM_WORLD); + int left_rank = (rank > 0) ? rank - 1 : MPI_PROC_NULL; + int right_rank = (rank < size - 1) ? rank + 1 : MPI_PROC_NULL; + int left_h = (left_rank != MPI_PROC_NULL) ? 1 : 0; + int right_h = (right_rank != MPI_PROC_NULL) ? 1 : 0; + int total_lc = local_cols + left_h + right_h; - for (int ii = 0; ii < rr; ++ii) { - std::size_t src_start = (static_cast(ii) * cc + (offset - left_h)) * ch; - std::size_t dest_start = (static_cast(ii) * total_lc) * ch; - std::size_t count = static_cast(total_lc) * ch; + std::vector local_with_halo(static_cast(rr) * total_lc * ch, 0); + FillLocalWithHalo(local_data, local_with_halo, rr, local_cols, total_lc, left_h, ch); + + ExchangeHalo(local_with_halo, right_rank, rr, total_lc, ch, static_cast(left_h + local_cols - 1), + static_cast(left_h + local_cols)); + ExchangeHalo(local_with_halo, left_rank, rr, total_lc, ch, static_cast(left_h), 0); - std::copy(std::next(shared_data.begin(), static_cast(src_start)), - std::next(shared_data.begin(), static_cast(src_start + count)), - std::next(local_in.begin(), static_cast(dest_start))); - } - - ApplyFilter(local_in, local_out, rr, local_cols, total_lc, ch); + std::vector local_out(static_cast(rr) * local_cols * ch); + ApplyFilter(local_with_halo, local_out, rr, local_cols, total_lc, ch); - std::vector counts(size); - std::vector displs(size); - for (int ii = 0; ii < size; ++ii) { - counts[ii] = (qq + (ii < rem ? 1 : 0)) * ch; - displs[ii] = (ii == 0) ? 0 : displs[ii - 1] + counts[ii - 1]; + if (rank == 0) { + GetOutput().rows = rr; + GetOutput().cols = cc; + GetOutput().channels = ch; + GetOutput().data.resize(static_cast(rr) * cc * ch); } - auto &output = GetOutput(); - output.rows = rr; - output.cols = cc; - output.channels = ch; - output.data.resize(static_cast(rr) * cc * ch); - - for (int ii = 0; ii < rr; ++ii) { - std::size_t send_offset = (static_cast(ii) * local_cols * ch); - std::size_t recv_offset = (static_cast(ii) * cc * ch); - - MPI_Allgatherv(local_out.data() + send_offset, counts[rank], MPI_BYTE, output.data.data() + recv_offset, - counts.data(), displs.data(), MPI_BYTE, MPI_COMM_WORLD); - } + void* recv_ptr = (rank == 0) ? GetOutput().data.data() : nullptr; + MPI_Gatherv(local_out.data(), static_cast(local_out.size()), MPI_BYTE, + recv_ptr, counts.data(), displs.data(), MPI_BYTE, 0, MPI_COMM_WORLD); return true; } -bool EgorovaLGaussFilterVertMPI::PostProcessingImpl() { - return true; -} +bool EgorovaLGaussFilterVertMPI::PostProcessingImpl() { return true; } -} // namespace egorova_l_gauss_filter_vert +} // namespace egorova_l_gauss_filter_vert \ No newline at end of file diff --git a/tasks/egorova_l_gauss_filter_vert/report.md b/tasks/egorova_l_gauss_filter_vert/report.md index 445d85c791..4efd9ca176 100644 --- a/tasks/egorova_l_gauss_filter_vert/report.md +++ b/tasks/egorova_l_gauss_filter_vert/report.md @@ -5,66 +5,62 @@ - **Вариант:** 27 ## 1. Введение -Фильтрация изображений является одной из ключевых задач компьютерного зрения и цифровой обработки сигналов. Фильтр Гаусса применяется для снижения уровня цветового шума и сглаживания деталей путем низкочастотной фильтрации. В условиях работы с изображениями высокого разрешения последовательная обработка становится узким местом, что обуславливает необходимость использования технологий параллельного программирования, таких как MPI, для распределения вычислительной нагрузки между ядрами процессора. +Фильтрация изображений является одной из ключевых задач компьютерного зрения. Фильтр Гаусса применяется для снижения уровня цветового шума и сглаживания деталей. В условиях работы с изображениями высокого разрешения последовательная обработка становится узким местом, что обуславливает необходимость использования технологий параллельного программирования, таких как MPI. ## 2. Постановка задачи Необходимо реализовать параллельный алгоритм фильтрации изображения с использованием ядра Гаусса размером $3 \times 3$. -- **Вход:** Структура данных, содержащая количество строк, столбцов, каналов и одномерный вектор пикселей `uint8_t`. +- **Вход:** Структура данных со строками, столбцами, каналами и вектором пикселей `uint8_t`. - **Выход:** Преобразованный вектор пикселей того же размера. -- **Особенности:** Реализация должна корректно обрабатывать границы изображения (используя зажатие координат `clamp`) и обеспечивать синхронизацию "призрачных зон" (halo-зоны) для непрерывности фильтрации на стыках данных разных процессов. +- **Особенности:** Реализация должна корректно обрабатывать границы изображения (стратегия `clamp`) и обеспечивать синхронизацию "призрачных зон" (halo-зоны) между процессами. ## 3. Последовательный алгоритм Алгоритм реализует математическую операцию свертки изображения с дискретным аналогом функции Гаусса. -1. Для каждого целевого пикселя $(x, y)$ анализируется окрестность $3 \times 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. Для пикселей на краях изображения применяется стратегия **Clamping**: если индекс выходит за пределы, используется значение ближайшего существующего пикселя, что предотвращает появление артефактов на границах. +3. На краях изображения используется значение ближайшего существующего пикселя. ## 4. Схема распараллеливания (MPI) -Для минимизации межпроцессных взаимодействий выбрана одномерная вертикальная декомпозиция (разделение изображения на полосы по столбцам). +Для минимизации взаимодействий выбрана одномерная вертикальная декомпозиция (разделение на полосы по столбцам). -1. **Распределение нагрузки:** Изображение делится на $N$ вертикальных полос, где $N$ — число процессов. Каждый процесс отвечает за вычисление своей части столбцов. -2. **Организация Halo-зоны:** Так как фильтр имеет радиус 1, каждому процессу для расчета крайних пикселей своей полосы требуется один соседний столбец от предыдущего и последующего процессов. -3. **Коллективные операции:** - `MPI_Bcast` используется для передачи метаданных и исходного массива данных. - - `MPI_Allgatherv` выполняет сборку обработанных полос обратно в единое изображение на всех узлах. +1. **Распределение нагрузки:** Изображение делится на $N$ вертикальных полос. Каждый процесс вычисляет свою часть столбцов. +2. **Halo-зоны:** Каждому процессу требуется один соседний столбец от предыдущего и последующего процессов для корректного расчета границ своей полосы. +3. **Коммуникации:** + - `MPI_Bcast` для передачи метаданных. + - `MPI_Scatterv` для раздачи полос изображения процессам. + - `MPI_Sendrecv` для обмена граничными столбцами. + - `MPI_Gatherv` для финальной сборки результата на мастере. -## 5. Детали реализации -- **Управление памятью:** Использование `std::vector` и `std::array` обеспечивает безопасность работы с памятью. -- **Оптимизация:** Прямой доступ к коэффициентам ядра через метод `.data()` минимизирует накладные расходы внутри вложенных циклов свертки. Промежуточные вычисления ведутся в типе `float` для сохранения точности. -- **Статический анализ:** Код соответствует стандартам `cppcoreguidelines`, в частности, заменены все C-style массивы и исправлены потенциальные ошибки приведения типов (`narrowing conversion`). +## 5. Эксперименты + +### 5.1 Среда проведения тестов -## 6. Настройка экспериментов - **Процессор:** 13th Gen Intel(R) Core(TM) i5-13420H (2.10 GHz, 8 ядер / 12 потоков) - **Оперативная память:** 16,0 ГБ -- **ОС:** Windows 10 (WSL2 / Docker Desktop) +- **ОС:** Windows 11 (WSL2 / Docker Desktop) - **Инструментарий:** Clang-21, MPI, CMake, Release build. -## 7. Результаты и обсуждение - -### 7.1 Корректность -Корректность алгоритма верифицирована с помощью модульных тестов: -- Сравнение с последовательной версией на различных форматах данных. -- Проверка многоканальных (RGB) и полутоновых изображений. -- Валидация работы на прямоугольных (неквадратных) матрицах. +### 5.2 Производительность +Замеры проводились в режиме `task_run`. Ускорение рассчитано относительно последовательной реализации (SEQ). -### 7.2 Производительность -Результаты замеров времени выполнения (режим `task_run`) для изображения **2000x2000**: +**Таблица 1. Результаты измерений производительности** -| Режим | Кол-во процессов | Время, с | Ускорение | Эффективность | -|-------|:----------------:|:--------:|:---------:|:-------------:| -| seq | 1 | 0.03016 | 1.00 | N/A | -| mpi | 1 | 0.03017 | 1.00 | 100.0% | -| mpi | 2 | 0.01386 | 2.17 | 108.5% | -| mpi | 4 | 0.01189 | 2.54 | 63.5% | +| Реализация | Процессы ($P$) | Время $T$ (сек) | Ускорение $S$ | Эффективность $E$ | +| :--------- | :------------: | :-------------: | :-----------: | :---------------: | +| **SEQ** | 1 | 0.06014 | 1.00 | 100% | +| **MPI** | 2 | 0.03325 | 1.81 | 90% | +| **MPI** | 3 | 0.02509 | 2.40 | 80% | +| **MPI** | 4 | 0.02552 | 2.36 | 59% | -**Анализ результатов:** -- **Суперлинейное ускорение (2 процесса):** Ускорение 2.17 (эффективность > 100%) связано с тем, что при разделении данных на части они начинают лучше утилизировать локальную кэш-память процессора ($L2/L3$), что сокращает задержки при доступе к памяти. -- **Масштабируемость (4 процесса):** Время выполнения достигает минимума (0.01189 с), однако эффективность падает до 63.5%. Это обусловлено тем, что на данном объеме данных затраты на коллективные операции обмена (`MPI_Allgatherv`) становятся сопоставимы со временем самих вычислений. +### 5.3 Анализ результатов +- **Ускорение:** Максимальное ускорение (**2.40×**) достигнуто на **3 процессах**. +- **Эффективность:** Высокая эффективность на 2 и 3 процессах (80-90%) подтверждает, что вычислительная нагрузка значительно превосходит затраты на пересылку halo-зон. +- **Масштабируемость:** На 4 процессах время выполнения практически не изменилось относительно 3 процессов (0.0255с против 0.0251с). Это указывает на то, что на данном объеме данных накладные расходы на коллективные операции MPI и конкуренция за общую шину памяти начинают ограничивать дальнейший рост скорости. -## 8. Заключение -В ходе выполнения работы была реализована параллельная версия вертикального фильтра Гаусса. Использование MPI позволило сократить время обработки в 2.54 раза на четырех процессах. Дальнейшее повышение эффективности масштабирования возможно при обработке изображений сверхвысокого разрешения (4K и выше), где вычислительная нагрузка будет преобладать над коммуникационной. +## 6. Заключение +Разработанный параллельный алгоритм вертикального фильтра Гаусса демонстрирует стабильное ускорение. Для текущей задачи оптимальным является использование 3 процессов, так как это обеспечивает лучший баланс между временем вычислений и коммуникационными издержками. Применение halo-зон позволило избежать артефактов на стыках данных. -## 9. Источники +## 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/) @@ -79,73 +75,57 @@ bool EgorovaLGaussFilterVertMPI::RunImpl() { MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); - const auto &full_img = GetInput(); int rr = 0; int cc = 0; int ch = 0; - if (rank == 0) { - rr = full_img.rows; - cc = full_img.cols; - ch = full_img.channels; + rr = GetInput().rows; + cc = GetInput().cols; + ch = GetInput().channels; } - MPI_Bcast(&rr, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&cc, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&ch, 1, MPI_INT, 0, MPI_COMM_WORLD); - int qq = (cc / size); - int rem = (cc % size); - int local_cols = (qq + (rank < rem ? 1 : 0)); - int offset = ((rank * qq) + std::min(rank, rem)); - - int left_h = ((rank > 0) ? 1 : 0); - int right_h = ((rank < size - 1) ? 1 : 0); - int total_lc = (local_cols + left_h + right_h); + int qq = cc / size; + int rem = cc % size; + int local_cols = qq + (rank < rem ? 1 : 0); - std::vector local_in(static_cast(rr) * total_lc * ch); - std::vector local_out(static_cast(rr) * local_cols * ch); + std::vector counts(static_cast(size)); + std::vector displs(static_cast(size)); + CalculateCountsAndDispls(size, qq, rem, rr, ch, counts, displs); - std::vector shared_data; - if (rank == 0) { - shared_data = full_img.data; - } else { - shared_data.resize(static_cast(rr) * cc * ch); - } - MPI_Bcast(shared_data.data(), static_cast(shared_data.size()), MPI_BYTE, 0, MPI_COMM_WORLD); + std::vector local_data(static_cast(rr) * local_cols * ch); + void* send_ptr = (rank == 0) ? GetInput().data.data() : nullptr; + MPI_Scatterv(send_ptr, counts.data(), displs.data(), MPI_BYTE, local_data.data(), + counts[static_cast(rank)], MPI_BYTE, 0, MPI_COMM_WORLD); - for (int ii = 0; ii < rr; ++ii) { - std::size_t src_start = (static_cast(ii) * cc + (offset - left_h)) * ch; - std::size_t dest_start = (static_cast(ii) * total_lc) * ch; - std::size_t count = static_cast(total_lc) * ch; + int left_rank = (rank > 0) ? rank - 1 : MPI_PROC_NULL; + int right_rank = (rank < size - 1) ? rank + 1 : MPI_PROC_NULL; + int left_h = (left_rank != MPI_PROC_NULL) ? 1 : 0; + int right_h = (right_rank != MPI_PROC_NULL) ? 1 : 0; + int total_lc = local_cols + left_h + right_h; - std::copy(std::next(shared_data.begin(), static_cast(src_start)), - std::next(shared_data.begin(), static_cast(src_start + count)), - std::next(local_in.begin(), static_cast(dest_start))); - } + std::vector local_with_halo(static_cast(rr) * total_lc * ch, 0); + FillLocalWithHalo(local_data, local_with_halo, rr, local_cols, total_lc, left_h, ch); + + ExchangeHalo(local_with_halo, right_rank, rr, total_lc, ch, static_cast(left_h + local_cols - 1), + static_cast(left_h + local_cols)); + ExchangeHalo(local_with_halo, left_rank, rr, total_lc, ch, static_cast(left_h), 0); - ApplyFilter(local_in, local_out, rr, local_cols, total_lc, ch); + std::vector local_out(static_cast(rr) * local_cols * ch); + ApplyFilter(local_with_halo, local_out, rr, local_cols, total_lc, ch); - std::vector counts(size); - std::vector displs(size); - for (int ii = 0; ii < size; ++ii) { - counts[ii] = (qq + (ii < rem ? 1 : 0)) * ch; - displs[ii] = (ii == 0) ? 0 : displs[ii - 1] + counts[ii - 1]; + if (rank == 0) { + GetOutput().rows = rr; + GetOutput().cols = cc; + GetOutput().channels = ch; + GetOutput().data.resize(static_cast(rr) * cc * ch); } - auto &output = GetOutput(); - output.rows = rr; - output.cols = cc; - output.channels = ch; - output.data.resize(static_cast(rr) * cc * ch); - - for (int ii = 0; ii < rr; ++ii) { - std::size_t send_offset = (static_cast(ii) * local_cols * ch); - std::size_t recv_offset = (static_cast(ii) * cc * ch); - - MPI_Allgatherv(local_out.data() + send_offset, counts[rank], MPI_BYTE, output.data.data() + recv_offset, - counts.data(), displs.data(), MPI_BYTE, MPI_COMM_WORLD); - } + void* recv_ptr = (rank == 0) ? GetOutput().data.data() : nullptr; + MPI_Gatherv(local_out.data(), static_cast(local_out.size()), MPI_BYTE, + recv_ptr, counts.data(), displs.data(), MPI_BYTE, 0, MPI_COMM_WORLD); return true; -} +} \ No newline at end of file diff --git a/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp b/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp index fd9cadee12..1e8c536710 100644 --- a/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp +++ b/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp @@ -1,72 +1,75 @@ -#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" -#include "util/include/util.hpp" - -namespace egorova_l_gauss_filter_vert { - -class EgorovaLGaussFilterRunFuncTests : public ppc::util::BaseRunFuncTests { - public: - static std::string PrintTestParam(const TestType &test_param) { - return std::to_string(std::get<0>(test_param)) + "_" + std::to_string(std::get<1>(test_param)) + "_" + - std::to_string(std::get<2>(test_param)) + "_" + std::get<3>(test_param); - } - - protected: - void SetUp() override { - auto test_params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); - - int rr = std::get<0>(test_params); - int cc = std::get<1>(test_params); - int ch = std::get<2>(test_params); - - input_data_.rows = rr; - input_data_.cols = cc; - input_data_.channels = ch; - input_data_.data.assign(static_cast(rr) * cc * ch, 128); - } - - bool CheckTestOutputData(OutType &output_data) final { - return output_data.rows == input_data_.rows && output_data.cols == input_data_.cols && - output_data.data.size() == input_data_.data.size(); - } - - InType GetTestInputData() final { - return input_data_; - } - - private: - InType input_data_; -}; - -namespace { - -TEST_P(EgorovaLGaussFilterRunFuncTests, GaussFilterVertical) { - ExecuteTest(GetParam()); -} - -const std::array kTestParam = { - 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_20x15"), std::make_tuple(5, 40, 1, "wide_image"), - std::make_tuple(40, 5, 1, "tall_image")}; - -const auto kTestTasksList = std::tuple_cat( - ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_egorova_l_gauss_filter_vert), - ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_egorova_l_gauss_filter_vert)); -const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); - -const auto kFuncTestName = EgorovaLGaussFilterRunFuncTests::PrintFuncTestName; - -INSTANTIATE_TEST_SUITE_P(EgorovaLGaussFilterFunc, EgorovaLGaussFilterRunFuncTests, kGtestValues, kFuncTestName); - -} // namespace -} // namespace egorova_l_gauss_filter_vert +#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" +#include "util/include/util.hpp" + +namespace egorova_l_gauss_filter_vert { + +class EgorovaLGaussFilterRunFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType& test_param) { + return std::to_string(std::get<0>(test_param)) + "_" + std::to_string(std::get<1>(test_param)) + "_" + + std::to_string(std::get<2>(test_param)) + "_" + std::get<3>(test_param); + } + + protected: + void SetUp() override { + auto test_params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + + int rr = std::get<0>(test_params); + int cc = std::get<1>(test_params); + int ch = std::get<2>(test_params); + + input_data_.rows = rr; + input_data_.cols = cc; + input_data_.channels = ch; + input_data_.data.assign(static_cast(rr) * static_cast(cc) * static_cast(ch), 128); + } + + bool CheckTestOutputData(OutType& output_data) final { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank != 0) { + return true; + } + + return output_data.rows == input_data_.rows && output_data.cols == input_data_.cols && + output_data.data.size() == input_data_.data.size(); + } + + InType GetTestInputData() final { return input_data_; } + + private: + InType input_data_; +}; + +namespace { +TEST_P(EgorovaLGaussFilterRunFuncTests, GaussFilterVertical) { ExecuteTest(GetParam()); } + +const std::array kTestParam = { + 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_20x15"), std::make_tuple(5, 40, 1, "wide_image"), + std::make_tuple(40, 5, 1, "tall_image")}; + +const auto kTestTasksList = + std::tuple_cat(ppc::util::AddFuncTask(kTestParam, + PPC_SETTINGS_egorova_l_gauss_filter_vert), + ppc::util::AddFuncTask(kTestParam, + PPC_SETTINGS_egorova_l_gauss_filter_vert)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); +const auto kFuncTestName = EgorovaLGaussFilterRunFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(EgorovaLGaussFilterFunc, EgorovaLGaussFilterRunFuncTests, kGtestValues, kFuncTestName); +} // namespace +} // namespace egorova_l_gauss_filter_vert \ No newline at end of file diff --git a/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp b/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp index 6a75832ed1..11ccc724c9 100644 --- a/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp +++ b/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -12,38 +13,42 @@ namespace egorova_l_gauss_filter_vert { class EgorovaLGaussFilterVertRunPerfTests : public ppc::util::BaseRunPerfTests { private: - const int kRows_ = 2000; - const int kCols_ = 2000; - const int kChannels_ = 1; + static constexpr int kRows = 2000; + static constexpr int kCols = 2000; + static constexpr int kChannels = 1; InType input_data_; public: void SetUp() override { - input_data_.rows = kRows_; - input_data_.cols = kCols_; - input_data_.channels = kChannels_; - input_data_.data.assign(static_cast(kRows_) * kCols_ * kChannels_, 128); - } - bool CheckTestOutputData(OutType &output_data) final { - return output_data.rows == kRows_ && output_data.cols == kCols_ && - output_data.data.size() == input_data_.data.size(); + input_data_.rows = kRows; + input_data_.cols = kCols; + input_data_.channels = kChannels; + input_data_.data.assign( + static_cast(kRows) * static_cast(kCols) * static_cast(kChannels), 128); } - InType GetTestInputData() final { - return input_data_; + bool CheckTestOutputData(OutType& output_data) final { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank != 0) { + return true; + } + + return output_data.rows == kRows && output_data.cols == kCols && + output_data.data.size() == + static_cast(kRows) * static_cast(kCols) * static_cast(kChannels); } + + InType GetTestInputData() final { return input_data_; } }; -TEST_P(EgorovaLGaussFilterVertRunPerfTests, EgorovaLGaussPerfModes) { - ExecuteTest(GetParam()); -} +TEST_P(EgorovaLGaussFilterVertRunPerfTests, EgorovaLGaussPerfModes) { ExecuteTest(GetParam()); } const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( PPC_SETTINGS_egorova_l_gauss_filter_vert); const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); - const auto kPerfTestName = EgorovaLGaussFilterVertRunPerfTests::CustomPerfTestName; INSTANTIATE_TEST_SUITE_P(EgorovaLGaussPerf, EgorovaLGaussFilterVertRunPerfTests, kGtestValues, kPerfTestName); -} // namespace egorova_l_gauss_filter_vert +} // namespace egorova_l_gauss_filter_vert \ No newline at end of file From feb71a48d826f34457e757b6fbcbd3674469170a Mon Sep 17 00:00:00 2001 From: LarisaEgorova Date: Sun, 28 Dec 2025 00:11:26 +0000 Subject: [PATCH 09/14] fix3 --- .../common/include/common.hpp | 2 +- .../mpi/include/ops_mpi.hpp | 5 +- .../mpi/src/ops_mpi.cpp | 50 +++--- .../tests/functional/main.cpp | 152 +++++++++--------- .../tests/performance/main.cpp | 12 +- 5 files changed, 115 insertions(+), 106 deletions(-) diff --git a/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp index c6e864e67a..1a548adac1 100644 --- a/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp +++ b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp @@ -21,4 +21,4 @@ using OutType = Image; using TestType = std::tuple; using BaseTask = ppc::task::Task; -} // namespace egorova_l_gauss_filter_vert \ No newline at end of file +} // namespace egorova_l_gauss_filter_vert 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 index d6692730f3..abf1c38392 100644 --- a/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp +++ b/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp @@ -1,12 +1,11 @@ #pragma once #include +#include #include #include -#include - #include "egorova_l_gauss_filter_vert/common/include/common.hpp" #include "task/include/task.hpp" @@ -35,4 +34,4 @@ class EgorovaLGaussFilterVertMPI : public BaseTask { size_t send_col, size_t recv_col); }; -} // namespace egorova_l_gauss_filter_vert \ No newline at end of file +} // 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 index 8baba6bb14..e949c8fe35 100644 --- a/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp +++ b/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp @@ -1,5 +1,7 @@ #include "egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp" +#include + #include #include #include @@ -7,15 +9,13 @@ #include #include -#include - #include "egorova_l_gauss_filter_vert/common/include/common.hpp" namespace egorova_l_gauss_filter_vert { namespace { -void CalculateCountsAndDispls(int size, int qq, int rem, int rows, int ch, std::vector& counts, - std::vector& displs) { +void CalculateCountsAndDispls(int size, int qq, int rem, int rows, int ch, std::vector &counts, + std::vector &displs) { int offset = 0; for (int i = 0; i < size; ++i) { int cur_cols = qq + (i < rem ? 1 : 0); @@ -26,13 +26,13 @@ void CalculateCountsAndDispls(int size, int qq, int rem, int rows, int ch, std:: } } // namespace -EgorovaLGaussFilterVertMPI::EgorovaLGaussFilterVertMPI(const InType& in) : BaseTask() { +EgorovaLGaussFilterVertMPI::EgorovaLGaussFilterVertMPI(const InType &in) : BaseTask() { SetTypeOfTask(GetStaticTypeOfTask()); GetInput() = in; } bool EgorovaLGaussFilterVertMPI::ValidationImpl() { - const auto& in = GetInput(); + const auto &in = GetInput(); int rank = 0; MPI_Comm_rank(MPI_COMM_WORLD, &rank); std::array params{}; @@ -45,9 +45,11 @@ bool EgorovaLGaussFilterVertMPI::ValidationImpl() { return (params[0] > 0 && params[1] > 0 && params[2] > 0); } -bool EgorovaLGaussFilterVertMPI::PreProcessingImpl() { return true; } +bool EgorovaLGaussFilterVertMPI::PreProcessingImpl() { + return true; +} -void EgorovaLGaussFilterVertMPI::ApplyFilter(const std::vector& local_in, std::vector& local_out, +void EgorovaLGaussFilterVertMPI::ApplyFilter(const std::vector &local_in, std::vector &local_out, int rows, int local_cols, int total_lc, int ch) { const std::array ker = {0.0625F, 0.125F, 0.0625F, 0.125F, 0.25F, 0.125F, 0.0625F, 0.125F, 0.0625F}; for (int yy = 0; yy < rows; ++yy) { @@ -70,9 +72,9 @@ void EgorovaLGaussFilterVertMPI::ApplyFilter(const std::vector& local_i } } -void EgorovaLGaussFilterVertMPI::FillLocalWithHalo(const std::vector& local_data, - std::vector& local_with_halo, - int rows, int local_cols, int total_lc, int left_h, int ch) { +void EgorovaLGaussFilterVertMPI::FillLocalWithHalo(const std::vector &local_data, + std::vector &local_with_halo, int rows, int local_cols, + int total_lc, int left_h, int ch) { for (int row = 0; row < rows; ++row) { size_t src_off = static_cast(row) * local_cols * ch; size_t dst_off = (static_cast(row) * total_lc + left_h) * ch; @@ -82,8 +84,8 @@ void EgorovaLGaussFilterVertMPI::FillLocalWithHalo(const std::vector& l } } -void EgorovaLGaussFilterVertMPI::ExchangeHalo(std::vector& local_with_halo, int target, int rows, - int total_lc, int ch, size_t send_col, size_t recv_col) { +void EgorovaLGaussFilterVertMPI::ExchangeHalo(std::vector &local_with_halo, int target, int rows, int total_lc, + int ch, size_t send_col, size_t recv_col) { if (target == MPI_PROC_NULL) { return; } @@ -132,9 +134,9 @@ bool EgorovaLGaussFilterVertMPI::RunImpl() { CalculateCountsAndDispls(size, qq, rem, rr, ch, counts, displs); std::vector local_data(static_cast(rr) * local_cols * ch); - void* send_ptr = (rank == 0) ? GetInput().data.data() : nullptr; - MPI_Scatterv(send_ptr, counts.data(), displs.data(), MPI_BYTE, local_data.data(), - counts[static_cast(rank)], MPI_BYTE, 0, MPI_COMM_WORLD); + void *send_ptr = (rank == 0) ? GetInput().data.data() : nullptr; + MPI_Scatterv(send_ptr, counts.data(), displs.data(), MPI_BYTE, local_data.data(), counts[static_cast(rank)], + MPI_BYTE, 0, MPI_COMM_WORLD); int left_rank = (rank > 0) ? rank - 1 : MPI_PROC_NULL; int right_rank = (rank < size - 1) ? rank + 1 : MPI_PROC_NULL; @@ -144,8 +146,8 @@ bool EgorovaLGaussFilterVertMPI::RunImpl() { std::vector local_with_halo(static_cast(rr) * total_lc * ch, 0); FillLocalWithHalo(local_data, local_with_halo, rr, local_cols, total_lc, left_h, ch); - - ExchangeHalo(local_with_halo, right_rank, rr, total_lc, ch, static_cast(left_h + local_cols - 1), + + ExchangeHalo(local_with_halo, right_rank, rr, total_lc, ch, static_cast(left_h + local_cols - 1), static_cast(left_h + local_cols)); ExchangeHalo(local_with_halo, left_rank, rr, total_lc, ch, static_cast(left_h), 0); @@ -159,13 +161,15 @@ bool EgorovaLGaussFilterVertMPI::RunImpl() { GetOutput().data.resize(static_cast(rr) * cc * ch); } - void* recv_ptr = (rank == 0) ? GetOutput().data.data() : nullptr; - MPI_Gatherv(local_out.data(), static_cast(local_out.size()), MPI_BYTE, - recv_ptr, counts.data(), displs.data(), MPI_BYTE, 0, MPI_COMM_WORLD); + void *recv_ptr = (rank == 0) ? GetOutput().data.data() : nullptr; + MPI_Gatherv(local_out.data(), static_cast(local_out.size()), MPI_BYTE, recv_ptr, counts.data(), displs.data(), + MPI_BYTE, 0, MPI_COMM_WORLD); return true; } -bool EgorovaLGaussFilterVertMPI::PostProcessingImpl() { return true; } +bool EgorovaLGaussFilterVertMPI::PostProcessingImpl() { + return true; +} -} // namespace egorova_l_gauss_filter_vert \ No newline at end of file +} // namespace egorova_l_gauss_filter_vert diff --git a/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp b/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp index 1e8c536710..97d12942b5 100644 --- a/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp +++ b/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp @@ -1,75 +1,77 @@ -#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" -#include "util/include/util.hpp" - -namespace egorova_l_gauss_filter_vert { - -class EgorovaLGaussFilterRunFuncTests : public ppc::util::BaseRunFuncTests { - public: - static std::string PrintTestParam(const TestType& test_param) { - return std::to_string(std::get<0>(test_param)) + "_" + std::to_string(std::get<1>(test_param)) + "_" + - std::to_string(std::get<2>(test_param)) + "_" + std::get<3>(test_param); - } - - protected: - void SetUp() override { - auto test_params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); - - int rr = std::get<0>(test_params); - int cc = std::get<1>(test_params); - int ch = std::get<2>(test_params); - - input_data_.rows = rr; - input_data_.cols = cc; - input_data_.channels = ch; - input_data_.data.assign(static_cast(rr) * static_cast(cc) * static_cast(ch), 128); - } - - bool CheckTestOutputData(OutType& output_data) final { - int rank = 0; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - if (rank != 0) { - return true; - } - - return output_data.rows == input_data_.rows && output_data.cols == input_data_.cols && - output_data.data.size() == input_data_.data.size(); - } - - InType GetTestInputData() final { return input_data_; } - - private: - InType input_data_; -}; - -namespace { -TEST_P(EgorovaLGaussFilterRunFuncTests, GaussFilterVertical) { ExecuteTest(GetParam()); } - -const std::array kTestParam = { - 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_20x15"), std::make_tuple(5, 40, 1, "wide_image"), - std::make_tuple(40, 5, 1, "tall_image")}; - -const auto kTestTasksList = - std::tuple_cat(ppc::util::AddFuncTask(kTestParam, - PPC_SETTINGS_egorova_l_gauss_filter_vert), - ppc::util::AddFuncTask(kTestParam, - PPC_SETTINGS_egorova_l_gauss_filter_vert)); - -const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); -const auto kFuncTestName = EgorovaLGaussFilterRunFuncTests::PrintFuncTestName; - -INSTANTIATE_TEST_SUITE_P(EgorovaLGaussFilterFunc, EgorovaLGaussFilterRunFuncTests, kGtestValues, kFuncTestName); -} // namespace -} // namespace egorova_l_gauss_filter_vert \ No newline at end of file +#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" +#include "util/include/util.hpp" + +namespace egorova_l_gauss_filter_vert { + +class EgorovaLGaussFilterRunFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::to_string(std::get<0>(test_param)) + "_" + std::to_string(std::get<1>(test_param)) + "_" + + std::to_string(std::get<2>(test_param)) + "_" + std::get<3>(test_param); + } + + protected: + void SetUp() override { + auto test_params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + + int rr = std::get<0>(test_params); + int cc = std::get<1>(test_params); + int ch = std::get<2>(test_params); + + input_data_.rows = rr; + input_data_.cols = cc; + input_data_.channels = ch; + input_data_.data.assign(static_cast(rr) * static_cast(cc) * static_cast(ch), 128); + } + + bool CheckTestOutputData(OutType &output_data) final { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank != 0) { + return true; + } + + return output_data.rows == input_data_.rows && output_data.cols == input_data_.cols && + output_data.data.size() == input_data_.data.size(); + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +namespace { +TEST_P(EgorovaLGaussFilterRunFuncTests, GaussFilterVertical) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + 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_20x15"), std::make_tuple(5, 40, 1, "wide_image"), + std::make_tuple(40, 5, 1, "tall_image")}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_egorova_l_gauss_filter_vert), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_egorova_l_gauss_filter_vert)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); +const auto kFuncTestName = EgorovaLGaussFilterRunFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(EgorovaLGaussFilterFunc, EgorovaLGaussFilterRunFuncTests, kGtestValues, kFuncTestName); +} // namespace +} // 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 index 11ccc724c9..9b2ed8b9e4 100644 --- a/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp +++ b/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp @@ -27,7 +27,7 @@ class EgorovaLGaussFilterVertRunPerfTests : public ppc::util::BaseRunPerfTests(kRows) * static_cast(kCols) * static_cast(kChannels), 128); } - bool CheckTestOutputData(OutType& output_data) final { + bool CheckTestOutputData(OutType &output_data) final { int rank = 0; MPI_Comm_rank(MPI_COMM_WORLD, &rank); if (rank != 0) { @@ -39,10 +39,14 @@ class EgorovaLGaussFilterVertRunPerfTests : public ppc::util::BaseRunPerfTests(kRows) * static_cast(kCols) * static_cast(kChannels); } - InType GetTestInputData() final { return input_data_; } + InType GetTestInputData() final { + return input_data_; + } }; -TEST_P(EgorovaLGaussFilterVertRunPerfTests, EgorovaLGaussPerfModes) { ExecuteTest(GetParam()); } +TEST_P(EgorovaLGaussFilterVertRunPerfTests, EgorovaLGaussPerfModes) { + ExecuteTest(GetParam()); +} const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( PPC_SETTINGS_egorova_l_gauss_filter_vert); @@ -51,4 +55,4 @@ const auto kPerfTestName = EgorovaLGaussFilterVertRunPerfTests::CustomPerfTestNa INSTANTIATE_TEST_SUITE_P(EgorovaLGaussPerf, EgorovaLGaussFilterVertRunPerfTests, kGtestValues, kPerfTestName); -} // namespace egorova_l_gauss_filter_vert \ No newline at end of file +} // namespace egorova_l_gauss_filter_vert From 78b3549312b2686161c08f0a29d1d4ee284d236f Mon Sep 17 00:00:00 2001 From: LarisaEgorova Date: Thu, 1 Jan 2026 17:41:42 +0000 Subject: [PATCH 10/14] fix4 --- .../mpi/src/ops_mpi.cpp | 77 +++++++++---------- tasks/egorova_l_gauss_filter_vert/report.md | 53 +++++++------ 2 files changed, 66 insertions(+), 64 deletions(-) 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 index e949c8fe35..5ff16a3da8 100644 --- a/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp +++ b/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp @@ -24,6 +24,22 @@ void CalculateCountsAndDispls(int size, int qq, int rem, int rows, int ch, std:: offset += counts.at(static_cast(i)); } } + +uint8_t ApplyKernelToPixel(const std::vector &in, int yy, int xx, int cc, int rows, int total_lc, int ch, + int halo_offset) { + const std::array kernel = {0.0625F, 0.125F, 0.0625F, 0.125F, 0.25F, 0.125F, 0.0625F, 0.125F, 0.0625F}; + float sum = 0.0F; + for (int ky = -1; ky <= 1; ++ky) { + int py = std::clamp(yy + ky, 0, rows - 1); + for (int kx = -1; kx <= 1; ++kx) { + int px = std::clamp(xx + kx + halo_offset, 0, total_lc - 1); + size_t in_idx = ((static_cast(py) * total_lc + px) * ch) + cc; + size_t k_idx = (static_cast(ky + 1) * 3) + (kx + 1); + sum += (static_cast(in[in_idx]) * kernel.at(k_idx)); + } + } + return static_cast(std::clamp(std::round(sum), 0.0F, 255.0F)); +} } // namespace EgorovaLGaussFilterVertMPI::EgorovaLGaussFilterVertMPI(const InType &in) : BaseTask() { @@ -32,17 +48,7 @@ EgorovaLGaussFilterVertMPI::EgorovaLGaussFilterVertMPI(const InType &in) : BaseT } bool EgorovaLGaussFilterVertMPI::ValidationImpl() { - const auto &in = GetInput(); - int rank = 0; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - std::array params{}; - if (rank == 0) { - params[0] = in.rows; - params[1] = in.cols; - params[2] = in.channels; - } - MPI_Bcast(params.data(), 3, MPI_INT, 0, MPI_COMM_WORLD); - return (params[0] > 0 && params[1] > 0 && params[2] > 0); + return GetInput().rows > 0 && GetInput().cols > 0 && GetInput().channels > 0; } bool EgorovaLGaussFilterVertMPI::PreProcessingImpl() { @@ -51,22 +57,16 @@ bool EgorovaLGaussFilterVertMPI::PreProcessingImpl() { void EgorovaLGaussFilterVertMPI::ApplyFilter(const std::vector &local_in, std::vector &local_out, int rows, int local_cols, int total_lc, int ch) { - const std::array ker = {0.0625F, 0.125F, 0.0625F, 0.125F, 0.25F, 0.125F, 0.0625F, 0.125F, 0.0625F}; + int halo_offset = 0; + if (total_lc > local_cols) { + halo_offset = 1; + } + for (int yy = 0; yy < rows; ++yy) { for (int xx = 0; xx < local_cols; ++xx) { for (int cc = 0; cc < ch; ++cc) { - float sum = 0.0F; - for (int ky = -1; ky <= 1; ++ky) { - for (int kx = -1; kx <= 1; ++kx) { - int py = std::clamp(yy + ky, 0, rows - 1); - int px = std::clamp(xx + kx + 1, 0, total_lc - 1); - size_t in_idx = ((static_cast(py) * total_lc + px) * ch) + cc; - size_t k_idx = (static_cast(ky + 1) * 3) + (kx + 1); - sum += (static_cast(local_in[in_idx]) * ker.at(k_idx)); - } - } size_t out_idx = ((static_cast(yy) * local_cols + xx) * ch) + cc; - local_out[out_idx] = static_cast(std::clamp(std::round(sum), 0.0F, 255.0F)); + local_out[out_idx] = ApplyKernelToPixel(local_in, yy, xx, cc, rows, total_lc, ch, halo_offset); } } } @@ -89,16 +89,20 @@ void EgorovaLGaussFilterVertMPI::ExchangeHalo(std::vector &local_with_h if (target == MPI_PROC_NULL) { return; } + std::vector s_buf(static_cast(rows) * ch); std::vector r_buf(static_cast(rows) * ch); + for (int i = 0; i < rows; ++i) { size_t s_idx = (static_cast(i) * total_lc + send_col) * ch; std::copy(local_with_halo.begin() + static_cast(s_idx), local_with_halo.begin() + static_cast(s_idx + ch), s_buf.begin() + static_cast(static_cast(i) * ch)); } + MPI_Sendrecv(s_buf.data(), static_cast(s_buf.size()), MPI_BYTE, target, 0, r_buf.data(), static_cast(r_buf.size()), MPI_BYTE, target, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + for (int i = 0; i < rows; ++i) { size_t d_idx = (static_cast(i) * total_lc + recv_col) * ch; std::copy(r_buf.begin() + static_cast(static_cast(i) * ch), @@ -113,14 +117,10 @@ bool EgorovaLGaussFilterVertMPI::RunImpl() { MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); - int rr = 0; - int cc = 0; - int ch = 0; - if (rank == 0) { - rr = GetInput().rows; - cc = GetInput().cols; - ch = GetInput().channels; - } + int rr = GetInput().rows; + int cc = GetInput().cols; + int ch = GetInput().channels; + MPI_Bcast(&rr, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&cc, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&ch, 1, MPI_INT, 0, MPI_COMM_WORLD); @@ -134,9 +134,9 @@ bool EgorovaLGaussFilterVertMPI::RunImpl() { CalculateCountsAndDispls(size, qq, rem, rr, ch, counts, displs); std::vector local_data(static_cast(rr) * local_cols * ch); - void *send_ptr = (rank == 0) ? GetInput().data.data() : nullptr; - MPI_Scatterv(send_ptr, counts.data(), displs.data(), MPI_BYTE, local_data.data(), counts[static_cast(rank)], - MPI_BYTE, 0, MPI_COMM_WORLD); + + MPI_Scatterv(rank == 0 ? GetInput().data.data() : nullptr, counts.data(), displs.data(), MPI_BYTE, local_data.data(), + static_cast(local_data.size()), MPI_BYTE, 0, MPI_COMM_WORLD); int left_rank = (rank > 0) ? rank - 1 : MPI_PROC_NULL; int right_rank = (rank < size - 1) ? rank + 1 : MPI_PROC_NULL; @@ -144,7 +144,7 @@ bool EgorovaLGaussFilterVertMPI::RunImpl() { int right_h = (right_rank != MPI_PROC_NULL) ? 1 : 0; int total_lc = local_cols + left_h + right_h; - std::vector local_with_halo(static_cast(rr) * total_lc * ch, 0); + std::vector local_with_halo(static_cast(rr) * total_lc * ch); FillLocalWithHalo(local_data, local_with_halo, rr, local_cols, total_lc, left_h, ch); ExchangeHalo(local_with_halo, right_rank, rr, total_lc, ch, static_cast(left_h + local_cols - 1), @@ -161,9 +161,8 @@ bool EgorovaLGaussFilterVertMPI::RunImpl() { GetOutput().data.resize(static_cast(rr) * cc * ch); } - void *recv_ptr = (rank == 0) ? GetOutput().data.data() : nullptr; - MPI_Gatherv(local_out.data(), static_cast(local_out.size()), MPI_BYTE, recv_ptr, counts.data(), displs.data(), - MPI_BYTE, 0, MPI_COMM_WORLD); + MPI_Gatherv(local_out.data(), static_cast(local_out.size()), MPI_BYTE, + rank == 0 ? GetOutput().data.data() : nullptr, counts.data(), displs.data(), MPI_BYTE, 0, MPI_COMM_WORLD); return true; } @@ -172,4 +171,4 @@ bool EgorovaLGaussFilterVertMPI::PostProcessingImpl() { return true; } -} // namespace egorova_l_gauss_filter_vert +} // namespace egorova_l_gauss_filter_vert \ No newline at end of file diff --git a/tasks/egorova_l_gauss_filter_vert/report.md b/tasks/egorova_l_gauss_filter_vert/report.md index 4efd9ca176..889c171f17 100644 --- a/tasks/egorova_l_gauss_filter_vert/report.md +++ b/tasks/egorova_l_gauss_filter_vert/report.md @@ -41,24 +41,31 @@ - **Инструментарий:** Clang-21, MPI, CMake, Release build. ### 5.2 Производительность -Замеры проводились в режиме `task_run`. Ускорение рассчитано относительно последовательной реализации (SEQ). + +Замеры проводились в режиме `task_run`. Ускорение ($S$) рассчитано относительно последовательной реализации (SEQ), где $T_{SEQ} = 0.03340$ сек. + +$$S = \frac{T_{SEQ}}{T_P} \quad E = \frac{S}{P} \times 100\%$$ **Таблица 1. Результаты измерений производительности** | Реализация | Процессы ($P$) | Время $T$ (сек) | Ускорение $S$ | Эффективность $E$ | | :--------- | :------------: | :-------------: | :-----------: | :---------------: | -| **SEQ** | 1 | 0.06014 | 1.00 | 100% | -| **MPI** | 2 | 0.03325 | 1.81 | 90% | -| **MPI** | 3 | 0.02509 | 2.40 | 80% | -| **MPI** | 4 | 0.02552 | 2.36 | 59% | +| **SEQ** | 1 | 0.03340 | 1.00 | 100% | +| **MPI** | 2 | 0.02341 | 1.43 | 71% | +| **MPI** | 3 | 0.01699 | 1.97 | 66% | +| **MPI** | 4 | 0.01979 | 1.69 | 42% | ### 5.3 Анализ результатов -- **Ускорение:** Максимальное ускорение (**2.40×**) достигнуто на **3 процессах**. -- **Эффективность:** Высокая эффективность на 2 и 3 процессах (80-90%) подтверждает, что вычислительная нагрузка значительно превосходит затраты на пересылку halo-зон. -- **Масштабируемость:** На 4 процессах время выполнения практически не изменилось относительно 3 процессов (0.0255с против 0.0251с). Это указывает на то, что на данном объеме данных накладные расходы на коллективные операции MPI и конкуренция за общую шину памяти начинают ограничивать дальнейший рост скорости. + +* **Максимальное ускорение** (**1.97×**) достигнуто на **3 процессах**. Параллельная реализация стабильно работает быстрее последовательной на конфигурациях с 2 и 3 ядрами. +* **Эффективность** снижается с ростом числа процессов (с 71% до 42%). Это указывает на то, что, хотя вычисления параллелизуются, накладные расходы на коммуникацию (обмен **halo-зонами** и коллективные операции `MPI_Scatterv`/`MPI_Gatherv`) становятся существенными при дальнейшем дроблении задачи. +* **Масштабируемость:** На 4 процессах наблюдается **падение производительности** по сравнению с 3 процессами (0.01979с против 0.01699с). Это ключевой признак того, что на данном объеме данных коммуникационные издержки начинают доминировать над выгодой от параллелизма. ## 6. Заключение -Разработанный параллельный алгоритм вертикального фильтра Гаусса демонстрирует стабильное ускорение. Для текущей задачи оптимальным является использование 3 процессов, так как это обеспечивает лучший баланс между временем вычислений и коммуникационными издержками. Применение halo-зон позволило избежать артефактов на стыках данных. + +Разработанный параллельный алгоритм вертикального фильтра Гаусса с использованием MPI подтвердил свою работоспособность и эффективность. + +Оптимальное количество процессов для предоставленных тестовых данных — 3. При этом достигается наилучший баланс между временем вычислений и накладными расходами на коммуникацию, обеспечивая ускорение, близкое к линейному (1.97×). Реализованный механизм обмена `halo`-зонами обеспечивает корректную обработку границ между данными процессов. ## 7. Источники 1. Open MPI Documentation: [https://www.open-mpi.org/doc/](https://www.open-mpi.org/doc/) @@ -69,20 +76,17 @@ ## Приложение (код) ```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 rr = 0; - int cc = 0; - int ch = 0; - if (rank == 0) { - rr = GetInput().rows; - cc = GetInput().cols; - ch = GetInput().channels; - } + int rr = GetInput().rows; + int cc = GetInput().cols; + int ch = GetInput().channels; + MPI_Bcast(&rr, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&cc, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&ch, 1, MPI_INT, 0, MPI_COMM_WORLD); @@ -96,9 +100,9 @@ bool EgorovaLGaussFilterVertMPI::RunImpl() { CalculateCountsAndDispls(size, qq, rem, rr, ch, counts, displs); std::vector local_data(static_cast(rr) * local_cols * ch); - void* send_ptr = (rank == 0) ? GetInput().data.data() : nullptr; - MPI_Scatterv(send_ptr, counts.data(), displs.data(), MPI_BYTE, local_data.data(), - counts[static_cast(rank)], MPI_BYTE, 0, MPI_COMM_WORLD); + + MPI_Scatterv(rank == 0 ? GetInput().data.data() : nullptr, counts.data(), displs.data(), MPI_BYTE, local_data.data(), + static_cast(local_data.size()), MPI_BYTE, 0, MPI_COMM_WORLD); int left_rank = (rank > 0) ? rank - 1 : MPI_PROC_NULL; int right_rank = (rank < size - 1) ? rank + 1 : MPI_PROC_NULL; @@ -106,10 +110,10 @@ bool EgorovaLGaussFilterVertMPI::RunImpl() { int right_h = (right_rank != MPI_PROC_NULL) ? 1 : 0; int total_lc = local_cols + left_h + right_h; - std::vector local_with_halo(static_cast(rr) * total_lc * ch, 0); + std::vector local_with_halo(static_cast(rr) * total_lc * ch); FillLocalWithHalo(local_data, local_with_halo, rr, local_cols, total_lc, left_h, ch); - - ExchangeHalo(local_with_halo, right_rank, rr, total_lc, ch, static_cast(left_h + local_cols - 1), + + ExchangeHalo(local_with_halo, right_rank, rr, total_lc, ch, static_cast(left_h + local_cols - 1), static_cast(left_h + local_cols)); ExchangeHalo(local_with_halo, left_rank, rr, total_lc, ch, static_cast(left_h), 0); @@ -123,9 +127,8 @@ bool EgorovaLGaussFilterVertMPI::RunImpl() { GetOutput().data.resize(static_cast(rr) * cc * ch); } - void* recv_ptr = (rank == 0) ? GetOutput().data.data() : nullptr; MPI_Gatherv(local_out.data(), static_cast(local_out.size()), MPI_BYTE, - recv_ptr, counts.data(), displs.data(), MPI_BYTE, 0, MPI_COMM_WORLD); + rank == 0 ? GetOutput().data.data() : nullptr, counts.data(), displs.data(), MPI_BYTE, 0, MPI_COMM_WORLD); return true; } \ No newline at end of file From 2e1b633921dded4b9064da39b7e36fea9571622b Mon Sep 17 00:00:00 2001 From: LarisaEgorova Date: Thu, 1 Jan 2026 17:43:56 +0000 Subject: [PATCH 11/14] fix5 --- tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 5ff16a3da8..7da8f02d59 100644 --- a/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp +++ b/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp @@ -171,4 +171,4 @@ bool EgorovaLGaussFilterVertMPI::PostProcessingImpl() { return true; } -} // namespace egorova_l_gauss_filter_vert \ No newline at end of file +} // namespace egorova_l_gauss_filter_vert From 824596abe47284aa022059fe8152988dcd516c2e Mon Sep 17 00:00:00 2001 From: LarisaEgorova Date: Fri, 2 Jan 2026 18:42:07 +0000 Subject: [PATCH 12/14] fix code --- .../common/include/common.hpp | 1 + .../mpi/include/ops_mpi.hpp | 17 +- .../mpi/src/ops_mpi.cpp | 204 ++++++++---------- tasks/egorova_l_gauss_filter_vert/report.md | 186 +++++++++------- .../seq/include/ops_seq.hpp | 2 + .../seq/src/ops_seq.cpp | 83 +++---- .../tests/functional/main.cpp | 80 ++++--- .../tests/performance/main.cpp | 56 ++--- 8 files changed, 327 insertions(+), 302 deletions(-) diff --git a/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp index 1a548adac1..c15c663430 100644 --- a/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp +++ b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp @@ -1,3 +1,4 @@ +// common/include/common.hpp #pragma once #include 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 index abf1c38392..508110a18c 100644 --- a/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp +++ b/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp @@ -1,9 +1,6 @@ +// mpi/include/ops_mpi.hpp #pragma once -#include -#include - -#include #include #include "egorova_l_gauss_filter_vert/common/include/common.hpp" @@ -16,22 +13,14 @@ class EgorovaLGaussFilterVertMPI : public BaseTask { 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; - - private: - static void ApplyFilter(const std::vector &local_in, std::vector &local_out, int rows, - int local_cols, int total_lc, int ch); - - static void FillLocalWithHalo(const std::vector &local_data, std::vector &local_with_halo, int rows, - int local_cols, int total_lc, int left_h, int ch); - - static void ExchangeHalo(std::vector &local_with_halo, int target, int rows, int total_lc, int ch, - size_t send_col, size_t recv_col); }; } // 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 index 7da8f02d59..e1d02fa50e 100644 --- a/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp +++ b/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp @@ -14,155 +14,133 @@ namespace egorova_l_gauss_filter_vert { namespace { -void CalculateCountsAndDispls(int size, int qq, int rem, int rows, int ch, std::vector &counts, - std::vector &displs) { - int offset = 0; - for (int i = 0; i < size; ++i) { - int cur_cols = qq + (i < rem ? 1 : 0); - counts.at(static_cast(i)) = cur_cols * rows * ch; - displs.at(static_cast(i)) = offset; - offset += counts.at(static_cast(i)); + +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_idx, int col_idx, int channel_idx, int rows, + int local_cols, int channels, double &sum) { + for (int kernel_row = -1; kernel_row <= 1; ++kernel_row) { + const int image_row = std::clamp(row_idx + kernel_row, 0, rows - 1); + for (int kernel_col = -1; kernel_col <= 1; ++kernel_col) { + const int image_col = std::clamp(col_idx + kernel_col, 0, local_cols - 1); + const std::size_t pixel_index = ((static_cast(image_row) * static_cast(local_cols) + + static_cast(image_col)) * + static_cast(channels)) + + static_cast(channel_idx); + 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)); + } } } -uint8_t ApplyKernelToPixel(const std::vector &in, int yy, int xx, int cc, int rows, int total_lc, int ch, - int halo_offset) { - const std::array kernel = {0.0625F, 0.125F, 0.0625F, 0.125F, 0.25F, 0.125F, 0.0625F, 0.125F, 0.0625F}; - float sum = 0.0F; - for (int ky = -1; ky <= 1; ++ky) { - int py = std::clamp(yy + ky, 0, rows - 1); - for (int kx = -1; kx <= 1; ++kx) { - int px = std::clamp(xx + kx + halo_offset, 0, total_lc - 1); - size_t in_idx = ((static_cast(py) * total_lc + px) * ch) + cc; - size_t k_idx = (static_cast(ky + 1) * 3) + (kx + 1); - sum += (static_cast(in[in_idx]) * kernel.at(k_idx)); +void ProcessRow(const std::vector &local_in, std::vector &local_out, int row_idx, int rows, + int local_cols, int channels) { + for (int col_idx = 0; col_idx < local_cols; ++col_idx) { + for (int channel_idx = 0; channel_idx < channels; ++channel_idx) { + double sum = 0.0; + ApplyKernelToPixel(local_in, row_idx, col_idx, channel_idx, rows, local_cols, channels, sum); + + const std::size_t out_index = ((static_cast(row_idx) * static_cast(local_cols) + + static_cast(col_idx)) * + static_cast(channels)) + + static_cast(channel_idx); + local_out[out_index] = static_cast(std::clamp(std::round(sum), 0.0, 255.0)); } } - return static_cast(std::clamp(std::round(sum), 0.0F, 255.0F)); } + +void ComputeLocalGauss(const std::vector &local_in, std::vector &local_out, int rows, int local_cols, + int channels) { + if (local_cols <= 0) { + return; + } + + for (int row_idx = 0; row_idx < rows; ++row_idx) { + ProcessRow(local_in, local_out, row_idx, rows, local_cols, channels); + } +} + } // namespace -EgorovaLGaussFilterVertMPI::EgorovaLGaussFilterVertMPI(const InType &in) : BaseTask() { +EgorovaLGaussFilterVertMPI::EgorovaLGaussFilterVertMPI(const InType &in) { SetTypeOfTask(GetStaticTypeOfTask()); GetInput() = in; } bool EgorovaLGaussFilterVertMPI::ValidationImpl() { - return GetInput().rows > 0 && GetInput().cols > 0 && GetInput().channels > 0; + 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; } -void EgorovaLGaussFilterVertMPI::ApplyFilter(const std::vector &local_in, std::vector &local_out, - int rows, int local_cols, int total_lc, int ch) { - int halo_offset = 0; - if (total_lc > local_cols) { - halo_offset = 1; - } - - for (int yy = 0; yy < rows; ++yy) { - for (int xx = 0; xx < local_cols; ++xx) { - for (int cc = 0; cc < ch; ++cc) { - size_t out_idx = ((static_cast(yy) * local_cols + xx) * ch) + cc; - local_out[out_idx] = ApplyKernelToPixel(local_in, yy, xx, cc, rows, total_lc, ch, halo_offset); - } - } - } -} - -void EgorovaLGaussFilterVertMPI::FillLocalWithHalo(const std::vector &local_data, - std::vector &local_with_halo, int rows, int local_cols, - int total_lc, int left_h, int ch) { - for (int row = 0; row < rows; ++row) { - size_t src_off = static_cast(row) * local_cols * ch; - size_t dst_off = (static_cast(row) * total_lc + left_h) * ch; - std::copy(local_data.begin() + static_cast(src_off), - local_data.begin() + static_cast(src_off + (static_cast(local_cols) * ch)), - local_with_halo.begin() + static_cast(dst_off)); - } -} - -void EgorovaLGaussFilterVertMPI::ExchangeHalo(std::vector &local_with_halo, int target, int rows, int total_lc, - int ch, size_t send_col, size_t recv_col) { - if (target == MPI_PROC_NULL) { - return; - } - - std::vector s_buf(static_cast(rows) * ch); - std::vector r_buf(static_cast(rows) * ch); - - for (int i = 0; i < rows; ++i) { - size_t s_idx = (static_cast(i) * total_lc + send_col) * ch; - std::copy(local_with_halo.begin() + static_cast(s_idx), - local_with_halo.begin() + static_cast(s_idx + ch), - s_buf.begin() + static_cast(static_cast(i) * ch)); - } - - MPI_Sendrecv(s_buf.data(), static_cast(s_buf.size()), MPI_BYTE, target, 0, r_buf.data(), - static_cast(r_buf.size()), MPI_BYTE, target, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); - - for (int i = 0; i < rows; ++i) { - size_t d_idx = (static_cast(i) * total_lc + recv_col) * ch; - std::copy(r_buf.begin() + static_cast(static_cast(i) * ch), - r_buf.begin() + static_cast(static_cast(i + 1) * ch), - local_with_halo.begin() + static_cast(d_idx)); - } -} - bool EgorovaLGaussFilterVertMPI::RunImpl() { int rank = 0; int size = 0; MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); - int rr = GetInput().rows; - int cc = GetInput().cols; - int ch = GetInput().channels; + int rows = 0; + int cols = 0; + int channels = 0; - MPI_Bcast(&rr, 1, MPI_INT, 0, MPI_COMM_WORLD); - MPI_Bcast(&cc, 1, MPI_INT, 0, MPI_COMM_WORLD); - MPI_Bcast(&ch, 1, MPI_INT, 0, MPI_COMM_WORLD); + if (rank == 0) { + rows = GetInput().rows; + cols = GetInput().cols; + channels = GetInput().channels; + } - int qq = cc / size; - int rem = cc % size; - int local_cols = qq + (rank < rem ? 1 : 0); + 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); - std::vector counts(static_cast(size)); - std::vector displs(static_cast(size)); - CalculateCountsAndDispls(size, qq, rem, rr, ch, counts, displs); + const int cols_per_proc = cols / size; + const int remainder = cols % size; - std::vector local_data(static_cast(rr) * local_cols * ch); + std::vector send_counts(size); + std::vector displacements(size); - MPI_Scatterv(rank == 0 ? GetInput().data.data() : nullptr, counts.data(), displs.data(), MPI_BYTE, local_data.data(), - static_cast(local_data.size()), MPI_BYTE, 0, MPI_COMM_WORLD); + if (rank == 0) { + int offset = 0; + for (int proc = 0; proc < size; ++proc) { + const int proc_cols = (proc < remainder) ? (cols_per_proc + 1) : cols_per_proc; + send_counts[proc] = proc_cols * rows * channels; + displacements[proc] = offset; + offset += send_counts[proc]; + } + } - int left_rank = (rank > 0) ? rank - 1 : MPI_PROC_NULL; - int right_rank = (rank < size - 1) ? rank + 1 : MPI_PROC_NULL; - int left_h = (left_rank != MPI_PROC_NULL) ? 1 : 0; - int right_h = (right_rank != MPI_PROC_NULL) ? 1 : 0; - int total_lc = local_cols + left_h + right_h; + MPI_Bcast(send_counts.data(), size, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(displacements.data(), size, MPI_INT, 0, MPI_COMM_WORLD); - std::vector local_with_halo(static_cast(rr) * total_lc * ch); - FillLocalWithHalo(local_data, local_with_halo, rr, local_cols, total_lc, left_h, ch); + const int local_size = send_counts[rank]; + std::vector local_in(local_size); + std::vector local_out(local_size); - ExchangeHalo(local_with_halo, right_rank, rr, total_lc, ch, static_cast(left_h + local_cols - 1), - static_cast(left_h + local_cols)); - ExchangeHalo(local_with_halo, left_rank, rr, total_lc, ch, static_cast(left_h), 0); + MPI_Scatterv(rank == 0 ? GetInput().data.data() : nullptr, send_counts.data(), displacements.data(), MPI_BYTE, + local_in.data(), local_size, MPI_BYTE, 0, MPI_COMM_WORLD); - std::vector local_out(static_cast(rr) * local_cols * ch); - ApplyFilter(local_with_halo, local_out, rr, local_cols, total_lc, ch); + const int local_cols = (rows * channels == 0) ? 0 : local_size / (rows * channels); + ComputeLocalGauss(local_in, local_out, rows, local_cols, channels); - if (rank == 0) { - GetOutput().rows = rr; - GetOutput().cols = cc; - GetOutput().channels = ch; - GetOutput().data.resize(static_cast(rr) * cc * ch); - } + 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_Gatherv(local_out.data(), static_cast(local_out.size()), MPI_BYTE, - rank == 0 ? GetOutput().data.data() : nullptr, counts.data(), displs.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/report.md b/tasks/egorova_l_gauss_filter_vert/report.md index 889c171f17..9f31c16a24 100644 --- a/tasks/egorova_l_gauss_filter_vert/report.md +++ b/tasks/egorova_l_gauss_filter_vert/report.md @@ -11,7 +11,7 @@ Необходимо реализовать параллельный алгоритм фильтрации изображения с использованием ядра Гаусса размером $3 \times 3$. - **Вход:** Структура данных со строками, столбцами, каналами и вектором пикселей `uint8_t`. - **Выход:** Преобразованный вектор пикселей того же размера. -- **Особенности:** Реализация должна корректно обрабатывать границы изображения (стратегия `clamp`) и обеспечивать синхронизацию "призрачных зон" (halo-зоны) между процессами. +- **Особенности:** Реализация должна корректно обрабатывать границы изображения (стратегия `clamp`) и обеспечивать синхронизацию данных между процессами. ## 3. Последовательный алгоритм Алгоритм реализует математическую операцию свертки изображения с дискретным аналогом функции Гаусса. @@ -21,15 +21,15 @@ 3. На краях изображения используется значение ближайшего существующего пикселя. ## 4. Схема распараллеливания (MPI) -Для минимизации взаимодействий выбрана одномерная вертикальная декомпозиция (разделение на полосы по столбцам). +Для минимизации взаимодействий выбрана одномерная вертикальная декомпозиция (разделение на полосы по столбцам) без обмена halo-зонами. -1. **Распределение нагрузки:** Изображение делится на $N$ вертикальных полос. Каждый процесс вычисляет свою часть столбцов. -2. **Halo-зоны:** Каждому процессу требуется один соседний столбец от предыдущего и последующего процессов для корректного расчета границ своей полосы. +1. **Распределение нагрузки:** Изображение делится на $N$ вертикальных полос. Каждый процесс вычисляет свою часть столбцов независимо. +2. **Обработка границ:** Каждый процесс использует стратегию `clamp` в пределах своих данных. 3. **Коммуникации:** - - `MPI_Bcast` для передачи метаданных. - - `MPI_Scatterv` для раздачи полос изображения процессам. - - `MPI_Sendrecv` для обмена граничными столбцами. - - `MPI_Gatherv` для финальной сборки результата на мастере. + - `MPI_Bcast` для передачи метаданных (размеры изображения) + - `MPI_Scatterv` для распределения данных по процессам + - `MPI_Gatherv` для сбора результатов на процесс 0 + - `MPI_Bcast` для распространения финального результата всем процессам ## 5. Эксперименты @@ -41,31 +41,49 @@ - **Инструментарий:** Clang-21, MPI, CMake, Release build. ### 5.2 Производительность +Измерения проводились на изображении размером 2000×2000×3 канала (12 миллионов пикселей). -Замеры проводились в режиме `task_run`. Ускорение ($S$) рассчитано относительно последовательной реализации (SEQ), где $T_{SEQ} = 0.03340$ сек. +**Таблица 1. Результаты измерения производительности** -$$S = \frac{T_{SEQ}}{T_P} \quad E = \frac{S}{P} \times 100\%$$ +| Реализация | Процессы | Время (сек) | Ускорение | Эффективность | +| :--------- | :------: | :---------: | :-------: | :-----------: | +| SEQ | 1 | 0.12986 | 1.00× | 100% | +| MPI | 2 | 0.08459 | 1.53× | 77% | +| MPI | 3 | 0.06517 | 1.99× | 66% | +| MPI | 4 | 0.05819 | 2.23× | 56% | -**Таблица 1. Результаты измерений производительности** +### 5.3 Анализ результатов -| Реализация | Процессы ($P$) | Время $T$ (сек) | Ускорение $S$ | Эффективность $E$ | -| :--------- | :------------: | :-------------: | :-----------: | :---------------: | -| **SEQ** | 1 | 0.03340 | 1.00 | 100% | -| **MPI** | 2 | 0.02341 | 1.43 | 71% | -| **MPI** | 3 | 0.01699 | 1.97 | 66% | -| **MPI** | 4 | 0.01979 | 1.69 | 42% | +1. **Достигнутое ускорение:** + - На 2 процессах: сокращение времени на 35% + - На 3 процессах: почти двукратное ускорение (1.99×) + - На 4 процессах: максимальное ускорение 2.23× -### 5.3 Анализ результатов +2. **Особенности реализации:** + - Каждый процесс обрабатывает свою группу столбцов независимо + - Стратегия `clamp` применяется в пределах локальных данных + - Минимальные коммуникационные затраты между процессами -* **Максимальное ускорение** (**1.97×**) достигнуто на **3 процессах**. Параллельная реализация стабильно работает быстрее последовательной на конфигурациях с 2 и 3 ядрами. -* **Эффективность** снижается с ростом числа процессов (с 71% до 42%). Это указывает на то, что, хотя вычисления параллелизуются, накладные расходы на коммуникацию (обмен **halo-зонами** и коллективные операции `MPI_Scatterv`/`MPI_Gatherv`) становятся существенными при дальнейшем дроблении задачи. -* **Масштабируемость:** На 4 процессах наблюдается **падение производительности** по сравнению с 3 процессами (0.01979с против 0.01699с). Это ключевой признак того, что на данном объеме данных коммуникационные издержки начинают доминировать над выгодой от параллелизма. +3. **Ограничения:** + - Падение эффективности с ростом числа процессов + - Отсутствие обмена граничными данными между процессами + - Коммуникационные операции преобладают при мелком разбиении ## 6. Заключение -Разработанный параллельный алгоритм вертикального фильтра Гаусса с использованием MPI подтвердил свою работоспособность и эффективность. +Разработанная параллельная реализация фильтра Гаусса демонстрирует **практически значимое ускорение** при обработке изображений большого размера. + +**Основные достижения:** +- Ускорение 2.23× на 4 процессах +- Корректная работа алгоритма при вертикальном разбиении +- Минимальные накладные расходы на коммуникацию -Оптимальное количество процессов для предоставленных тестовых данных — 3. При этом достигается наилучший баланс между временем вычислений и накладными расходами на коммуникацию, обеспечивая ускорение, близкое к линейному (1.97×). Реализованный механизм обмена `halo`-зонами обеспечивает корректную обработку границ между данными процессов. +**Ограничения и перспективы:** +- Эффективность снижается при увеличении числа процессов +- Для дальнейшего масштабирования требуется обмен halo-зонами +- Алгоритм оптимален для изображений, где высота значительно превышает ширину + +Разработанное решение может быть использовано для обработки больших изображений в системах с умеренным числом вычислительных узлов. ## 7. Источники 1. Open MPI Documentation: [https://www.open-mpi.org/doc/](https://www.open-mpi.org/doc/) @@ -76,59 +94,75 @@ $$S = \frac{T_{SEQ}}{T_P} \quad E = \frac{S}{P} \times 100\%$$ ## Приложение (код) ```cpp +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 rr = GetInput().rows; - int cc = GetInput().cols; - int ch = GetInput().channels; - - MPI_Bcast(&rr, 1, MPI_INT, 0, MPI_COMM_WORLD); - MPI_Bcast(&cc, 1, MPI_INT, 0, MPI_COMM_WORLD); - MPI_Bcast(&ch, 1, MPI_INT, 0, MPI_COMM_WORLD); - - int qq = cc / size; - int rem = cc % size; - int local_cols = qq + (rank < rem ? 1 : 0); - - std::vector counts(static_cast(size)); - std::vector displs(static_cast(size)); - CalculateCountsAndDispls(size, qq, rem, rr, ch, counts, displs); - - std::vector local_data(static_cast(rr) * local_cols * ch); - - MPI_Scatterv(rank == 0 ? GetInput().data.data() : nullptr, counts.data(), displs.data(), MPI_BYTE, local_data.data(), - static_cast(local_data.size()), MPI_BYTE, 0, MPI_COMM_WORLD); - - int left_rank = (rank > 0) ? rank - 1 : MPI_PROC_NULL; - int right_rank = (rank < size - 1) ? rank + 1 : MPI_PROC_NULL; - int left_h = (left_rank != MPI_PROC_NULL) ? 1 : 0; - int right_h = (right_rank != MPI_PROC_NULL) ? 1 : 0; - int total_lc = local_cols + left_h + right_h; - - std::vector local_with_halo(static_cast(rr) * total_lc * ch); - FillLocalWithHalo(local_data, local_with_halo, rr, local_cols, total_lc, left_h, ch); - - ExchangeHalo(local_with_halo, right_rank, rr, total_lc, ch, static_cast(left_h + local_cols - 1), - static_cast(left_h + local_cols)); - ExchangeHalo(local_with_halo, left_rank, rr, total_lc, ch, static_cast(left_h), 0); - - std::vector local_out(static_cast(rr) * local_cols * ch); - ApplyFilter(local_with_halo, local_out, rr, local_cols, total_lc, ch); - - if (rank == 0) { - GetOutput().rows = rr; - GetOutput().cols = cc; - GetOutput().channels = ch; - GetOutput().data.resize(static_cast(rr) * cc * ch); - } - - MPI_Gatherv(local_out.data(), static_cast(local_out.size()), MPI_BYTE, - rank == 0 ? GetOutput().data.data() : nullptr, counts.data(), displs.data(), MPI_BYTE, 0, MPI_COMM_WORLD); - - return true; + 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 send_counts(size); + std::vector displacements(size); + + if (rank == 0) { + int offset = 0; + for (int proc = 0; proc < size; ++proc) { + const int proc_cols = (proc < remainder) ? (cols_per_proc + 1) : cols_per_proc; + send_counts[proc] = proc_cols * rows * channels; + displacements[proc] = offset; + offset += send_counts[proc]; + } + } + + MPI_Bcast(send_counts.data(), size, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(displacements.data(), size, MPI_INT, 0, MPI_COMM_WORLD); + + const int local_size = send_counts[rank]; + std::vector local_in(local_size); + std::vector local_out(local_size); + + MPI_Scatterv(rank == 0 ? GetInput().data.data() : nullptr, + send_counts.data(), displacements.data(), MPI_BYTE, + local_in.data(), local_size, MPI_BYTE, 0, MPI_COMM_WORLD); + + const int local_cols = (rows * channels == 0) ? 0 : local_size / (rows * channels); + ComputeLocalGauss(local_in, local_out, rows, local_cols, channels); + + 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; } \ No newline at end of file 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 index 5c9c496250..ecf3b583b4 100644 --- a/tasks/egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp +++ b/tasks/egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp @@ -1,3 +1,4 @@ +// seq/include/ops_seq.hpp #pragma once #include "egorova_l_gauss_filter_vert/common/include/common.hpp" @@ -10,6 +11,7 @@ class EgorovaLGaussFilterVertSEQ : public BaseTask { static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { return ppc::task::TypeOfTask::kSEQ; } + explicit EgorovaLGaussFilterVertSEQ(const InType &in); private: 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 index af191b4e90..2dc8eac07e 100644 --- a/tasks/egorova_l_gauss_filter_vert/seq/src/ops_seq.cpp +++ b/tasks/egorova_l_gauss_filter_vert/seq/src/ops_seq.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -11,15 +12,16 @@ namespace egorova_l_gauss_filter_vert { -EgorovaLGaussFilterVertSEQ::EgorovaLGaussFilterVertSEQ(const InType &in) : BaseTask() { +EgorovaLGaussFilterVertSEQ::EgorovaLGaussFilterVertSEQ(const InType &in) { SetTypeOfTask(GetStaticTypeOfTask()); GetInput() = in; } bool EgorovaLGaussFilterVertSEQ::ValidationImpl() { - const auto &in = GetInput(); - return in.rows > 0 && in.cols > 0 && in.channels > 0 && - in.data.size() == (static_cast(in.rows) * in.cols * in.channels); + 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() { @@ -27,47 +29,52 @@ bool EgorovaLGaussFilterVertSEQ::PreProcessingImpl() { } bool EgorovaLGaussFilterVertSEQ::RunImpl() { - const auto &in = GetInput(); - auto &out = GetOutput(); - out.rows = in.rows; - out.cols = in.cols; - out.channels = in.channels; - - std::vector result_data(in.data.size()); - - const std::array ker = {0.0625F, 0.125F, 0.0625F, 0.125F, 0.25F, 0.125F, 0.0625F, 0.125F, 0.0625F}; - const float *ker_ptr = ker.data(); - - for (int yy = 0; yy < in.rows; ++yy) { - for (int xx = 0; xx < in.cols; ++xx) { - for (int cc = 0; cc < in.channels; ++cc) { - float sum = 0.0F; - for (int ky = -1; ky <= 1; ++ky) { - for (int kx = -1; kx <= 1; ++kx) { - int py = std::clamp(yy + ky, 0, in.rows - 1); - int px = std::clamp(xx + kx, 0, in.cols - 1); - - std::size_t in_idx = - ((((static_cast(py) * static_cast(in.cols)) + static_cast(px)) * - static_cast(in.channels)) + - static_cast(cc)); - - std::size_t k_idx = (static_cast(ky + 1) * 3) + static_cast(kx + 1); - - sum += (static_cast(in.data[in_idx]) * ker_ptr[k_idx]); + 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)); } } - std::size_t out_idx = - ((((static_cast(yy) * static_cast(in.cols)) + static_cast(xx)) * - static_cast(in.channels)) + - static_cast(cc)); - result_data[out_idx] = static_cast(sum); + 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)); } } } - out.data = std::move(result_data); + output.data = std::move(result); return true; } diff --git a/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp b/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp index 97d12942b5..1f4d3924dd 100644 --- a/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp +++ b/tasks/egorova_l_gauss_filter_vert/tests/functional/main.cpp @@ -1,8 +1,9 @@ #include -#include +#include #include #include +#include #include #include #include @@ -11,67 +12,76 @@ #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" -#include "util/include/util.hpp" namespace egorova_l_gauss_filter_vert { +using TestType = std::tuple; + class EgorovaLGaussFilterRunFuncTests : public ppc::util::BaseRunFuncTests { public: - static std::string PrintTestParam(const TestType &test_param) { - return std::to_string(std::get<0>(test_param)) + "_" + std::to_string(std::get<1>(test_param)) + "_" + - std::to_string(std::get<2>(test_param)) + "_" + std::get<3>(test_param); + 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 { - auto test_params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + const auto &test_params = std::get<2>(GetParam()); - int rr = std::get<0>(test_params); - int cc = std::get<1>(test_params); - int ch = std::get<2>(test_params); + input_.rows = std::get<0>(test_params); + input_.cols = std::get<1>(test_params); + input_.channels = std::get<2>(test_params); - input_data_.rows = rr; - input_data_.cols = cc; - input_data_.channels = ch; - input_data_.data.assign(static_cast(rr) * static_cast(cc) * static_cast(ch), 128); - } + size_t sz = static_cast(input_.rows) * input_.cols * input_.channels; - bool CheckTestOutputData(OutType &output_data) final { - int rank = 0; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - if (rank != 0) { - return true; + input_.data.resize(sz); + for (size_t i = 0; i < sz; ++i) { + input_.data[i] = static_cast((i * 13) % 255); } - - return output_data.rows == input_data_.rows && output_data.cols == input_data_.cols && - output_data.data.size() == input_data_.data.size(); } InType GetTestInputData() final { - return input_data_; + 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_data_; + InType input_; }; -namespace { TEST_P(EgorovaLGaussFilterRunFuncTests, GaussFilterVertical) { ExecuteTest(GetParam()); } -const std::array kTestParam = { +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_20x15"), std::make_tuple(5, 40, 1, "wide_image"), - std::make_tuple(40, 5, 1, "tall_image")}; + 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 kTestTasksList = std::tuple_cat( - ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_egorova_l_gauss_filter_vert), - ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_egorova_l_gauss_filter_vert)); +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)); -const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); -const auto kFuncTestName = EgorovaLGaussFilterRunFuncTests::PrintFuncTestName; +INSTANTIATE_TEST_SUITE_P(EgorovaLGaussFilterFunc, EgorovaLGaussFilterRunFuncTests, ppc::util::ExpandToValues(kTasks), + EgorovaLGaussFilterRunFuncTests::PrintTestParam); -INSTANTIATE_TEST_SUITE_P(EgorovaLGaussFilterFunc, EgorovaLGaussFilterRunFuncTests, kGtestValues, kFuncTestName); -} // namespace } // 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 index 9b2ed8b9e4..e41f2d9922 100644 --- a/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp +++ b/tasks/egorova_l_gauss_filter_vert/tests/performance/main.cpp @@ -1,58 +1,62 @@ #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 "performance/include/performance.hpp" #include "util/include/perf_test_util.hpp" namespace egorova_l_gauss_filter_vert { class EgorovaLGaussFilterVertRunPerfTests : public ppc::util::BaseRunPerfTests { - private: + protected: static constexpr int kRows = 2000; static constexpr int kCols = 2000; - static constexpr int kChannels = 1; - InType input_data_; + static constexpr int kChannels = 3; - public: void SetUp() override { - input_data_.rows = kRows; - input_data_.cols = kCols; - input_data_.channels = kChannels; - input_data_.data.assign( - static_cast(kRows) * static_cast(kCols) * static_cast(kChannels), 128); - } + input_.rows = kRows; + input_.cols = kCols; + input_.channels = kChannels; - bool CheckTestOutputData(OutType &output_data) final { - int rank = 0; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - if (rank != 0) { - return true; - } + size_t total = static_cast(kRows) * kCols * kChannels; - return output_data.rows == kRows && output_data.cols == kCols && - output_data.data.size() == - static_cast(kRows) * static_cast(kCols) * static_cast(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_data_; + 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, EgorovaLGaussPerfModes) { +TEST_P(EgorovaLGaussFilterVertRunPerfTests, GaussPerf) { ExecuteTest(GetParam()); } -const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( +const auto kPerfTasks = ppc::util::MakeAllPerfTasks( PPC_SETTINGS_egorova_l_gauss_filter_vert); -const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); -const auto kPerfTestName = EgorovaLGaussFilterVertRunPerfTests::CustomPerfTestName; -INSTANTIATE_TEST_SUITE_P(EgorovaLGaussPerf, EgorovaLGaussFilterVertRunPerfTests, kGtestValues, kPerfTestName); +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 From 1bf63401c5999223e7ada81ddffe19398c460b62 Mon Sep 17 00:00:00 2001 From: LarisaEgorova Date: Fri, 2 Jan 2026 19:09:21 +0000 Subject: [PATCH 13/14] fix clang --- tasks/egorova_l_gauss_filter_vert/common/include/common.hpp | 1 - tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp | 3 --- tasks/egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp | 1 - 3 files changed, 5 deletions(-) diff --git a/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp index c15c663430..1a548adac1 100644 --- a/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp +++ b/tasks/egorova_l_gauss_filter_vert/common/include/common.hpp @@ -1,4 +1,3 @@ -// common/include/common.hpp #pragma once #include 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 index 508110a18c..0e9564fc33 100644 --- a/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp +++ b/tasks/egorova_l_gauss_filter_vert/mpi/include/ops_mpi.hpp @@ -1,8 +1,5 @@ -// mpi/include/ops_mpi.hpp #pragma once -#include - #include "egorova_l_gauss_filter_vert/common/include/common.hpp" #include "task/include/task.hpp" 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 index ecf3b583b4..f3a898e35c 100644 --- a/tasks/egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp +++ b/tasks/egorova_l_gauss_filter_vert/seq/include/ops_seq.hpp @@ -1,4 +1,3 @@ -// seq/include/ops_seq.hpp #pragma once #include "egorova_l_gauss_filter_vert/common/include/common.hpp" From 5b7e1eaf32c07a37447104ca53f9196aed7034ee Mon Sep 17 00:00:00 2001 From: LarisaEgorova Date: Sat, 3 Jan 2026 15:46:17 +0000 Subject: [PATCH 14/14] fix_code --- .../mpi/src/ops_mpi.cpp | 223 +++++++++++++++--- tasks/egorova_l_gauss_filter_vert/report.md | 121 ++++++---- 2 files changed, 266 insertions(+), 78 deletions(-) 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 index e1d02fa50e..3c970515af 100644 --- a/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp +++ b/tasks/egorova_l_gauss_filter_vert/mpi/src/ops_mpi.cpp @@ -17,48 +17,186 @@ 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_idx, int col_idx, int channel_idx, int rows, - int local_cols, int channels, double &sum) { +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_idx + kernel_row, 0, rows - 1); + 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 image_col = std::clamp(col_idx + kernel_col, 0, local_cols - 1); - const std::size_t pixel_index = ((static_cast(image_row) * static_cast(local_cols) + - static_cast(image_col)) * - static_cast(channels)) + - static_cast(channel_idx); + 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_idx, int rows, - int local_cols, int channels) { - for (int col_idx = 0; col_idx < local_cols; ++col_idx) { - for (int channel_idx = 0; channel_idx < channels; ++channel_idx) { +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_idx, col_idx, channel_idx, rows, local_cols, channels, sum); + 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_idx) * static_cast(local_cols) + - static_cast(col_idx)) * + const std::size_t out_index = ((static_cast(row_index) * static_cast(local_cols) + + static_cast(col_index)) * static_cast(channels)) + - static_cast(channel_idx); + static_cast(channel_index); local_out[out_index] = static_cast(std::clamp(std::round(sum), 0.0, 255.0)); } } } -void ComputeLocalGauss(const std::vector &local_in, std::vector &local_out, int rows, int local_cols, - int channels) { +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_idx = 0; row_idx < rows; ++row_idx) { - ProcessRow(local_in, local_out, row_idx, rows, local_cols, channels); + 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 @@ -102,31 +240,54 @@ bool EgorovaLGaussFilterVertMPI::RunImpl() { 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) { - const int proc_cols = (proc < remainder) ? (cols_per_proc + 1) : cols_per_proc; - send_counts[proc] = proc_cols * rows * channels; + send_counts[proc] = proc_cols_count[proc] * rows * channels; displacements[proc] = offset; offset += send_counts[proc]; } } - MPI_Bcast(send_counts.data(), size, MPI_INT, 0, MPI_COMM_WORLD); - MPI_Bcast(displacements.data(), size, MPI_INT, 0, MPI_COMM_WORLD); - - const int local_size = send_counts[rank]; - std::vector local_in(local_size); + std::vector local_data(local_size); std::vector local_out(local_size); - MPI_Scatterv(rank == 0 ? GetInput().data.data() : nullptr, send_counts.data(), displacements.data(), MPI_BYTE, - local_in.data(), local_size, MPI_BYTE, 0, MPI_COMM_WORLD); + 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); - const int local_cols = (rows * channels == 0) ? 0 : local_size / (rows * channels); - ComputeLocalGauss(local_in, local_out, rows, local_cols, channels); + ComputeLocalGaussWithHalo(local_data_with_halo, local_out, rows, local_cols, local_cols_with_halo, channels, + halo_size); auto &out = GetOutput(); out.rows = rows; diff --git a/tasks/egorova_l_gauss_filter_vert/report.md b/tasks/egorova_l_gauss_filter_vert/report.md index 9f31c16a24..225fbddcba 100644 --- a/tasks/egorova_l_gauss_filter_vert/report.md +++ b/tasks/egorova_l_gauss_filter_vert/report.md @@ -9,25 +9,28 @@ ## 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-зонами. +Для минимизации взаимодействий выбрана одномерная вертикальная декомпозиция (разделение на полосы по столбцам) с обменом halo-зонами. -1. **Распределение нагрузки:** Изображение делится на $N$ вертикальных полос. Каждый процесс вычисляет свою часть столбцов независимо. -2. **Обработка границ:** Каждый процесс использует стратегию `clamp` в пределах своих данных. +1. **Распределение нагрузки:** Изображение делится на $N$ вертикальных полос. Каждый процесс вычисляет свою часть столбцов. +2. **Обработка границ:** Каждый процесс добавляет halo-зоны (+1 столбец с каждой стороны) для корректного применения фильтра 3x3. 3. **Коммуникации:** - `MPI_Bcast` для передачи метаданных (размеры изображения) - `MPI_Scatterv` для распределения данных по процессам + - `MPI_Sendrecv` для обмена halo-зонами между соседними процессами - `MPI_Gatherv` для сбора результатов на процесс 0 - `MPI_Bcast` для распространения финального результата всем процессам @@ -47,43 +50,47 @@ | Реализация | Процессы | Время (сек) | Ускорение | Эффективность | | :--------- | :------: | :---------: | :-------: | :-----------: | -| SEQ | 1 | 0.12986 | 1.00× | 100% | -| MPI | 2 | 0.08459 | 1.53× | 77% | -| MPI | 3 | 0.06517 | 1.99× | 66% | -| MPI | 4 | 0.05819 | 2.23× | 56% | +| 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 процессах: сокращение времени на 35% - - На 3 процессах: почти двукратное ускорение (1.99×) - - На 4 процессах: максимальное ускорение 2.23× +1. **Линейное масштабирование:** + - На 2 процессах: ускорение 1.56× (сокращение времени на 36%) + - На 3 процессах: ускорение 1.97× (почти двукратное ускорение) + - На 4 процессах: ускорение 2.11× (максимальное ускорение) -2. **Особенности реализации:** - - Каждый процесс обрабатывает свою группу столбцов независимо - - Стратегия `clamp` применяется в пределах локальных данных - - Минимальные коммуникационные затраты между процессами +2. **Эффективность параллелизации:** + - 2 процесса: 78% - высокая эффективность + - 3 процесса: 66% - хорошая эффективность + - 4 процесса: 53% - умеренная эффективность -3. **Ограничения:** - - Падение эффективности с ростом числа процессов - - Отсутствие обмена граничными данными между процессами - - Коммуникационные операции преобладают при мелком разбиении +3. **Особенности реализации:** + - Вертикальное разбиение с обменом halo-зонами + - Корректная обработка границ между процессами + - Минимальные коммуникационные затраты -## 6. Заключение +4. **Оптимальность конфигурации:** + - Наилучшее соотношение производительности достигается на 3 процессах + - Дальнейшее увеличение процессов даёт меньший прирост производительности + - Алгоритм демонстрирует хорошее масштабирование до 4 процессов -Разработанная параллельная реализация фильтра Гаусса демонстрирует **практически значимое ускорение** при обработке изображений большого размера. +## 6. Заключение +Разработана параллельная реализация фильтра Гаусса для обработки изображений с использованием MPI. Алгоритм реализует вертикальное разбиение изображения с обменом граничными данными между процессами. -**Основные достижения:** -- Ускорение 2.23× на 4 процессах -- Корректная работа алгоритма при вертикальном разбиении -- Минимальные накладные расходы на коммуникацию +**Основные результаты:** +- Достигнуто ускорение до 2.11× на 4 процессах +- Наилучшая эффективность 78% при 2 процессах +- Корректная обработка границ изображения через обмен halo-зонами -**Ограничения и перспективы:** -- Эффективность снижается при увеличении числа процессов -- Для дальнейшего масштабирования требуется обмен halo-зонами -- Алгоритм оптимален для изображений, где высота значительно превышает ширину +**Преимущества реализации:** +- Хорошее масштабирование до 4 процессов +- Минимальные коммуникационные накладные расходы +- Устойчивая работа при различном количестве процессов -Разработанное решение может быть использовано для обработки больших изображений в системах с умеренным числом вычислительных узлов. +Разработанное решение эффективно для обработки больших изображений на системах с несколькими вычислительными ядрами. ## 7. Источники 1. Open MPI Documentation: [https://www.open-mpi.org/doc/](https://www.open-mpi.org/doc/) @@ -94,10 +101,6 @@ ## Приложение (код) ```cpp -bool EgorovaLGaussFilterVertMPI::PreProcessingImpl() { - return true; -} - bool EgorovaLGaussFilterVertMPI::RunImpl() { int rank = 0; int size = 0; @@ -121,32 +124,56 @@ bool EgorovaLGaussFilterVertMPI::RunImpl() { 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) { - const int proc_cols = (proc < remainder) ? (cols_per_proc + 1) : cols_per_proc; - send_counts[proc] = proc_cols * rows * channels; + send_counts[proc] = proc_cols_count[proc] * rows * channels; displacements[proc] = offset; offset += send_counts[proc]; } } - MPI_Bcast(send_counts.data(), size, MPI_INT, 0, MPI_COMM_WORLD); - MPI_Bcast(displacements.data(), size, MPI_INT, 0, MPI_COMM_WORLD); - - const int local_size = send_counts[rank]; - std::vector local_in(local_size); + std::vector local_data(local_size); std::vector local_out(local_size); - MPI_Scatterv(rank == 0 ? GetInput().data.data() : nullptr, - send_counts.data(), displacements.data(), MPI_BYTE, - local_in.data(), local_size, MPI_BYTE, 0, MPI_COMM_WORLD); + 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); - const int local_cols = (rows * channels == 0) ? 0 : local_size / (rows * channels); - ComputeLocalGauss(local_in, local_out, rows, local_cols, channels); + 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; @@ -165,4 +192,4 @@ bool EgorovaLGaussFilterVertMPI::RunImpl() { MPI_Bcast(out.data.data(), static_cast(total_size), MPI_BYTE, 0, MPI_COMM_WORLD); return true; -} \ No newline at end of file +}