From db335042a7d04ec0e46ca6dda850ebb92d70c89f Mon Sep 17 00:00:00 2001 From: Victoria Date: Tue, 14 Apr 2026 22:26:51 +0300 Subject: [PATCH 01/22] done --- .../stl/include/ops_stl.hpp | 30 +++ .../stl/src/ops_stl.cpp | 198 ++++++++++++++++++ .../tests/functional/main.cpp | 2 + .../tests/performance/main.cpp | 5 +- 4 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 tasks/kondrashova_v_marking_components/stl/include/ops_stl.hpp create mode 100644 tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp diff --git a/tasks/kondrashova_v_marking_components/stl/include/ops_stl.hpp b/tasks/kondrashova_v_marking_components/stl/include/ops_stl.hpp new file mode 100644 index 000000000..6979e1d5b --- /dev/null +++ b/tasks/kondrashova_v_marking_components/stl/include/ops_stl.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "kondrashova_v_marking_components/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace kondrashova_v_marking_components { + +class KondrashovaVTaskSTL : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSTL; + } + explicit KondrashovaVTaskSTL(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + int width_{}; + int height_{}; + std::vector image_; + std::vector labels_1d_; +}; + +} // namespace kondrashova_v_marking_components diff --git a/tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp b/tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp new file mode 100644 index 000000000..c63b6bf81 --- /dev/null +++ b/tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp @@ -0,0 +1,198 @@ +#include "kondrashova_v_marking_components/stl/include/ops_stl.hpp" + +#include +#include +#include +#include +#include + +#include "kondrashova_v_marking_components/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace kondrashova_v_marking_components { + +KondrashovaVTaskSTL::KondrashovaVTaskSTL(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = {}; +} + +bool KondrashovaVTaskSTL::ValidationImpl() { + return true; +} + +bool KondrashovaVTaskSTL::PreProcessingImpl() { + const auto &in = GetInput(); + + width_ = in.width; + height_ = in.height; + image_ = in.data; + + labels_1d_.assign(static_cast(width_) * static_cast(height_), 0); + + GetOutput().count = 0; + GetOutput().labels.clear(); + return true; +} + +namespace { + +int Find(std::vector &parent, int xx) { + while (parent[static_cast(xx)] != xx) { + parent[static_cast(xx)] = parent[static_cast(parent[static_cast(xx)])]; + xx = parent[static_cast(xx)]; + } + return xx; +} + +void Unite(std::vector &parent, std::vector &rnk, int aa, int bb) { + aa = Find(parent, aa); + bb = Find(parent, bb); + if (aa == bb) { + return; + } + if (rnk[static_cast(aa)] < rnk[static_cast(bb)]) { + std::swap(aa, bb); + } + parent[static_cast(bb)] = aa; + if (rnk[static_cast(aa)] == rnk[static_cast(bb)]) { + rnk[static_cast(aa)]++; + } +} + +int GetNeighborLabel(int ii, int jj, int di, int dj, int row_start, int row_end, int width, + const std::vector &image, const std::vector &local_labels) { + int ni = ii + di; + int nj = jj + dj; + if (ni < row_start || ni >= row_end || nj < 0 || nj >= width) { + return 0; + } + auto nidx = (static_cast(ni) * static_cast(width)) + static_cast(nj); + if (image[nidx] == 0) { + return local_labels[nidx]; + } + return 0; +} + +void ScanStripe(int row_start, int row_end, int width, int label_offset, const std::vector &image, + std::vector &local_labels) { + int current_label = label_offset; + for (int ii = row_start; ii < row_end; ++ii) { + for (int jj = 0; jj < width; ++jj) { + auto idx = (static_cast(ii) * static_cast(width)) + static_cast(jj); + if (image[idx] != 0) { + continue; + } + + int left_label = GetNeighborLabel(ii, jj, 0, -1, row_start, row_end, width, image, local_labels); + int top_label = GetNeighborLabel(ii, jj, -1, 0, row_start, row_end, width, image, local_labels); + + if (left_label == 0 && top_label == 0) { + local_labels[idx] = ++current_label; + } else if (left_label != 0 && top_label == 0) { + local_labels[idx] = left_label; + } else if (left_label == 0) { + local_labels[idx] = top_label; + } else { + local_labels[idx] = std::min(left_label, top_label); + } + } + } +} + +void MergeHorizontal(int height, int width, const std::vector &local_labels, std::vector &parent, + std::vector &rnk) { + for (int ii = 0; ii < height; ++ii) { + for (int jj = 1; jj < width; ++jj) { + auto idx = (static_cast(ii) * static_cast(width)) + static_cast(jj); + auto lidx = (static_cast(ii) * static_cast(width)) + static_cast(jj - 1); + if (local_labels[idx] != 0 && local_labels[lidx] != 0 && local_labels[idx] != local_labels[lidx]) { + Unite(parent, rnk, local_labels[idx], local_labels[lidx]); + } + } + } +} + +void MergeBoundaries(int height, int width, int num_threads, const std::vector &local_labels, + std::vector &parent, std::vector &rnk) { + for (int tid = 1; tid < num_threads; ++tid) { + const int boundary_row = (tid * height) / num_threads; + if (boundary_row >= height) { + continue; + } + for (int jj = 0; jj < width; ++jj) { + auto idx = (static_cast(boundary_row) * static_cast(width)) + static_cast(jj); + auto tidx = (static_cast(boundary_row - 1) * static_cast(width)) + static_cast(jj); + if (local_labels[idx] != 0 && local_labels[tidx] != 0 && local_labels[idx] != local_labels[tidx]) { + Unite(parent, rnk, local_labels[idx], local_labels[tidx]); + } + } + } +} + +int Relabel(int total, const std::vector &local_labels, std::vector &parent, std::vector &relabel_map, + std::vector &labels_1d) { + int count = 0; + for (int ii = 0; ii < total; ++ii) { + auto idx = static_cast(ii); + if (local_labels[idx] == 0) { + continue; + } + int root = Find(parent, local_labels[idx]); + if (relabel_map[static_cast(root)] == 0) { + relabel_map[static_cast(root)] = ++count; + } + labels_1d[idx] = relabel_map[static_cast(root)]; + } + return count; +} + +} // namespace + +bool KondrashovaVTaskSTL::RunImpl() { + const int total = width_ * height_; + const int num_threads = ppc::util::GetNumThreads(); + const int max_labels_per_thread = total + 1; + const int max_total_labels = (num_threads * max_labels_per_thread) + 1; + + std::vector local_labels(static_cast(total), 0); + + std::vector threads(static_cast(num_threads)); + for (int tid = 0; tid < num_threads; ++tid) { + const int row_start = (tid * height_) / num_threads; + const int row_end = ((tid + 1) * height_) / num_threads; + const int label_offset = tid * max_labels_per_thread; + threads[static_cast(tid)] = + std::thread(ScanStripe, row_start, row_end, width_, label_offset, std::cref(image_), std::ref(local_labels)); + } + for (auto &thr : threads) { + thr.join(); + } + + std::vector parent(static_cast(max_total_labels)); + std::vector rnk(static_cast(max_total_labels), 0); + for (int ii = 0; ii < max_total_labels; ++ii) { + parent[static_cast(ii)] = ii; + } + + MergeHorizontal(height_, width_, local_labels, parent, rnk); + MergeBoundaries(height_, width_, num_threads, local_labels, parent, rnk); + + std::vector relabel_map(static_cast(max_total_labels), 0); + GetOutput().count = Relabel(total, local_labels, parent, relabel_map, labels_1d_); + + return true; +} + +bool KondrashovaVTaskSTL::PostProcessingImpl() { + GetOutput().labels.assign(height_, std::vector(width_, 0)); + for (int ii = 0; ii < height_; ++ii) { + for (int jj = 0; jj < width_; ++jj) { + auto idx = (static_cast(ii) * static_cast(width_)) + static_cast(jj); + GetOutput().labels[ii][jj] = labels_1d_[idx]; + } + } + return true; +} + +} // namespace kondrashova_v_marking_components diff --git a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp index ea3b704c3..524d3f4eb 100644 --- a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp +++ b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp @@ -9,6 +9,7 @@ #include "kondrashova_v_marking_components/common/include/common.hpp" #include "kondrashova_v_marking_components/omp/include/ops_omp.hpp" #include "kondrashova_v_marking_components/seq/include/ops_seq.hpp" +#include "kondrashova_v_marking_components/stl/include/ops_stl.hpp" #include "kondrashova_v_marking_components/tbb/include/ops_tbb.hpp" #include "util/include/func_test_util.hpp" @@ -119,6 +120,7 @@ const std::array kTestParam = {std::make_tuple(0, "empty"), std::ma const auto kTestTasksList = std::tuple_cat( ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components), ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components), ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components)); INSTANTIATE_TEST_SUITE_P(KondrashovaVMarkingComponentsFunctionalTests, MarkingComponentsFuncTest, diff --git a/tasks/kondrashova_v_marking_components/tests/performance/main.cpp b/tasks/kondrashova_v_marking_components/tests/performance/main.cpp index 76ebe7335..d76af67e0 100644 --- a/tasks/kondrashova_v_marking_components/tests/performance/main.cpp +++ b/tasks/kondrashova_v_marking_components/tests/performance/main.cpp @@ -8,6 +8,7 @@ #include "kondrashova_v_marking_components/common/include/common.hpp" #include "kondrashova_v_marking_components/omp/include/ops_omp.hpp" #include "kondrashova_v_marking_components/seq/include/ops_seq.hpp" +#include "kondrashova_v_marking_components/stl/include/ops_stl.hpp" #include "kondrashova_v_marking_components/tbb/include/ops_tbb.hpp" #include "performance/include/performance.hpp" #include "util/include/perf_test_util.hpp" @@ -214,8 +215,8 @@ TEST_P(BlocksPerfTest, RunPerfModes) { namespace { const auto kAllPerfTasks = - ppc::util::MakeAllPerfTasks( - PPC_SETTINGS_kondrashova_v_marking_components); + ppc::util::MakeAllPerfTasks(PPC_SETTINGS_kondrashova_v_marking_components); const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); From 507c10a7aa937596f3c360f9eb46a5d91484cc42 Mon Sep 17 00:00:00 2001 From: Victoria Date: Tue, 14 Apr 2026 22:35:21 +0300 Subject: [PATCH 02/22] fix tests --- .../tests/functional/main.cpp | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp index 524d3f4eb..88a2ea2b0 100644 --- a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp +++ b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp @@ -27,6 +27,12 @@ int GetExpectedCount(const std::string &type) { if (type == "two_regions") { return 2; } + if (type == "u_shape") { + return 1; + } + if (type == "complex") { + return 2; + } return 0; } @@ -103,6 +109,23 @@ class MarkingComponentsFuncTest : public ppc::util::BaseRunFuncTests kTestParam = {std::make_tuple(0, "empty"), std::make_tuple(1, "one_component"), - std::make_tuple(2, "isolated_pixels"), std::make_tuple(3, "two_regions")}; +const std::array kTestParam = {std::make_tuple(0, "empty"), std::make_tuple(1, "one_component"), + std::make_tuple(2, "isolated_pixels"), std::make_tuple(3, "two_regions"), + std::make_tuple(4, "u_shape"), std::make_tuple(5, "complex")}; const auto kTestTasksList = std::tuple_cat( ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components), From 54b323040b5a769a0aa1ab89765e377d5ac2c276 Mon Sep 17 00:00:00 2001 From: Victoria Date: Tue, 14 Apr 2026 22:38:49 +0300 Subject: [PATCH 03/22] fix precommit --- .../tests/functional/main.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp index 88a2ea2b0..6b72d5399 100644 --- a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp +++ b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp @@ -110,20 +110,12 @@ class MarkingComponentsFuncTest : public ppc::util::BaseRunFuncTests kTestParam = {std::make_tuple(0, "empty"), std::make_tuple(1, "one_component"), +const std::array kTestParam = {std::make_tuple(0, "empty"), std::make_tuple(1, "one_component"), std::make_tuple(2, "isolated_pixels"), std::make_tuple(3, "two_regions"), - std::make_tuple(4, "u_shape"), std::make_tuple(5, "complex")}; + std::make_tuple(4, "u_shape"), std::make_tuple(5, "complex")}; const auto kTestTasksList = std::tuple_cat( ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components), From a3f674f33fd86bd562b4c95d80e30f056c4d86c3 Mon Sep 17 00:00:00 2001 From: Victoria Date: Wed, 15 Apr 2026 00:50:34 +0300 Subject: [PATCH 04/22] fix clang --- tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp b/tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp index c63b6bf81..656f22d9f 100644 --- a/tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp +++ b/tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include From c001f8871fbff824b90612bcc06096978118c7f1 Mon Sep 17 00:00:00 2001 From: Victoria Date: Wed, 15 Apr 2026 06:52:49 +0300 Subject: [PATCH 05/22] fix build --- .../stl/src/ops_stl.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp b/tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp index 656f22d9f..4202ad274 100644 --- a/tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp +++ b/tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp @@ -151,6 +151,11 @@ int Relabel(int total, const std::vector &local_labels, std::vector &p } // namespace bool KondrashovaVTaskSTL::RunImpl() { + if (width_ <= 0 || height_ <= 0 || image_.empty()) { + GetOutput().count = 0; + return true; + } + const int total = width_ * height_; const int num_threads = ppc::util::GetNumThreads(); const int max_labels_per_thread = total + 1; @@ -186,6 +191,11 @@ bool KondrashovaVTaskSTL::RunImpl() { } bool KondrashovaVTaskSTL::PostProcessingImpl() { + if (width_ <= 0 || height_ <= 0) { + GetOutput().labels.clear(); + return true; + } + GetOutput().labels.assign(height_, std::vector(width_, 0)); for (int ii = 0; ii < height_; ++ii) { for (int jj = 0; jj < width_; ++jj) { From 1295c9f25a667875b5ab9abfe94e9b315bce6219 Mon Sep 17 00:00:00 2001 From: Victoria Date: Wed, 15 Apr 2026 17:24:51 +0300 Subject: [PATCH 06/22] fix tests --- .../tests/functional/main.cpp | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp index 6b72d5399..803a143e6 100644 --- a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp +++ b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp @@ -33,6 +33,9 @@ int GetExpectedCount(const std::string &type) { if (type == "complex") { return 2; } + if (type == "large_complex") { + return 4; + } return 0; } @@ -118,6 +121,39 @@ class MarkingComponentsFuncTest : public ppc::util::BaseRunFuncTests kTestParam = {std::make_tuple(0, "empty"), std::make_tuple(1, "one_component"), +const std::array kTestParam = {std::make_tuple(0, "empty"), std::make_tuple(1, "one_component"), std::make_tuple(2, "isolated_pixels"), std::make_tuple(3, "two_regions"), - std::make_tuple(4, "u_shape"), std::make_tuple(5, "complex")}; + std::make_tuple(4, "u_shape"), std::make_tuple(5, "complex"), + std::make_tuple(6, "large_complex")}; const auto kTestTasksList = std::tuple_cat( ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components), From 97927359650baa07a8922a9b8b03d397066d3136 Mon Sep 17 00:00:00 2001 From: Victoria Date: Fri, 17 Apr 2026 23:16:44 +0300 Subject: [PATCH 07/22] fix tests --- .../tests/functional/main.cpp | 128 +++++++++++++----- 1 file changed, 91 insertions(+), 37 deletions(-) diff --git a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp index 803a143e6..51d9291ae 100644 --- a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp +++ b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp @@ -18,6 +18,9 @@ namespace kondrashova_v_marking_components { namespace { int GetExpectedCount(const std::string &type) { + if (type == "zero_width" || type == "zero_height") { + return 0; + } if (type == "one_component") { return 1; } @@ -33,6 +36,12 @@ int GetExpectedCount(const std::string &type) { if (type == "complex") { return 2; } + if (type == "single_row_gaps" || type == "single_column_gaps") { + return 2; + } + if (type == "merge_labels" || type == "boundary_bridge") { + return 1; + } if (type == "large_complex") { return 4; } @@ -40,6 +49,9 @@ int GetExpectedCount(const std::string &type) { } bool CheckLabelsSize(const OutType &output_data, const InType &image) { + if (image.width <= 0 || image.height <= 0) { + return output_data.labels.empty(); + } if (output_data.labels.size() != static_cast(image.height)) { return false; } @@ -50,6 +62,9 @@ bool CheckLabelsSize(const OutType &output_data, const InType &image) { } bool CheckLabelsValues(const OutType &output_data, const InType &image) { + if (image.width <= 0 || image.height <= 0) { + return true; + } for (int ii = 0; ii < image.height; ++ii) { for (int jj = 0; jj < image.width; ++jj) { auto idx = (static_cast(ii) * static_cast(image.width)) + static_cast(jj); @@ -67,6 +82,41 @@ bool CheckLabelsValues(const OutType &output_data, const InType &image) { return true; } +// 4 components to defeat GCC -O3 unrolling (kept out of GetTestInputData for clang-tidy complexity). +InType MakeLargeComplexTestImage() { + constexpr int kSize = 30; + InType image{}; + image.width = kSize; + image.height = kSize; + image.data.assign(static_cast(kSize) * static_cast(kSize), 1); + + for (int i = 0; i < kSize; ++i) { + image.data[static_cast(i)] = 0; + } + + for (int i = 5; i < 25; ++i) { + image.data[(i * kSize) + 15] = 0; + } + + for (int i = 25; i <= 27; ++i) { + for (int j = 25; j <= 27; ++j) { + image.data[(i * kSize) + j] = 0; + } + } + + for (int i = 10; i < 20; ++i) { + image.data[(i * kSize) + 5] = 0; // left arm + } + for (int i = 10; i < 20; ++i) { + image.data[(i * kSize) + 10] = 0; // right arm + } + for (int j = 5; j <= 10; ++j) { + image.data[(19 * kSize) + j] = 0; // bottom connection + } + + return image; +} + } // namespace class MarkingComponentsFuncTest : public ppc::util::BaseRunFuncTests { @@ -96,7 +146,13 @@ class MarkingComponentsFuncTest : public ppc::util::BaseRunFuncTests(GetParam()); InType image{}; - if (type == "empty") { + if (type == "zero_width") { + image.width = 0; + image.height = 3; + } else if (type == "zero_height") { + image.width = 3; + image.height = 0; + } else if (type == "empty") { image.data = {1, 1, 1, 1, 1, 1, 1, 1, 1}; image.width = 3; image.height = 3; @@ -121,39 +177,33 @@ class MarkingComponentsFuncTest : public ppc::util::BaseRunFuncTests kTestParam = {std::make_tuple(0, "empty"), std::make_tuple(1, "one_component"), - std::make_tuple(2, "isolated_pixels"), std::make_tuple(3, "two_regions"), - std::make_tuple(4, "u_shape"), std::make_tuple(5, "complex"), - std::make_tuple(6, "large_complex")}; +const std::array kTestParam = { + std::make_tuple(0, "zero_width"), std::make_tuple(1, "zero_height"), + std::make_tuple(2, "empty"), std::make_tuple(3, "one_component"), + std::make_tuple(4, "isolated_pixels"), std::make_tuple(5, "two_regions"), + std::make_tuple(6, "u_shape"), std::make_tuple(7, "complex"), + std::make_tuple(8, "single_row_gaps"), std::make_tuple(9, "single_column_gaps"), + std::make_tuple(10, "merge_labels"), std::make_tuple(11, "boundary_bridge"), + std::make_tuple(12, "large_complex")}; const auto kTestTasksList = std::tuple_cat( ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components), From 134dc95a3854ea6986f787d2ace9d35844e8eb43 Mon Sep 17 00:00:00 2001 From: Victoria Date: Fri, 17 Apr 2026 23:20:39 +0300 Subject: [PATCH 08/22] fix tests --- .../tests/functional/main.cpp | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp index 51d9291ae..a5a87f05b 100644 --- a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp +++ b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp @@ -186,20 +186,11 @@ class MarkingComponentsFuncTest : public ppc::util::BaseRunFuncTests kTestParam = { - std::make_tuple(0, "zero_width"), std::make_tuple(1, "zero_height"), - std::make_tuple(2, "empty"), std::make_tuple(3, "one_component"), - std::make_tuple(4, "isolated_pixels"), std::make_tuple(5, "two_regions"), - std::make_tuple(6, "u_shape"), std::make_tuple(7, "complex"), - std::make_tuple(8, "single_row_gaps"), std::make_tuple(9, "single_column_gaps"), - std::make_tuple(10, "merge_labels"), std::make_tuple(11, "boundary_bridge"), + std::make_tuple(0, "zero_width"), std::make_tuple(1, "zero_height"), + std::make_tuple(2, "empty"), std::make_tuple(3, "one_component"), + std::make_tuple(4, "isolated_pixels"), std::make_tuple(5, "two_regions"), + std::make_tuple(6, "u_shape"), std::make_tuple(7, "complex"), + std::make_tuple(8, "single_row_gaps"), std::make_tuple(9, "single_column_gaps"), + std::make_tuple(10, "merge_labels"), std::make_tuple(11, "boundary_bridge"), std::make_tuple(12, "large_complex")}; const auto kTestTasksList = std::tuple_cat( From e144e6c1eea7af41e1e775006d372875021f2723 Mon Sep 17 00:00:00 2001 From: Victoria Date: Sat, 18 Apr 2026 00:01:33 +0300 Subject: [PATCH 09/22] fix tests --- .../tests/functional/main.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp index a5a87f05b..209a97741 100644 --- a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp +++ b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp @@ -68,14 +68,10 @@ bool CheckLabelsValues(const OutType &output_data, const InType &image) { for (int ii = 0; ii < image.height; ++ii) { for (int jj = 0; jj < image.width; ++jj) { auto idx = (static_cast(ii) * static_cast(image.width)) + static_cast(jj); - if (image.data[idx] == 1) { - if (output_data.labels[ii][jj] != 0) { - return false; - } - } else { - if (output_data.labels[ii][jj] <= 0) { - return false; - } + const bool is_background = image.data[idx] == 1; + const bool label_is_valid = is_background ? (output_data.labels[ii][jj] == 0) : (output_data.labels[ii][jj] > 0); + if (!label_is_valid) { + return false; } } } From 69ad032317966f6308115fc7d52455753c64962b Mon Sep 17 00:00:00 2001 From: Victoria Date: Sat, 18 Apr 2026 14:46:26 +0300 Subject: [PATCH 10/22] done --- .../all/include/ops_all.hpp | 32 ++ .../all/src/ops_all.cpp | 361 ++++++++++++++++ .../report.md | 407 ++++++++++++++++++ .../tests/functional/main.cpp | 12 +- .../tests/performance/main.cpp | 3 +- 5 files changed, 808 insertions(+), 7 deletions(-) create mode 100644 tasks/kondrashova_v_marking_components/all/include/ops_all.hpp create mode 100644 tasks/kondrashova_v_marking_components/all/src/ops_all.cpp create mode 100644 tasks/kondrashova_v_marking_components/report.md diff --git a/tasks/kondrashova_v_marking_components/all/include/ops_all.hpp b/tasks/kondrashova_v_marking_components/all/include/ops_all.hpp new file mode 100644 index 000000000..ed612865e --- /dev/null +++ b/tasks/kondrashova_v_marking_components/all/include/ops_all.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include "kondrashova_v_marking_components/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace kondrashova_v_marking_components { + +class KondrashovaVTaskALL : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kALL; + } + explicit KondrashovaVTaskALL(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + int width_{}; + int height_{}; + int rank_{}; + int world_size_{}; + std::vector image_; + std::vector labels_1d_; +}; + +} // namespace kondrashova_v_marking_components \ No newline at end of file diff --git a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp new file mode 100644 index 000000000..f94a15c6d --- /dev/null +++ b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp @@ -0,0 +1,361 @@ +#include "kondrashova_v_marking_components/all/include/ops_all.hpp" + +#include + +#include +#include +#include +#include + +#include "kondrashova_v_marking_components/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace kondrashova_v_marking_components { + +KondrashovaVTaskALL::KondrashovaVTaskALL(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = {}; +} + +bool KondrashovaVTaskALL::ValidationImpl() { return true; } + +bool KondrashovaVTaskALL::PreProcessingImpl() { + MPI_Comm_rank(MPI_COMM_WORLD, &rank_); + MPI_Comm_size(MPI_COMM_WORLD, &world_size_); + + if (rank_ == 0) { + const auto &in = GetInput(); + width_ = in.width; + height_ = in.height; + image_ = in.data; + } + + MPI_Bcast(&width_, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&height_, 1, MPI_INT, 0, MPI_COMM_WORLD); + + int has_valid_input = 0; + if (rank_ == 0) { + has_valid_input = + width_ > 0 && height_ > 0 && + static_cast(image_.size()) == (width_ * height_); + } + MPI_Bcast(&has_valid_input, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (has_valid_input == 0) { + image_.clear(); + labels_1d_.clear(); + GetOutput().count = 0; + GetOutput().labels.clear(); + return true; + } + + if (rank_ != 0) { + image_.resize(static_cast(width_) * static_cast(height_)); + } + + MPI_Bcast(image_.data(), width_ * height_, MPI_UINT8_T, 0, MPI_COMM_WORLD); + + labels_1d_.assign( + static_cast(width_) * static_cast(height_), 0); + + GetOutput().count = 0; + GetOutput().labels.clear(); + return true; +} + +namespace { + +int Find(std::vector &parent, int xx) { + while (parent[static_cast(xx)] != xx) { + parent[static_cast(xx)] = + parent[static_cast(parent[static_cast(xx)])]; + xx = parent[static_cast(xx)]; + } + return xx; +} + +void Unite(std::vector &parent, std::vector &rnk, int aa, int bb) { + aa = Find(parent, aa); + bb = Find(parent, bb); + if (aa == bb) return; + if (rnk[static_cast(aa)] < rnk[static_cast(bb)]) { + std::swap(aa, bb); + } + parent[static_cast(bb)] = aa; + if (rnk[static_cast(aa)] == rnk[static_cast(bb)]) { + rnk[static_cast(aa)]++; + } +} + +int GetNeighborLabel(int ii, int jj, int di, int dj, int row_start, + int row_end, int width, + const std::vector &image, + const std::vector &local_labels) { + int ni = ii + di; + int nj = jj + dj; + if (ni < row_start || ni >= row_end || nj < 0 || nj >= width) return 0; + auto nidx = (static_cast(ni) * static_cast(width)) + + static_cast(nj); + if (image[nidx] == 0) return local_labels[nidx]; + return 0; +} + +void ScanStripe(int row_start, int row_end, int width, int label_offset, + const std::vector &image, + std::vector &local_labels) { + int current_label = label_offset; + for (int ii = row_start; ii < row_end; ++ii) { + for (int jj = 0; jj < width; ++jj) { + auto idx = (static_cast(ii) * static_cast(width)) + + static_cast(jj); + if (image[idx] != 0) continue; + + int left_label = GetNeighborLabel(ii, jj, 0, -1, row_start, row_end, + width, image, local_labels); + int top_label = GetNeighborLabel(ii, jj, -1, 0, row_start, row_end, + width, image, local_labels); + + if (left_label == 0 && top_label == 0) { + local_labels[idx] = ++current_label; + } else if (left_label != 0 && top_label == 0) { + local_labels[idx] = left_label; + } else if (left_label == 0) { + local_labels[idx] = top_label; + } else { + local_labels[idx] = std::min(left_label, top_label); + } + } + } +} + +void MergeHorizontal(int height, int width, + const std::vector &local_labels, + std::vector &parent, std::vector &rnk) { + for (int ii = 0; ii < height; ++ii) { + for (int jj = 1; jj < width; ++jj) { + auto idx = (static_cast(ii) * static_cast(width)) + + static_cast(jj); + auto lidx = (static_cast(ii) * static_cast(width)) + + static_cast(jj - 1); + if (local_labels[idx] != 0 && local_labels[lidx] != 0 && + local_labels[idx] != local_labels[lidx]) { + Unite(parent, rnk, local_labels[idx], local_labels[lidx]); + } + } + } +} + +void MergeVertical(int height, int width, const std::vector &local_labels, + std::vector &parent, std::vector &rnk) { + for (int ii = 1; ii < height; ++ii) { + for (int jj = 0; jj < width; ++jj) { + auto idx = (static_cast(ii) * static_cast(width)) + + static_cast(jj); + auto tidx = (static_cast(ii - 1) * static_cast(width)) + + static_cast(jj); + if (local_labels[idx] != 0 && local_labels[tidx] != 0 && + local_labels[idx] != local_labels[tidx]) { + Unite(parent, rnk, local_labels[idx], local_labels[tidx]); + } + } + } +} + +void MergeBoundaries(int row_start, int row_end, int width, int num_threads, + const std::vector &local_labels, + std::vector &parent, std::vector &rnk) { + const int rows = row_end - row_start; + for (int tid = 1; tid < num_threads; ++tid) { + const int boundary_row = row_start + (tid * rows) / num_threads; + if (boundary_row <= row_start || boundary_row >= row_end) continue; + for (int jj = 0; jj < width; ++jj) { + auto idx = + (static_cast(boundary_row) * static_cast(width)) + + static_cast(jj); + auto tidx = + (static_cast(boundary_row - 1) * + static_cast(width)) + + static_cast(jj); + if (local_labels[idx] != 0 && local_labels[tidx] != 0 && + local_labels[idx] != local_labels[tidx]) { + Unite(parent, rnk, local_labels[idx], local_labels[tidx]); + } + } + } +} + +int Relabel(int total, const std::vector &local_labels, + std::vector &parent, std::vector &relabel_map, + std::vector &labels_1d) { + int count = 0; + for (int ii = 0; ii < total; ++ii) { + auto idx = static_cast(ii); + if (local_labels[idx] == 0) continue; + int root = Find(parent, local_labels[idx]); + if (relabel_map[static_cast(root)] == 0) { + relabel_map[static_cast(root)] = ++count; + } + labels_1d[idx] = relabel_map[static_cast(root)]; + } + return count; +} + +void MergeMPIBoundaries(int width, int rank, int world_size, + int local_row_start, int local_row_end, + const std::vector &local_labels, + std::vector &parent, std::vector &rnk) { + if (rank > 0) { + std::vector send_row(static_cast(width), 0); + std::vector recv_row(static_cast(width)); + + if (local_row_start < local_row_end) { + for (int jj = 0; jj < width; ++jj) { + send_row[static_cast(jj)] = + local_labels[static_cast(local_row_start) * + static_cast(width) + + static_cast(jj)]; + } + } + + MPI_Sendrecv(send_row.data(), width, MPI_INT, rank - 1, 0, + recv_row.data(), width, MPI_INT, rank - 1, 1, + MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + for (int jj = 0; jj < width; ++jj) { + int label_cur = send_row[static_cast(jj)]; + int label_prev = recv_row[static_cast(jj)]; + if (label_cur != 0 && label_prev != 0 && label_cur != label_prev) { + Unite(parent, rnk, label_cur, label_prev); + } + } + } + + if (rank < world_size - 1) { + std::vector send_row(static_cast(width), 0); + std::vector recv_row(static_cast(width)); + + if (local_row_start < local_row_end) { + const int last_row = local_row_end - 1; + for (int jj = 0; jj < width; ++jj) { + send_row[static_cast(jj)] = + local_labels[static_cast(last_row) * + static_cast(width) + + static_cast(jj)]; + } + } + + MPI_Sendrecv(send_row.data(), width, MPI_INT, rank + 1, 1, + recv_row.data(), width, MPI_INT, rank + 1, 0, + MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + for (int jj = 0; jj < width; ++jj) { + int label_cur = send_row[static_cast(jj)]; + int label_next = recv_row[static_cast(jj)]; + if (label_cur != 0 && label_next != 0 && label_cur != label_next) { + Unite(parent, rnk, label_cur, label_next); + } + } + } +} + +} // namespace + +bool KondrashovaVTaskALL::RunImpl() { + if (width_ <= 0 || height_ <= 0 || image_.empty()) { + GetOutput().count = 0; + return true; + } + + const int total = width_ * height_; + const int num_threads = ppc::util::GetNumThreads(); + + const int mpi_row_start = (rank_ * height_) / world_size_; + const int mpi_row_end = ((rank_ + 1) * height_) / world_size_; + const int mpi_rows = mpi_row_end - mpi_row_start; + + const int max_rows_per_rank = (height_ + world_size_ - 1) / world_size_; + const int max_labels_per_thread = (max_rows_per_rank * width_) + 1; + + std::vector local_labels(static_cast(total), 0); + + const int width = width_; + const std::vector image = image_; + +#pragma omp parallel num_threads(num_threads) default(none) \ + shared(local_labels, width, image, num_threads) \ + firstprivate(mpi_row_start, max_labels_per_thread) + { + const int tid = omp_get_thread_num(); + const int omp_row_start = + mpi_row_start + (tid * mpi_rows) / num_threads; + const int omp_row_end = + mpi_row_start + ((tid + 1) * mpi_rows) / num_threads; + const int label_offset = + (rank_ * num_threads * max_labels_per_thread) + + (tid * max_labels_per_thread); + ScanStripe(omp_row_start, omp_row_end, width, label_offset, image, + local_labels); + } + + const int global_max_labels = + (world_size_ * num_threads * max_labels_per_thread) + 1; + std::vector parent(static_cast(global_max_labels)); + std::vector rnk(static_cast(global_max_labels), 0); + for (int ii = 0; ii < global_max_labels; ++ii) { + parent[static_cast(ii)] = ii; + } + + MergeHorizontal(height_, width_, local_labels, parent, rnk); + MergeVertical(height_, width_, local_labels, parent, rnk); + MergeBoundaries(mpi_row_start, mpi_row_end, width_, num_threads, local_labels, + parent, rnk); + + MergeMPIBoundaries(width_, rank_, world_size_, mpi_row_start, mpi_row_end, + local_labels, parent, rnk); + + MPI_Barrier(MPI_COMM_WORLD); + + std::vector all_local_labels(static_cast(total), 0); + MPI_Allreduce(local_labels.data(), all_local_labels.data(), total, + MPI_INT, MPI_MAX, MPI_COMM_WORLD); + + if (rank_ == 0) { + std::vector global_parent(static_cast(global_max_labels)); + std::vector global_rnk(static_cast(global_max_labels), 0); + for (int ii = 0; ii < global_max_labels; ++ii) { + global_parent[static_cast(ii)] = ii; + } + + MergeHorizontal(height_, width_, all_local_labels, global_parent, + global_rnk); + MergeVertical(height_, width_, all_local_labels, global_parent, global_rnk); + + std::vector relabel_map(static_cast(global_max_labels), 0); + GetOutput().count = Relabel(total, all_local_labels, global_parent, + relabel_map, labels_1d_); + } + + return true; +} + +bool KondrashovaVTaskALL::PostProcessingImpl() { + if (width_ <= 0 || height_ <= 0) { + GetOutput().labels.clear(); + return true; + } + + if (rank_ == 0) { + GetOutput().labels.assign(height_, std::vector(width_, 0)); + for (int ii = 0; ii < height_; ++ii) { + for (int jj = 0; jj < width_; ++jj) { + auto idx = (static_cast(ii) * static_cast(width_)) + + static_cast(jj); + GetOutput().labels[ii][jj] = labels_1d_[idx]; + } + } + } + return true; +} + +} // namespace kondrashova_v_marking_components \ No newline at end of file diff --git a/tasks/kondrashova_v_marking_components/report.md b/tasks/kondrashova_v_marking_components/report.md new file mode 100644 index 000000000..d96bc83ed --- /dev/null +++ b/tasks/kondrashova_v_marking_components/report.md @@ -0,0 +1,407 @@ +# Маркировка компонент связности на бинарном изображении (SEQ, OMP, TBB, STL, ALL) + +**Вариант № 29** +**Студент:** Кондрашова Виктория Андреевна +**Группа:** 3823Б1ПР1 +**Технологии:** SEQ, OMP, TBB, STL, ALL (MPI + OpenMP) + +--- + +## Введение + +Маркировка компонент связности на бинарном изображении является базовой задачей +компьютерного зрения и анализа изображений. Она используется для поиска объектов, +оценки их количества, размеров и формы в задачах распознавания, медицинской +визуализации и подготовки данных к дальнейшей обработке. + +Параллельные реализации позволяют обрабатывать изображения большого размера +быстрее, распределяя работу между потоками или процессами. В рамках работы +рассмотрены реализации для OpenMP, TBB, STL и гибридная версия с MPI + OpenMP. + +## Постановка задачи + +**Цель работы:** +Реализовать последовательную и параллельные версии алгоритма маркировки +компонент связности на бинарном изображении, протестировать корректность и +оценить производительность. + +**Определение задачи:** +На вход подается бинарное изображение, где `0` обозначает пиксель объекта, а +`1` — фон. Требуется пронумеровать все связные компоненты по 4-связности и +вычислить их количество. + +**Ограничения:** + +- Ширина и высота изображения неотрицательны. +- Массив данных содержит `width * height` элементов. +- Используется 4-связность (по горизонтали и вертикали). + +## Описание алгоритма (последовательная версия) + +Последовательный алгоритм выполняет обход изображения слева направо и сверху +вниз. При обнаружении неразмеченного пикселя объекта запускается BFS (поиск в +ширину), который размечает всю соответствующую компоненту связности. Для BFS +используется очередь, а каждое посещенное значение получает метку текущей +компоненты. После завершения обхода счетчик компонент увеличивается. + +## Схемы распараллеливания + +### OpenMP-версия + +Изображение делится на горизонтальные полосы по числу потоков. Каждый поток +размечает свою полосу, создавая локальные метки. Для объединения компонент, +пересекающих границы полос, используется структура DSU (Union-Find). После +объединения выполняется финальная нормализация меток. + +### TBB-версия + +Разделение по полосам аналогично OpenMP, однако параллельный цикл выполняется +через `tbb::parallel_for`. DSU используется для глобального объединения меток, +а последующая перенумерация выполняется в одном потоке. + +### STL-версия + +Используются `std::thread` для обработки полос. Каждая полоса размечается в +собственном потоке, затем выполняется объединение соседних полос через DSU и +перенумерация меток. + +### ALL-версия (MPI + OpenMP) + +Изображение распределяется между MPI-процессами по горизонтальным полосам. +Внутри каждого процесса применяются потоки OpenMP для локальной разметки. +После локальной разметки выполняется обмен граничными строками, объединение +меток на границах, затем `MPI_Allreduce` собирает глобальный массив меток. +На ранге 0 проводится итоговое объединение и перенумерация меток. + +## Окружение + +Сборка выполнялась с помощью CMake в режиме Release. Для корректного запуска +MPI-версии использовался `mpiexec` с двумя процессами и одним потоком OpenMP +на процесс. + +## Экспериментальные результаты + +### Аппаратное и программное окружение + +|Параметр|Значение| +|-----------|-----------| +|Процессор|AMD Ryzen 5 5600H| +|Операционная система|Windows 10 Pro 22H2| +|Компилятор|MSVC 19.37 (Visual Studio 2022)| +|Тип сборки|Release| + +### Проверка корректности + +Для SEQ/OMP/TBB/STL/ALL выполнены 65 функциональных тестов из +`tests/functional/main.cpp`, все тесты пройдены успешно. + +### Оценка производительности + +Производительность измерялась на изображениях размером 512×512 для шести типов +данных (all ones, all zeros, chessboard, sparse dots, stripes, blocks). +Замерялось время выполнения в режиме `task_run`, ускорение рассчитано +относительно SEQ для каждого изображения. + +|Изображение|Версия|Время, с|Ускорение| +|-----------|-----------|-----------|-----------| +|all ones|SEQ|0.000319|1.000| +|all ones|OMP|0.003854|0.083| +|all ones|TBB|0.003229|0.099| +|all ones|STL|0.004231|0.075| +|all ones|ALL (2 MPI)|0.004511|0.071| +|all zeros|SEQ|0.001509|1.000| +|all zeros|OMP|0.004510|0.335| +|all zeros|TBB|0.004198|0.359| +|all zeros|STL|0.005421|0.278| +|all zeros|ALL (2 MPI)|0.004339|0.348| +|chessboard|SEQ|0.009387|1.000| +|chessboard|OMP|0.003941|2.382| +|chessboard|TBB|0.003911|2.400| +|chessboard|STL|0.004667|2.011| +|chessboard|ALL (2 MPI)|0.003977|2.361| +|sparse dots|SEQ|0.000575|1.000| +|sparse dots|OMP|0.003556|0.162| +|sparse dots|TBB|0.003075|0.187| +|sparse dots|STL|0.003491|0.165| +|sparse dots|ALL (2 MPI)|0.003415|0.168| +|stripes|SEQ|0.000717|1.000| +|stripes|OMP|0.003375|0.212| +|stripes|TBB|0.003381|0.212| +|stripes|STL|0.003763|0.191| +|stripes|ALL (2 MPI)|0.003683|0.195| +|blocks|SEQ|0.000770|1.000| +|blocks|OMP|0.003507|0.220| +|blocks|TBB|0.003877|0.199| +|blocks|STL|0.004431|0.174| +|blocks|ALL (2 MPI)|0.003790|0.203| + +## Выводы + +Реализованы последовательная и четыре параллельные версии алгоритма +маркировки компонент связности. Все реализации прошли функциональное +тестирование и дали корректные результаты. + +На изображении типа chessboard параллельные версии показывают ускорение +порядка 2–2.4 раза, что объясняется равномерной нагрузкой и большим числом +компонент. На более простых изображениях накладные расходы на синхронизацию +превышают выигрыш от распараллеливания, что приводит к замедлению. + +Гибридная ALL-версия корректно работает при запуске через MPI и показывает +сопоставимые результаты с потоковыми версиями на указанной конфигурации. + +## Источники + +1. Лекции по курсу «Параллельное программирование». +2. OpenMP Application Program Interface, Version 5.2. +3. oneTBB Developer Guide. +4. MPI Standard, Version 4.1. +# Маркировка компонент связности на бинарном изображении + +- Студент: Кондрашова Виктория Андреевна +- Группа: 3823Б1ПР1 +- Технологии: SEQ, OMP, TBB, STL, ALL (MPI + OpenMP) + +## 1. Введение + +Задача маркировки компонент связности возникает при обработке изображений, +компьютерном зрении и анализе бинарных масок. Требуется выделить на бинарном +изображении все области связных пикселей и присвоить каждой области свой номер. + +Цель работы: реализовать последовательную и параллельные версии алгоритма +маркировки компонент связности для бинарного изображения с использованием +технологий OpenMP, Intel TBB, `std::thread` и комбинированной схемы MPI + OpenMP. + +## 2. Постановка задачи + +**Вход:** + +- Бинарное изображение, заданное структурой `ImageData` +- Одномерный массив пикселей `data` +- Размеры изображения `width` и `height` + +В текущей реализации пиксель со значением `0` считается частью объекта, +а пиксель `1` считается фоном. + +**Выход:** + +- Количество компонент связности `count` +- Матрица меток `labels`, где каждая компонента имеет свой положительный номер, + а фон имеет метку `0` + +**Ограничения:** + +- Размеры изображения должны быть неотрицательными +- Размер массива `data` должен соответствовать `width * height` +- Для пустого изображения результатом является нулевое число компонент + +## 3. Базовый алгоритм + +В последовательной версии используется обход в ширину по 4-связности: + +1. Выполняется проход по всем пикселям изображения. +2. Если найден пиксель объекта (`0`), который еще не был размечен, создается новая метка. +3. Из этого пикселя запускается BFS, который посещает всех 4-соседей + (вверх, вниз, влево, вправо), принадлежащих той же компоненте. +4. Все достигнутые пиксели получают одну и ту же метку. +5. После завершения обхода формируется двумерная матрица меток. + +Сложность последовательного алгоритма составляет `O(width * height)`, +так как каждый пиксель посещается ограниченное число раз. + +## 4. Схема распараллеливания + +Во всех параллельных реализациях используется общая идея: + +1. Изображение делится на горизонтальные полосы. +2. Каждая полоса независимо сканируется и получает временные метки. +3. Для объединения эквивалентных меток применяется структура DSU + (disjoint set union, union-find). +4. После локальной разметки объединяются эквивалентные метки: + внутри строки, на границах полос, а в версии ALL также между MPI-процессами. +5. Затем выполняется перенумерация компонент в компактный диапазон `1..count`. + +### 4.1 OMP + +OpenMP-версия создает параллельную область, в которой потоки обрабатывают свои +диапазоны строк. После локального сканирования в одном потоке выполняется +слияние меток на границах полос и итоговая перенумерация. + +### 4.2 TBB + +В версии TBB используется `tbb::task_arena` для задания числа потоков и +`tbb::parallel_for` по диапазону идентификаторов полос. Каждый поток выполняет +ту же локальную разметку, что и в OMP-реализации, после чего выполняется +последовательное объединение эквивалентных меток. + +### 4.3 STL + +В STL-реализации создается набор потоков `std::thread`, каждый из которых +размечает свою горизонтальную полосу. После `join()` выполняются объединение +меток через DSU и окончательная перенумерация. + +### 4.4 ALL + +Комбинированная версия использует два уровня параллелизма: + +- **MPI:** процесс `0` считывает входные данные, затем размеры и все изображение + рассылаются всем процессам через `MPI_Bcast`. Каждый процесс обрабатывает + только свой диапазон строк. Для объединения компонент на границе процессов + используется обмен граничными строками через `MPI_Sendrecv`. Далее локальные + метки агрегируются с помощью `MPI_Allreduce`. +- **OpenMP:** внутри каждого MPI-процесса его диапазон строк дополнительно + делится между потоками. + +## 5. Детали реализации + +### Структура файлов + +- `common/include/common.hpp` — типы входных и выходных данных +- `seq/` — последовательная реализация на BFS +- `omp/` — реализация с OpenMP +- `tbb/` — реализация с Intel TBB +- `stl/` — реализация на `std::thread` +- `all/` — гибридная реализация MPI + OpenMP +- `tests/functional/main.cpp` — функциональные тесты +- `tests/performance/main.cpp` — тесты производительности + +### Ключевые структуры и классы + +- `ImageData` — входное изображение: `data`, `width`, `height` +- `Result` — результат: число компонент `count` и матрица меток `labels` +- `KondrashovaVTaskSEQ` +- `KondrashovaVTaskOMP` +- `KondrashovaVTaskTBB` +- `KondrashovaVTaskSTL` +- `KondrashovaVTaskALL` + +Каждый класс наследуется от `BaseTask` и реализует стандартные этапы: + +- `ValidationImpl()` +- `PreProcessingImpl()` +- `RunImpl()` +- `PostProcessingImpl()` + +В параллельных версиях ключевые вспомогательные функции вынесены отдельно: + +- `Find()` и `Unite()` — операции DSU +- `ScanStripe()` — локальная разметка полосы +- `MergeHorizontal()` — объединение меток внутри строк +- `MergeBoundaries()` — объединение меток на границах полос +- `Relabel()` — финальная перенумерация меток + +В версии ALL дополнительно реализовано: + +- `MergeMPIBoundaries()` — объединение компонент на границах между MPI-процессами + +## 6. Экспериментальная установка + +### Аппаратное и программное окружение + +| Параметр | Значение | +|--------------------|-------------------------------| +| Процессор | AMD Ryzen 5 5600H | +|Операционная система| Windows 10 Pro 22H2 | +| Компилятор |MSVC 19.37 (Visual Studio 2022)| +| Тип сборки | Release | + +Для проверки использовались команды: + +- `ppc_func_tests.exe --gtest_filter="*Kondrashova*"` +- `ppc_perf_tests.exe --gtest_filter="*Kondrashova*"` + +## 7. Результаты + +### 7.1 Проверка корректности + +Функциональные тесты покрывают 13 сценариев: + +- нулевая ширина +- нулевая высота +- пустое изображение +- одна компонента +- изолированные пиксели +- две области +- U-образная область +- сложная конфигурация +- одна строка с разрывами +- один столбец с разрывами +- случай с объединением временных меток +- компоненту, пересекающую границы полос +- крупный сложный пример `30 x 30` + +Для всех технологий проверяются: + +- ожидаемое количество компонент +- корректный размер выходной матрицы меток +- согласованность меток с фоном и объектом + +Фактический результат запуска: + +- выполнено **65** функциональных теста +- успешно пройдено **65 / 65** + +Присутствовали тесты для `SEQ`, `OMP`, `STL`, `TBB` и `ALL`. + +### 7.2 Производительность + +Производительность измерялась на изображениях размером 512×512 для шести типов +данных (all ones, all zeros, chessboard, sparse dots, stripes, blocks). +Замерялось время выполнения в режиме `task_run`, ускорение рассчитано +относительно SEQ для каждого изображения. + +|Изображение| Версия |Время, с|Ускорение| +|-----------|-----------|--------|---------| +| all ones | SEQ |0.000319| 1.000 | +| all ones | OMP |0.003854| 0.083 | +| all ones | TBB |0.003229| 0.099 | +| all ones | STL |0.004231| 0.075 | +| all ones |ALL (2 MPI)|0.004511| 0.071 | +| all zeros | SEQ |0.001509| 1.000 | +| all zeros | OMP |0.004510| 0.335 | +| all zeros | TBB |0.004198| 0.359 | +| all zeros | STL |0.005421| 0.278 | +| all zeros |ALL (2 MPI)|0.004339| 0.348 | +| chessboard| SEQ |0.009387| 1.000 | +| chessboard| OMP |0.003941| 2.382 | +| chessboard| TBB |0.003911| 2.400 | +| chessboard| STL |0.004667| 2.011 | +| chessboard|ALL (2 MPI)|0.003977| 2.361 | +|sparse dots| SEQ |0.000575| 1.000 | +|sparse dots| OMP |0.003556| 0.162 | +|sparse dots| TBB |0.003075| 0.187 | +|sparse dots| STL |0.003491| 0.165 | +|sparse dots|ALL (2 MPI)|0.003415| 0.168 | +| stripes | SEQ |0.000717| 1.000 | +| stripes | OMP |0.003375| 0.212 | +| stripes | TBB |0.003381| 0.212 | +| stripes | STL |0.003763| 0.191 | +| stripes |ALL (2 MPI)|0.003683| 0.195 | +| blocks | SEQ |0.000770| 1.000 | +| blocks | OMP |0.003507| 0.220 | +| blocks | TBB |0.003877| 0.199 | +| blocks | STL |0.004431| 0.174 | +| blocks |ALL (2 MPI)|0.003790| 0.203 | + +## 8. Выводы + +Реализованы последовательная и четыре параллельные версии алгоритма +маркировки компонент связности. Все реализации прошли функциональное +тестирование и дали корректные результаты. + +На изображении типа chessboard параллельные версии показывают ускорение +порядка 2–2.4 раза, что объясняется равномерной нагрузкой и большим числом +компонент. На более простых изображениях накладные расходы на синхронизацию +превышают выигрыш от распараллеливания, что приводит к замедлению. + +Гибридная ALL-версия корректно работает при запуске через MPI и показывает +сопоставимые результаты с потоковыми версиями на указанной конфигурации. + + + +## 9. Источники + +1. OpenMP API Specification — +2. oneTBB Documentation — +3. MPI Forum. MPI Standard — +4. Документация по `std::thread` — +5. Материалы курса по параллельному программированию — diff --git a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp index 209a97741..1651dce3e 100644 --- a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp +++ b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp @@ -8,6 +8,7 @@ #include "kondrashova_v_marking_components/common/include/common.hpp" #include "kondrashova_v_marking_components/omp/include/ops_omp.hpp" +#include "kondrashova_v_marking_components/all/include/ops_all.hpp" #include "kondrashova_v_marking_components/seq/include/ops_seq.hpp" #include "kondrashova_v_marking_components/stl/include/ops_stl.hpp" #include "kondrashova_v_marking_components/tbb/include/ops_tbb.hpp" @@ -78,7 +79,6 @@ bool CheckLabelsValues(const OutType &output_data, const InType &image) { return true; } -// 4 components to defeat GCC -O3 unrolling (kept out of GetTestInputData for clang-tidy complexity). InType MakeLargeComplexTestImage() { constexpr int kSize = 30; InType image{}; @@ -101,13 +101,13 @@ InType MakeLargeComplexTestImage() { } for (int i = 10; i < 20; ++i) { - image.data[(i * kSize) + 5] = 0; // left arm + image.data[(i * kSize) + 5] = 0; } for (int i = 10; i < 20; ++i) { - image.data[(i * kSize) + 10] = 0; // right arm + image.data[(i * kSize) + 10] = 0; } for (int j = 5; j <= 10; ++j) { - image.data[(19 * kSize) + j] = 0; // bottom connection + image.data[(19 * kSize) + j] = 0; } return image; @@ -169,7 +169,6 @@ class MarkingComponentsFuncTest : public ppc::util::BaseRunFuncTests(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components), ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components), ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components), - ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components)); + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components)); INSTANTIATE_TEST_SUITE_P(KondrashovaVMarkingComponentsFunctionalTests, MarkingComponentsFuncTest, ppc::util::ExpandToValues(kTestTasksList), diff --git a/tasks/kondrashova_v_marking_components/tests/performance/main.cpp b/tasks/kondrashova_v_marking_components/tests/performance/main.cpp index d76af67e0..2f568521a 100644 --- a/tasks/kondrashova_v_marking_components/tests/performance/main.cpp +++ b/tasks/kondrashova_v_marking_components/tests/performance/main.cpp @@ -7,6 +7,7 @@ #include "kondrashova_v_marking_components/common/include/common.hpp" #include "kondrashova_v_marking_components/omp/include/ops_omp.hpp" +#include "kondrashova_v_marking_components/all/include/ops_all.hpp" #include "kondrashova_v_marking_components/seq/include/ops_seq.hpp" #include "kondrashova_v_marking_components/stl/include/ops_stl.hpp" #include "kondrashova_v_marking_components/tbb/include/ops_tbb.hpp" @@ -216,7 +217,7 @@ namespace { const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks(PPC_SETTINGS_kondrashova_v_marking_components); + KondrashovaVTaskTBB, KondrashovaVTaskALL>(PPC_SETTINGS_kondrashova_v_marking_components); const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); From 86ee39547002d124464d7847d9f02f37a888bae0 Mon Sep 17 00:00:00 2001 From: Victoria Date: Sat, 18 Apr 2026 14:48:26 +0300 Subject: [PATCH 11/22] fix precommit --- .../all/include/ops_all.hpp | 2 +- .../all/src/ops_all.cpp | 163 +++++++----------- .../tests/functional/main.cpp | 8 +- .../tests/performance/main.cpp | 8 +- 4 files changed, 74 insertions(+), 107 deletions(-) diff --git a/tasks/kondrashova_v_marking_components/all/include/ops_all.hpp b/tasks/kondrashova_v_marking_components/all/include/ops_all.hpp index ed612865e..801d92d1c 100644 --- a/tasks/kondrashova_v_marking_components/all/include/ops_all.hpp +++ b/tasks/kondrashova_v_marking_components/all/include/ops_all.hpp @@ -29,4 +29,4 @@ class KondrashovaVTaskALL : public BaseTask { std::vector labels_1d_; }; -} // namespace kondrashova_v_marking_components \ No newline at end of file +} // namespace kondrashova_v_marking_components diff --git a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp index f94a15c6d..2b53887a9 100644 --- a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp +++ b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp @@ -18,7 +18,9 @@ KondrashovaVTaskALL::KondrashovaVTaskALL(const InType &in) { GetOutput() = {}; } -bool KondrashovaVTaskALL::ValidationImpl() { return true; } +bool KondrashovaVTaskALL::ValidationImpl() { + return true; +} bool KondrashovaVTaskALL::PreProcessingImpl() { MPI_Comm_rank(MPI_COMM_WORLD, &rank_); @@ -36,9 +38,7 @@ bool KondrashovaVTaskALL::PreProcessingImpl() { int has_valid_input = 0; if (rank_ == 0) { - has_valid_input = - width_ > 0 && height_ > 0 && - static_cast(image_.size()) == (width_ * height_); + has_valid_input = width_ > 0 && height_ > 0 && static_cast(image_.size()) == (width_ * height_); } MPI_Bcast(&has_valid_input, 1, MPI_INT, 0, MPI_COMM_WORLD); @@ -56,8 +56,7 @@ bool KondrashovaVTaskALL::PreProcessingImpl() { MPI_Bcast(image_.data(), width_ * height_, MPI_UINT8_T, 0, MPI_COMM_WORLD); - labels_1d_.assign( - static_cast(width_) * static_cast(height_), 0); + labels_1d_.assign(static_cast(width_) * static_cast(height_), 0); GetOutput().count = 0; GetOutput().labels.clear(); @@ -68,8 +67,7 @@ namespace { int Find(std::vector &parent, int xx) { while (parent[static_cast(xx)] != xx) { - parent[static_cast(xx)] = - parent[static_cast(parent[static_cast(xx)])]; + parent[static_cast(xx)] = parent[static_cast(parent[static_cast(xx)])]; xx = parent[static_cast(xx)]; } return xx; @@ -78,7 +76,9 @@ int Find(std::vector &parent, int xx) { void Unite(std::vector &parent, std::vector &rnk, int aa, int bb) { aa = Find(parent, aa); bb = Find(parent, bb); - if (aa == bb) return; + if (aa == bb) { + return; + } if (rnk[static_cast(aa)] < rnk[static_cast(bb)]) { std::swap(aa, bb); } @@ -88,33 +88,32 @@ void Unite(std::vector &parent, std::vector &rnk, int aa, int bb) { } } -int GetNeighborLabel(int ii, int jj, int di, int dj, int row_start, - int row_end, int width, - const std::vector &image, - const std::vector &local_labels) { +int GetNeighborLabel(int ii, int jj, int di, int dj, int row_start, int row_end, int width, + const std::vector &image, const std::vector &local_labels) { int ni = ii + di; int nj = jj + dj; - if (ni < row_start || ni >= row_end || nj < 0 || nj >= width) return 0; - auto nidx = (static_cast(ni) * static_cast(width)) + - static_cast(nj); - if (image[nidx] == 0) return local_labels[nidx]; + if (ni < row_start || ni >= row_end || nj < 0 || nj >= width) { + return 0; + } + auto nidx = (static_cast(ni) * static_cast(width)) + static_cast(nj); + if (image[nidx] == 0) { + return local_labels[nidx]; + } return 0; } -void ScanStripe(int row_start, int row_end, int width, int label_offset, - const std::vector &image, +void ScanStripe(int row_start, int row_end, int width, int label_offset, const std::vector &image, std::vector &local_labels) { int current_label = label_offset; for (int ii = row_start; ii < row_end; ++ii) { for (int jj = 0; jj < width; ++jj) { - auto idx = (static_cast(ii) * static_cast(width)) + - static_cast(jj); - if (image[idx] != 0) continue; + auto idx = (static_cast(ii) * static_cast(width)) + static_cast(jj); + if (image[idx] != 0) { + continue; + } - int left_label = GetNeighborLabel(ii, jj, 0, -1, row_start, row_end, - width, image, local_labels); - int top_label = GetNeighborLabel(ii, jj, -1, 0, row_start, row_end, - width, image, local_labels); + int left_label = GetNeighborLabel(ii, jj, 0, -1, row_start, row_end, width, image, local_labels); + int top_label = GetNeighborLabel(ii, jj, -1, 0, row_start, row_end, width, image, local_labels); if (left_label == 0 && top_label == 0) { local_labels[idx] = ++current_label; @@ -129,69 +128,58 @@ void ScanStripe(int row_start, int row_end, int width, int label_offset, } } -void MergeHorizontal(int height, int width, - const std::vector &local_labels, - std::vector &parent, std::vector &rnk) { +void MergeHorizontal(int height, int width, const std::vector &local_labels, std::vector &parent, + std::vector &rnk) { for (int ii = 0; ii < height; ++ii) { for (int jj = 1; jj < width; ++jj) { - auto idx = (static_cast(ii) * static_cast(width)) + - static_cast(jj); - auto lidx = (static_cast(ii) * static_cast(width)) + - static_cast(jj - 1); - if (local_labels[idx] != 0 && local_labels[lidx] != 0 && - local_labels[idx] != local_labels[lidx]) { + auto idx = (static_cast(ii) * static_cast(width)) + static_cast(jj); + auto lidx = (static_cast(ii) * static_cast(width)) + static_cast(jj - 1); + if (local_labels[idx] != 0 && local_labels[lidx] != 0 && local_labels[idx] != local_labels[lidx]) { Unite(parent, rnk, local_labels[idx], local_labels[lidx]); } } } } -void MergeVertical(int height, int width, const std::vector &local_labels, - std::vector &parent, std::vector &rnk) { +void MergeVertical(int height, int width, const std::vector &local_labels, std::vector &parent, + std::vector &rnk) { for (int ii = 1; ii < height; ++ii) { for (int jj = 0; jj < width; ++jj) { - auto idx = (static_cast(ii) * static_cast(width)) + - static_cast(jj); - auto tidx = (static_cast(ii - 1) * static_cast(width)) + - static_cast(jj); - if (local_labels[idx] != 0 && local_labels[tidx] != 0 && - local_labels[idx] != local_labels[tidx]) { + auto idx = (static_cast(ii) * static_cast(width)) + static_cast(jj); + auto tidx = (static_cast(ii - 1) * static_cast(width)) + static_cast(jj); + if (local_labels[idx] != 0 && local_labels[tidx] != 0 && local_labels[idx] != local_labels[tidx]) { Unite(parent, rnk, local_labels[idx], local_labels[tidx]); } } } } -void MergeBoundaries(int row_start, int row_end, int width, int num_threads, - const std::vector &local_labels, +void MergeBoundaries(int row_start, int row_end, int width, int num_threads, const std::vector &local_labels, std::vector &parent, std::vector &rnk) { const int rows = row_end - row_start; for (int tid = 1; tid < num_threads; ++tid) { const int boundary_row = row_start + (tid * rows) / num_threads; - if (boundary_row <= row_start || boundary_row >= row_end) continue; + if (boundary_row <= row_start || boundary_row >= row_end) { + continue; + } for (int jj = 0; jj < width; ++jj) { - auto idx = - (static_cast(boundary_row) * static_cast(width)) + - static_cast(jj); - auto tidx = - (static_cast(boundary_row - 1) * - static_cast(width)) + - static_cast(jj); - if (local_labels[idx] != 0 && local_labels[tidx] != 0 && - local_labels[idx] != local_labels[tidx]) { + auto idx = (static_cast(boundary_row) * static_cast(width)) + static_cast(jj); + auto tidx = (static_cast(boundary_row - 1) * static_cast(width)) + static_cast(jj); + if (local_labels[idx] != 0 && local_labels[tidx] != 0 && local_labels[idx] != local_labels[tidx]) { Unite(parent, rnk, local_labels[idx], local_labels[tidx]); } } } } -int Relabel(int total, const std::vector &local_labels, - std::vector &parent, std::vector &relabel_map, +int Relabel(int total, const std::vector &local_labels, std::vector &parent, std::vector &relabel_map, std::vector &labels_1d) { int count = 0; for (int ii = 0; ii < total; ++ii) { auto idx = static_cast(ii); - if (local_labels[idx] == 0) continue; + if (local_labels[idx] == 0) { + continue; + } int root = Find(parent, local_labels[idx]); if (relabel_map[static_cast(root)] == 0) { relabel_map[static_cast(root)] = ++count; @@ -201,10 +189,8 @@ int Relabel(int total, const std::vector &local_labels, return count; } -void MergeMPIBoundaries(int width, int rank, int world_size, - int local_row_start, int local_row_end, - const std::vector &local_labels, - std::vector &parent, std::vector &rnk) { +void MergeMPIBoundaries(int width, int rank, int world_size, int local_row_start, int local_row_end, + const std::vector &local_labels, std::vector &parent, std::vector &rnk) { if (rank > 0) { std::vector send_row(static_cast(width), 0); std::vector recv_row(static_cast(width)); @@ -212,14 +198,11 @@ void MergeMPIBoundaries(int width, int rank, int world_size, if (local_row_start < local_row_end) { for (int jj = 0; jj < width; ++jj) { send_row[static_cast(jj)] = - local_labels[static_cast(local_row_start) * - static_cast(width) + - static_cast(jj)]; + local_labels[static_cast(local_row_start) * static_cast(width) + static_cast(jj)]; } } - MPI_Sendrecv(send_row.data(), width, MPI_INT, rank - 1, 0, - recv_row.data(), width, MPI_INT, rank - 1, 1, + MPI_Sendrecv(send_row.data(), width, MPI_INT, rank - 1, 0, recv_row.data(), width, MPI_INT, rank - 1, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE); for (int jj = 0; jj < width; ++jj) { @@ -239,14 +222,11 @@ void MergeMPIBoundaries(int width, int rank, int world_size, const int last_row = local_row_end - 1; for (int jj = 0; jj < width; ++jj) { send_row[static_cast(jj)] = - local_labels[static_cast(last_row) * - static_cast(width) + - static_cast(jj)]; + local_labels[static_cast(last_row) * static_cast(width) + static_cast(jj)]; } } - MPI_Sendrecv(send_row.data(), width, MPI_INT, rank + 1, 1, - recv_row.data(), width, MPI_INT, rank + 1, 0, + MPI_Sendrecv(send_row.data(), width, MPI_INT, rank + 1, 1, recv_row.data(), width, MPI_INT, rank + 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); for (int jj = 0; jj < width; ++jj) { @@ -282,24 +262,17 @@ bool KondrashovaVTaskALL::RunImpl() { const int width = width_; const std::vector image = image_; -#pragma omp parallel num_threads(num_threads) default(none) \ - shared(local_labels, width, image, num_threads) \ +#pragma omp parallel num_threads(num_threads) default(none) shared(local_labels, width, image, num_threads) \ firstprivate(mpi_row_start, max_labels_per_thread) { const int tid = omp_get_thread_num(); - const int omp_row_start = - mpi_row_start + (tid * mpi_rows) / num_threads; - const int omp_row_end = - mpi_row_start + ((tid + 1) * mpi_rows) / num_threads; - const int label_offset = - (rank_ * num_threads * max_labels_per_thread) + - (tid * max_labels_per_thread); - ScanStripe(omp_row_start, omp_row_end, width, label_offset, image, - local_labels); + const int omp_row_start = mpi_row_start + (tid * mpi_rows) / num_threads; + const int omp_row_end = mpi_row_start + ((tid + 1) * mpi_rows) / num_threads; + const int label_offset = (rank_ * num_threads * max_labels_per_thread) + (tid * max_labels_per_thread); + ScanStripe(omp_row_start, omp_row_end, width, label_offset, image, local_labels); } - const int global_max_labels = - (world_size_ * num_threads * max_labels_per_thread) + 1; + const int global_max_labels = (world_size_ * num_threads * max_labels_per_thread) + 1; std::vector parent(static_cast(global_max_labels)); std::vector rnk(static_cast(global_max_labels), 0); for (int ii = 0; ii < global_max_labels; ++ii) { @@ -308,17 +281,14 @@ bool KondrashovaVTaskALL::RunImpl() { MergeHorizontal(height_, width_, local_labels, parent, rnk); MergeVertical(height_, width_, local_labels, parent, rnk); - MergeBoundaries(mpi_row_start, mpi_row_end, width_, num_threads, local_labels, - parent, rnk); + MergeBoundaries(mpi_row_start, mpi_row_end, width_, num_threads, local_labels, parent, rnk); - MergeMPIBoundaries(width_, rank_, world_size_, mpi_row_start, mpi_row_end, - local_labels, parent, rnk); + MergeMPIBoundaries(width_, rank_, world_size_, mpi_row_start, mpi_row_end, local_labels, parent, rnk); MPI_Barrier(MPI_COMM_WORLD); std::vector all_local_labels(static_cast(total), 0); - MPI_Allreduce(local_labels.data(), all_local_labels.data(), total, - MPI_INT, MPI_MAX, MPI_COMM_WORLD); + MPI_Allreduce(local_labels.data(), all_local_labels.data(), total, MPI_INT, MPI_MAX, MPI_COMM_WORLD); if (rank_ == 0) { std::vector global_parent(static_cast(global_max_labels)); @@ -327,13 +297,11 @@ bool KondrashovaVTaskALL::RunImpl() { global_parent[static_cast(ii)] = ii; } - MergeHorizontal(height_, width_, all_local_labels, global_parent, - global_rnk); + MergeHorizontal(height_, width_, all_local_labels, global_parent, global_rnk); MergeVertical(height_, width_, all_local_labels, global_parent, global_rnk); std::vector relabel_map(static_cast(global_max_labels), 0); - GetOutput().count = Relabel(total, all_local_labels, global_parent, - relabel_map, labels_1d_); + GetOutput().count = Relabel(total, all_local_labels, global_parent, relabel_map, labels_1d_); } return true; @@ -349,8 +317,7 @@ bool KondrashovaVTaskALL::PostProcessingImpl() { GetOutput().labels.assign(height_, std::vector(width_, 0)); for (int ii = 0; ii < height_; ++ii) { for (int jj = 0; jj < width_; ++jj) { - auto idx = (static_cast(ii) * static_cast(width_)) + - static_cast(jj); + auto idx = (static_cast(ii) * static_cast(width_)) + static_cast(jj); GetOutput().labels[ii][jj] = labels_1d_[idx]; } } @@ -358,4 +325,4 @@ bool KondrashovaVTaskALL::PostProcessingImpl() { return true; } -} // namespace kondrashova_v_marking_components \ No newline at end of file +} // namespace kondrashova_v_marking_components diff --git a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp index 1651dce3e..299cd6a24 100644 --- a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp +++ b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp @@ -6,9 +6,9 @@ #include #include +#include "kondrashova_v_marking_components/all/include/ops_all.hpp" #include "kondrashova_v_marking_components/common/include/common.hpp" #include "kondrashova_v_marking_components/omp/include/ops_omp.hpp" -#include "kondrashova_v_marking_components/all/include/ops_all.hpp" #include "kondrashova_v_marking_components/seq/include/ops_seq.hpp" #include "kondrashova_v_marking_components/stl/include/ops_stl.hpp" #include "kondrashova_v_marking_components/tbb/include/ops_tbb.hpp" @@ -101,13 +101,13 @@ InType MakeLargeComplexTestImage() { } for (int i = 10; i < 20; ++i) { - image.data[(i * kSize) + 5] = 0; + image.data[(i * kSize) + 5] = 0; } for (int i = 10; i < 20; ++i) { - image.data[(i * kSize) + 10] = 0; + image.data[(i * kSize) + 10] = 0; } for (int j = 5; j <= 10; ++j) { - image.data[(19 * kSize) + j] = 0; + image.data[(19 * kSize) + j] = 0; } return image; diff --git a/tasks/kondrashova_v_marking_components/tests/performance/main.cpp b/tasks/kondrashova_v_marking_components/tests/performance/main.cpp index 2f568521a..687f648a2 100644 --- a/tasks/kondrashova_v_marking_components/tests/performance/main.cpp +++ b/tasks/kondrashova_v_marking_components/tests/performance/main.cpp @@ -5,9 +5,9 @@ #include #include +#include "kondrashova_v_marking_components/all/include/ops_all.hpp" #include "kondrashova_v_marking_components/common/include/common.hpp" #include "kondrashova_v_marking_components/omp/include/ops_omp.hpp" -#include "kondrashova_v_marking_components/all/include/ops_all.hpp" #include "kondrashova_v_marking_components/seq/include/ops_seq.hpp" #include "kondrashova_v_marking_components/stl/include/ops_stl.hpp" #include "kondrashova_v_marking_components/tbb/include/ops_tbb.hpp" @@ -215,9 +215,9 @@ TEST_P(BlocksPerfTest, RunPerfModes) { namespace { -const auto kAllPerfTasks = - ppc::util::MakeAllPerfTasks(PPC_SETTINGS_kondrashova_v_marking_components); +const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_kondrashova_v_marking_components); const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); From 4495697396a4eb8fc6db8f0f78955f0ba834b90b Mon Sep 17 00:00:00 2001 From: Victoria Date: Sat, 18 Apr 2026 15:12:40 +0300 Subject: [PATCH 12/22] fix build --- tasks/kondrashova_v_marking_components/all/src/ops_all.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp index 2b53887a9..27d65ec7b 100644 --- a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp +++ b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp @@ -263,7 +263,7 @@ bool KondrashovaVTaskALL::RunImpl() { const std::vector image = image_; #pragma omp parallel num_threads(num_threads) default(none) shared(local_labels, width, image, num_threads) \ - firstprivate(mpi_row_start, max_labels_per_thread) + firstprivate(mpi_row_start, mpi_rows, max_labels_per_thread) { const int tid = omp_get_thread_num(); const int omp_row_start = mpi_row_start + (tid * mpi_rows) / num_threads; From e86fcc599f5362e168770e51b1c9fca97bbcd1c5 Mon Sep 17 00:00:00 2001 From: Victoria Date: Sat, 18 Apr 2026 16:08:15 +0300 Subject: [PATCH 13/22] fix report --- .../report.md | 156 ------------------ 1 file changed, 156 deletions(-) diff --git a/tasks/kondrashova_v_marking_components/report.md b/tasks/kondrashova_v_marking_components/report.md index d96bc83ed..43df1c468 100644 --- a/tasks/kondrashova_v_marking_components/report.md +++ b/tasks/kondrashova_v_marking_components/report.md @@ -1,160 +1,4 @@ -# Маркировка компонент связности на бинарном изображении (SEQ, OMP, TBB, STL, ALL) -**Вариант № 29** -**Студент:** Кондрашова Виктория Андреевна -**Группа:** 3823Б1ПР1 -**Технологии:** SEQ, OMP, TBB, STL, ALL (MPI + OpenMP) - ---- - -## Введение - -Маркировка компонент связности на бинарном изображении является базовой задачей -компьютерного зрения и анализа изображений. Она используется для поиска объектов, -оценки их количества, размеров и формы в задачах распознавания, медицинской -визуализации и подготовки данных к дальнейшей обработке. - -Параллельные реализации позволяют обрабатывать изображения большого размера -быстрее, распределяя работу между потоками или процессами. В рамках работы -рассмотрены реализации для OpenMP, TBB, STL и гибридная версия с MPI + OpenMP. - -## Постановка задачи - -**Цель работы:** -Реализовать последовательную и параллельные версии алгоритма маркировки -компонент связности на бинарном изображении, протестировать корректность и -оценить производительность. - -**Определение задачи:** -На вход подается бинарное изображение, где `0` обозначает пиксель объекта, а -`1` — фон. Требуется пронумеровать все связные компоненты по 4-связности и -вычислить их количество. - -**Ограничения:** - -- Ширина и высота изображения неотрицательны. -- Массив данных содержит `width * height` элементов. -- Используется 4-связность (по горизонтали и вертикали). - -## Описание алгоритма (последовательная версия) - -Последовательный алгоритм выполняет обход изображения слева направо и сверху -вниз. При обнаружении неразмеченного пикселя объекта запускается BFS (поиск в -ширину), который размечает всю соответствующую компоненту связности. Для BFS -используется очередь, а каждое посещенное значение получает метку текущей -компоненты. После завершения обхода счетчик компонент увеличивается. - -## Схемы распараллеливания - -### OpenMP-версия - -Изображение делится на горизонтальные полосы по числу потоков. Каждый поток -размечает свою полосу, создавая локальные метки. Для объединения компонент, -пересекающих границы полос, используется структура DSU (Union-Find). После -объединения выполняется финальная нормализация меток. - -### TBB-версия - -Разделение по полосам аналогично OpenMP, однако параллельный цикл выполняется -через `tbb::parallel_for`. DSU используется для глобального объединения меток, -а последующая перенумерация выполняется в одном потоке. - -### STL-версия - -Используются `std::thread` для обработки полос. Каждая полоса размечается в -собственном потоке, затем выполняется объединение соседних полос через DSU и -перенумерация меток. - -### ALL-версия (MPI + OpenMP) - -Изображение распределяется между MPI-процессами по горизонтальным полосам. -Внутри каждого процесса применяются потоки OpenMP для локальной разметки. -После локальной разметки выполняется обмен граничными строками, объединение -меток на границах, затем `MPI_Allreduce` собирает глобальный массив меток. -На ранге 0 проводится итоговое объединение и перенумерация меток. - -## Окружение - -Сборка выполнялась с помощью CMake в режиме Release. Для корректного запуска -MPI-версии использовался `mpiexec` с двумя процессами и одним потоком OpenMP -на процесс. - -## Экспериментальные результаты - -### Аппаратное и программное окружение - -|Параметр|Значение| -|-----------|-----------| -|Процессор|AMD Ryzen 5 5600H| -|Операционная система|Windows 10 Pro 22H2| -|Компилятор|MSVC 19.37 (Visual Studio 2022)| -|Тип сборки|Release| - -### Проверка корректности - -Для SEQ/OMP/TBB/STL/ALL выполнены 65 функциональных тестов из -`tests/functional/main.cpp`, все тесты пройдены успешно. - -### Оценка производительности - -Производительность измерялась на изображениях размером 512×512 для шести типов -данных (all ones, all zeros, chessboard, sparse dots, stripes, blocks). -Замерялось время выполнения в режиме `task_run`, ускорение рассчитано -относительно SEQ для каждого изображения. - -|Изображение|Версия|Время, с|Ускорение| -|-----------|-----------|-----------|-----------| -|all ones|SEQ|0.000319|1.000| -|all ones|OMP|0.003854|0.083| -|all ones|TBB|0.003229|0.099| -|all ones|STL|0.004231|0.075| -|all ones|ALL (2 MPI)|0.004511|0.071| -|all zeros|SEQ|0.001509|1.000| -|all zeros|OMP|0.004510|0.335| -|all zeros|TBB|0.004198|0.359| -|all zeros|STL|0.005421|0.278| -|all zeros|ALL (2 MPI)|0.004339|0.348| -|chessboard|SEQ|0.009387|1.000| -|chessboard|OMP|0.003941|2.382| -|chessboard|TBB|0.003911|2.400| -|chessboard|STL|0.004667|2.011| -|chessboard|ALL (2 MPI)|0.003977|2.361| -|sparse dots|SEQ|0.000575|1.000| -|sparse dots|OMP|0.003556|0.162| -|sparse dots|TBB|0.003075|0.187| -|sparse dots|STL|0.003491|0.165| -|sparse dots|ALL (2 MPI)|0.003415|0.168| -|stripes|SEQ|0.000717|1.000| -|stripes|OMP|0.003375|0.212| -|stripes|TBB|0.003381|0.212| -|stripes|STL|0.003763|0.191| -|stripes|ALL (2 MPI)|0.003683|0.195| -|blocks|SEQ|0.000770|1.000| -|blocks|OMP|0.003507|0.220| -|blocks|TBB|0.003877|0.199| -|blocks|STL|0.004431|0.174| -|blocks|ALL (2 MPI)|0.003790|0.203| - -## Выводы - -Реализованы последовательная и четыре параллельные версии алгоритма -маркировки компонент связности. Все реализации прошли функциональное -тестирование и дали корректные результаты. - -На изображении типа chessboard параллельные версии показывают ускорение -порядка 2–2.4 раза, что объясняется равномерной нагрузкой и большим числом -компонент. На более простых изображениях накладные расходы на синхронизацию -превышают выигрыш от распараллеливания, что приводит к замедлению. - -Гибридная ALL-версия корректно работает при запуске через MPI и показывает -сопоставимые результаты с потоковыми версиями на указанной конфигурации. - -## Источники - -1. Лекции по курсу «Параллельное программирование». -2. OpenMP Application Program Interface, Version 5.2. -3. oneTBB Developer Guide. -4. MPI Standard, Version 4.1. # Маркировка компонент связности на бинарном изображении - Студент: Кондрашова Виктория Андреевна From 95caca50a5bb38d9081081ece37a19a339b425db Mon Sep 17 00:00:00 2001 From: Victoria Date: Sat, 18 Apr 2026 16:43:47 +0300 Subject: [PATCH 14/22] fix clang --- .../report.md | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/tasks/kondrashova_v_marking_components/report.md b/tasks/kondrashova_v_marking_components/report.md index 43df1c468..081dce515 100644 --- a/tasks/kondrashova_v_marking_components/report.md +++ b/tasks/kondrashova_v_marking_components/report.md @@ -1,3 +1,162 @@ +# Маркировка компонент связности на бинарном изображении (SEQ, OMP, TBB, STL, ALL) + +**Вариант № 1** +**Студент:** Кондрашова Виктория Андреевна +**Группа:** 3823Б1ПР1 +**Преподаватель:** — + +--- + +## Введение + +Маркировка компонент связности на бинарном изображении является базовой задачей +компьютерного зрения и анализа изображений. Она используется для поиска объектов, +оценки их количества, размеров и формы в задачах распознавания, медицинской +визуализации и подготовки данных к дальнейшей обработке. + +Параллельные реализации позволяют обрабатывать изображения большого размера +быстрее, распределяя работу между потоками или процессами. В рамках работы +рассмотрены реализации для OpenMP, TBB, STL и гибридная версия с MPI + OpenMP. + +## Постановка задачи + +**Цель работы:** +Реализовать последовательную и параллельные версии алгоритма маркировки +компонент связности на бинарном изображении, протестировать корректность и +оценить производительность. + +**Определение задачи:** +На вход подается бинарное изображение, где `0` обозначает пиксель объекта, а +`1` — фон. Требуется пронумеровать все связные компоненты по 4-связности и +вычислить их количество. + +**Ограничения:** + +- Ширина и высота изображения неотрицательны. +- Массив данных содержит `width * height` элементов. +- Используется 4-связность (по горизонтали и вертикали). + +## Описание алгоритма (последовательная версия) + +Последовательный алгоритм выполняет обход изображения слева направо и сверху +вниз. При обнаружении неразмеченного пикселя объекта запускается BFS (поиск в +ширину), который размечает всю соответствующую компоненту связности. Для BFS +используется очередь, а каждое посещенное значение получает метку текущей +компоненты. После завершения обхода счетчик компонент увеличивается. + +## Схемы распараллеливания + +### OpenMP-версия + +Изображение делится на горизонтальные полосы по числу потоков. Каждый поток +размечает свою полосу, создавая локальные метки. Для объединения компонент, +пересекающих границы полос, используется структура DSU (Union-Find). После +объединения выполняется финальная нормализация меток. + +### TBB-версия + +Разделение по полосам аналогично OpenMP, однако параллельный цикл выполняется +через `tbb::parallel_for`. DSU используется для глобального объединения меток, +а последующая перенумерация выполняется в одном потоке. + +### STL-версия + +Используются `std::thread` для обработки полос. Каждая полоса размечается в +собственном потоке, затем выполняется объединение соседних полос через DSU и +перенумерация меток. + +### ALL-версия (MPI + OpenMP) + +Изображение распределяется между MPI-процессами по горизонтальным полосам. +Внутри каждого процесса применяются потоки OpenMP для локальной разметки. +После локальной разметки выполняется обмен граничными строками, объединение +меток на границах, затем `MPI_Allreduce` собирает глобальный массив меток. +На ранге 0 проводится итоговое объединение и перенумерация меток. + +## Окружение + +Сборка выполнялась с помощью CMake в режиме Release. Для корректного запуска +MPI-версии использовался `mpiexec` с двумя процессами и одним потоком OpenMP +на процесс. + +## Экспериментальные результаты + +### Аппаратное и программное окружение + +|Параметр|Значение| +|-----------|-----------| +|Процессор|AMD Ryzen 5 5600H| +|Операционная система|Windows 10 Pro 22H2| +|Компилятор|MSVC 19.37 (Visual Studio 2022)| +|Тип сборки|Release| + +### Проверка корректности + +Для SEQ/OMP/TBB/STL выполнены 52 функциональных теста из +`tests/functional/main.cpp`, все тесты пройдены успешно. +Для ALL-версии выполнены 13 тестов из отдельного MPI-раннера +при запуске `mpiexec -n 2`, все тесты пройдены успешно. + +### Оценка производительности + +Производительность измерялась на изображениях размером 512×512 для шести типов +данных (all ones, all zeros, chessboard, sparse dots, stripes, blocks). +Замерялось время выполнения в режиме `task_run`, ускорение рассчитано +относительно SEQ для каждого изображения. + +|Изображение|Версия|Время, с|Ускорение| +|-----------|-----------|-----------|-----------| +|all ones|SEQ|0.000319|1.000| +|all ones|OMP|0.003854|0.083| +|all ones|TBB|0.003229|0.099| +|all ones|STL|0.004231|0.075| +|all ones|ALL (2 MPI)|0.004511|0.071| +|all zeros|SEQ|0.001509|1.000| +|all zeros|OMP|0.004510|0.335| +|all zeros|TBB|0.004198|0.359| +|all zeros|STL|0.005421|0.278| +|all zeros|ALL (2 MPI)|0.004339|0.348| +|chessboard|SEQ|0.009387|1.000| +|chessboard|OMP|0.003941|2.382| +|chessboard|TBB|0.003911|2.400| +|chessboard|STL|0.004667|2.011| +|chessboard|ALL (2 MPI)|0.003977|2.361| +|sparse dots|SEQ|0.000575|1.000| +|sparse dots|OMP|0.003556|0.162| +|sparse dots|TBB|0.003075|0.187| +|sparse dots|STL|0.003491|0.165| +|sparse dots|ALL (2 MPI)|0.003415|0.168| +|stripes|SEQ|0.000717|1.000| +|stripes|OMP|0.003375|0.212| +|stripes|TBB|0.003381|0.212| +|stripes|STL|0.003763|0.191| +|stripes|ALL (2 MPI)|0.003683|0.195| +|blocks|SEQ|0.000770|1.000| +|blocks|OMP|0.003507|0.220| +|blocks|TBB|0.003877|0.199| +|blocks|STL|0.004431|0.174| +|blocks|ALL (2 MPI)|0.003790|0.203| + +## Выводы + +Реализованы последовательная и четыре параллельные версии алгоритма +маркировки компонент связности. Все реализации прошли функциональное +тестирование и дали корректные результаты. + +На изображении типа chessboard параллельные версии показывают ускорение +порядка 2–2.4 раза, что объясняется равномерной нагрузкой и большим числом +компонент. На более простых изображениях накладные расходы на синхронизацию +превышают выигрыш от распараллеливания, что приводит к замедлению. + +Гибридная ALL-версия корректно работает при запуске через MPI и показывает +сопоставимые результаты с потоковыми версиями на указанной конфигурации. + +## Источники + +1. Лекции по курсу «Параллельное программирование». +2. OpenMP Application Program Interface, Version 5.2. +3. oneTBB Developer Guide. +4. MPI Standard, Version 4.1. # Маркировка компонент связности на бинарном изображении From 4d202b8cc79c0819f3ab96906a75944eb69fc762 Mon Sep 17 00:00:00 2001 From: Victoria Date: Sat, 18 Apr 2026 16:43:57 +0300 Subject: [PATCH 15/22] fix --- .../all/src/ops_all.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp index 27d65ec7b..3a389d8b4 100644 --- a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp +++ b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp @@ -38,7 +38,7 @@ bool KondrashovaVTaskALL::PreProcessingImpl() { int has_valid_input = 0; if (rank_ == 0) { - has_valid_input = width_ > 0 && height_ > 0 && static_cast(image_.size()) == (width_ * height_); + has_valid_input = (width_ > 0 && height_ > 0 && static_cast(image_.size()) == (width_ * height_)) ? 1 : 0; } MPI_Bcast(&has_valid_input, 1, MPI_INT, 0, MPI_COMM_WORLD); @@ -54,7 +54,7 @@ bool KondrashovaVTaskALL::PreProcessingImpl() { image_.resize(static_cast(width_) * static_cast(height_)); } - MPI_Bcast(image_.data(), width_ * height_, MPI_UINT8_T, 0, MPI_COMM_WORLD); + MPI_Bcast(image_.data(), width_ * height_, MPI_UNSIGNED_CHAR, 0, MPI_COMM_WORLD); labels_1d_.assign(static_cast(width_) * static_cast(height_), 0); @@ -158,7 +158,7 @@ void MergeBoundaries(int row_start, int row_end, int width, int num_threads, con std::vector &parent, std::vector &rnk) { const int rows = row_end - row_start; for (int tid = 1; tid < num_threads; ++tid) { - const int boundary_row = row_start + (tid * rows) / num_threads; + const int boundary_row = row_start + ((tid * rows) / num_threads); if (boundary_row <= row_start || boundary_row >= row_end) { continue; } @@ -190,7 +190,8 @@ int Relabel(int total, const std::vector &local_labels, std::vector &p } void MergeMPIBoundaries(int width, int rank, int world_size, int local_row_start, int local_row_end, - const std::vector &local_labels, std::vector &parent, std::vector &rnk) { + const std::vector &local_labels, std::vector &parent, + std::vector &rnk) { // NOLINT(readability-function-cognitive-complexity) if (rank > 0) { std::vector send_row(static_cast(width), 0); std::vector recv_row(static_cast(width)); @@ -198,7 +199,7 @@ void MergeMPIBoundaries(int width, int rank, int world_size, int local_row_start if (local_row_start < local_row_end) { for (int jj = 0; jj < width; ++jj) { send_row[static_cast(jj)] = - local_labels[static_cast(local_row_start) * static_cast(width) + static_cast(jj)]; + local_labels[(static_cast(local_row_start) * static_cast(width)) + static_cast(jj)]; } } @@ -222,7 +223,7 @@ void MergeMPIBoundaries(int width, int rank, int world_size, int local_row_start const int last_row = local_row_end - 1; for (int jj = 0; jj < width; ++jj) { send_row[static_cast(jj)] = - local_labels[static_cast(last_row) * static_cast(width) + static_cast(jj)]; + local_labels[(static_cast(last_row) * static_cast(width)) + static_cast(jj)]; } } @@ -266,8 +267,8 @@ bool KondrashovaVTaskALL::RunImpl() { firstprivate(mpi_row_start, mpi_rows, max_labels_per_thread) { const int tid = omp_get_thread_num(); - const int omp_row_start = mpi_row_start + (tid * mpi_rows) / num_threads; - const int omp_row_end = mpi_row_start + ((tid + 1) * mpi_rows) / num_threads; + const int omp_row_start = mpi_row_start + ((tid * mpi_rows) / num_threads); + const int omp_row_end = mpi_row_start + (((tid + 1) * mpi_rows) / num_threads); const int label_offset = (rank_ * num_threads * max_labels_per_thread) + (tid * max_labels_per_thread); ScanStripe(omp_row_start, omp_row_end, width, label_offset, image, local_labels); } From 83fc2d4b0683b9906bd0e7f993eaa960a48fd162 Mon Sep 17 00:00:00 2001 From: Victoria Date: Sat, 18 Apr 2026 16:49:01 +0300 Subject: [PATCH 16/22] fix report --- .../report.md | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/tasks/kondrashova_v_marking_components/report.md b/tasks/kondrashova_v_marking_components/report.md index 081dce515..3e4c8f727 100644 --- a/tasks/kondrashova_v_marking_components/report.md +++ b/tasks/kondrashova_v_marking_components/report.md @@ -153,6 +153,165 @@ MPI-версии использовался `mpiexec` с двумя процес ## Источники +1. Лекции по курсу «Параллельное программирование». +2. OpenMP Application Program Interface, Version 5.2. +3. oneTBB Developer Guide. +4. MPI Standard, Version 4.1. +# Маркировка компонент связности на бинарном изображении (SEQ, OMP, TBB, STL, ALL) + +**Вариант № 1** +**Студент:** Кондрашова Виктория Андреевна +**Группа:** 3823Б1ПР1 +**Преподаватель:** — + +--- + +## Введение + +Маркировка компонент связности на бинарном изображении является базовой задачей +компьютерного зрения и анализа изображений. Она используется для поиска объектов, +оценки их количества, размеров и формы в задачах распознавания, медицинской +визуализации и подготовки данных к дальнейшей обработке. + +Параллельные реализации позволяют обрабатывать изображения большого размера +быстрее, распределяя работу между потоками или процессами. В рамках работы +рассмотрены реализации для OpenMP, TBB, STL и гибридная версия с MPI + OpenMP. + +## Постановка задачи + +**Цель работы:** +Реализовать последовательную и параллельные версии алгоритма маркировки +компонент связности на бинарном изображении, протестировать корректность и +оценить производительность. + +**Определение задачи:** +На вход подается бинарное изображение, где `0` обозначает пиксель объекта, а +`1` — фон. Требуется пронумеровать все связные компоненты по 4-связности и +вычислить их количество. + +**Ограничения:** + +- Ширина и высота изображения неотрицательны. +- Массив данных содержит `width * height` элементов. +- Используется 4-связность (по горизонтали и вертикали). + +## Описание алгоритма (последовательная версия) + +Последовательный алгоритм выполняет обход изображения слева направо и сверху +вниз. При обнаружении неразмеченного пикселя объекта запускается BFS (поиск в +ширину), который размечает всю соответствующую компоненту связности. Для BFS +используется очередь, а каждое посещенное значение получает метку текущей +компоненты. После завершения обхода счетчик компонент увеличивается. + +## Схемы распараллеливания + +### OpenMP-версия + +Изображение делится на горизонтальные полосы по числу потоков. Каждый поток +размечает свою полосу, создавая локальные метки. Для объединения компонент, +пересекающих границы полос, используется структура DSU (Union-Find). После +объединения выполняется финальная нормализация меток. + +### TBB-версия + +Разделение по полосам аналогично OpenMP, однако параллельный цикл выполняется +через `tbb::parallel_for`. DSU используется для глобального объединения меток, +а последующая перенумерация выполняется в одном потоке. + +### STL-версия + +Используются `std::thread` для обработки полос. Каждая полоса размечается в +собственном потоке, затем выполняется объединение соседних полос через DSU и +перенумерация меток. + +### ALL-версия (MPI + OpenMP) + +Изображение распределяется между MPI-процессами по горизонтальным полосам. +Внутри каждого процесса применяются потоки OpenMP для локальной разметки. +После локальной разметки выполняется обмен граничными строками, объединение +меток на границах, затем `MPI_Allreduce` собирает глобальный массив меток. +На ранге 0 проводится итоговое объединение и перенумерация меток. + +## Окружение + +Сборка выполнялась с помощью CMake в режиме Release. Для корректного запуска +MPI-версии использовался `mpiexec` с двумя процессами и одним потоком OpenMP +на процесс. + +## Экспериментальные результаты + +### Аппаратное и программное окружение + +|Параметр|Значение| +|-----------|-----------| +|Процессор|AMD Ryzen 5 5600H| +|Операционная система|Windows 10 Pro 22H2| +|Компилятор|MSVC 19.37 (Visual Studio 2022)| +|Тип сборки|Release| + +### Проверка корректности + +Для SEQ/OMP/TBB/STL выполнены 52 функциональных теста из +`tests/functional/main.cpp`, все тесты пройдены успешно. +Для ALL-версии выполнены 13 тестов из отдельного MPI-раннера +при запуске `mpiexec -n 2`, все тесты пройдены успешно. + +### Оценка производительности + +Производительность измерялась на изображениях размером 512×512 для шести типов +данных (all ones, all zeros, chessboard, sparse dots, stripes, blocks). +Замерялось время выполнения в режиме `task_run`, ускорение рассчитано +относительно SEQ для каждого изображения. + +|Изображение|Версия|Время, с|Ускорение| +|-----------|-----------|-----------|-----------| +|all ones|SEQ|0.000319|1.000| +|all ones|OMP|0.003854|0.083| +|all ones|TBB|0.003229|0.099| +|all ones|STL|0.004231|0.075| +|all ones|ALL (2 MPI)|0.004511|0.071| +|all zeros|SEQ|0.001509|1.000| +|all zeros|OMP|0.004510|0.335| +|all zeros|TBB|0.004198|0.359| +|all zeros|STL|0.005421|0.278| +|all zeros|ALL (2 MPI)|0.004339|0.348| +|chessboard|SEQ|0.009387|1.000| +|chessboard|OMP|0.003941|2.382| +|chessboard|TBB|0.003911|2.400| +|chessboard|STL|0.004667|2.011| +|chessboard|ALL (2 MPI)|0.003977|2.361| +|sparse dots|SEQ|0.000575|1.000| +|sparse dots|OMP|0.003556|0.162| +|sparse dots|TBB|0.003075|0.187| +|sparse dots|STL|0.003491|0.165| +|sparse dots|ALL (2 MPI)|0.003415|0.168| +|stripes|SEQ|0.000717|1.000| +|stripes|OMP|0.003375|0.212| +|stripes|TBB|0.003381|0.212| +|stripes|STL|0.003763|0.191| +|stripes|ALL (2 MPI)|0.003683|0.195| +|blocks|SEQ|0.000770|1.000| +|blocks|OMP|0.003507|0.220| +|blocks|TBB|0.003877|0.199| +|blocks|STL|0.004431|0.174| +|blocks|ALL (2 MPI)|0.003790|0.203| + +## Выводы + +Реализованы последовательная и четыре параллельные версии алгоритма +маркировки компонент связности. Все реализации прошли функциональное +тестирование и дали корректные результаты. + +На изображении типа chessboard параллельные версии показывают ускорение +порядка 2–2.4 раза, что объясняется равномерной нагрузкой и большим числом +компонент. На более простых изображениях накладные расходы на синхронизацию +превышают выигрыш от распараллеливания, что приводит к замедлению. + +Гибридная ALL-версия корректно работает при запуске через MPI и показывает +сопоставимые результаты с потоковыми версиями на указанной конфигурации. + +## Источники + 1. Лекции по курсу «Параллельное программирование». 2. OpenMP Application Program Interface, Version 5.2. 3. oneTBB Developer Guide. From bcced8c811472005740408bcda76325cf3dbd844 Mon Sep 17 00:00:00 2001 From: Victoria Date: Sat, 18 Apr 2026 17:00:19 +0300 Subject: [PATCH 17/22] fix clang --- .../all/src/ops_all.cpp | 78 +++-- .../report.md | 319 ------------------ 2 files changed, 38 insertions(+), 359 deletions(-) diff --git a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp index 3a389d8b4..fc60b5e9e 100644 --- a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp +++ b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp @@ -189,54 +189,52 @@ int Relabel(int total, const std::vector &local_labels, std::vector &p return count; } -void MergeMPIBoundaries(int width, int rank, int world_size, int local_row_start, int local_row_end, - const std::vector &local_labels, std::vector &parent, - std::vector &rnk) { // NOLINT(readability-function-cognitive-complexity) - if (rank > 0) { - std::vector send_row(static_cast(width), 0); - std::vector recv_row(static_cast(width)); +void FillRowLabels(int width, int row, const std::vector &local_labels, std::vector &row_labels) { + for (int jj = 0; jj < width; ++jj) { + row_labels[static_cast(jj)] = + local_labels[(static_cast(row) * static_cast(width)) + static_cast(jj)]; + } +} - if (local_row_start < local_row_end) { - for (int jj = 0; jj < width; ++jj) { - send_row[static_cast(jj)] = - local_labels[(static_cast(local_row_start) * static_cast(width)) + static_cast(jj)]; - } +void MergeBoundaryLabels(int width, const std::vector &send_row, const std::vector &recv_row, + std::vector &parent, std::vector &rnk) { + for (int jj = 0; jj < width; ++jj) { + const int label_cur = send_row[static_cast(jj)]; + const int label_neighbor = recv_row[static_cast(jj)]; + if (label_cur != 0 && label_neighbor != 0 && label_cur != label_neighbor) { + Unite(parent, rnk, label_cur, label_neighbor); } + } +} - MPI_Sendrecv(send_row.data(), width, MPI_INT, rank - 1, 0, recv_row.data(), width, MPI_INT, rank - 1, 1, - MPI_COMM_WORLD, MPI_STATUS_IGNORE); +void ExchangeAndMergeRow(int width, int neighbor_rank, int send_tag, int recv_tag, bool has_rows, int row_index, + const std::vector &local_labels, std::vector &parent, std::vector &rnk) { + std::vector send_row(static_cast(width), 0); + std::vector recv_row(static_cast(width)); - for (int jj = 0; jj < width; ++jj) { - int label_cur = send_row[static_cast(jj)]; - int label_prev = recv_row[static_cast(jj)]; - if (label_cur != 0 && label_prev != 0 && label_cur != label_prev) { - Unite(parent, rnk, label_cur, label_prev); - } - } + if (has_rows) { + FillRowLabels(width, row_index, local_labels, send_row); } - if (rank < world_size - 1) { - std::vector send_row(static_cast(width), 0); - std::vector recv_row(static_cast(width)); - - if (local_row_start < local_row_end) { - const int last_row = local_row_end - 1; - for (int jj = 0; jj < width; ++jj) { - send_row[static_cast(jj)] = - local_labels[(static_cast(last_row) * static_cast(width)) + static_cast(jj)]; - } - } + MPI_Sendrecv(send_row.data(), width, MPI_INT, neighbor_rank, send_tag, recv_row.data(), width, MPI_INT, + neighbor_rank, recv_tag, MPI_COMM_WORLD, MPI_STATUS_IGNORE); - MPI_Sendrecv(send_row.data(), width, MPI_INT, rank + 1, 1, recv_row.data(), width, MPI_INT, rank + 1, 0, - MPI_COMM_WORLD, MPI_STATUS_IGNORE); + MergeBoundaryLabels(width, send_row, recv_row, parent, rnk); +} - for (int jj = 0; jj < width; ++jj) { - int label_cur = send_row[static_cast(jj)]; - int label_next = recv_row[static_cast(jj)]; - if (label_cur != 0 && label_next != 0 && label_cur != label_next) { - Unite(parent, rnk, label_cur, label_next); - } - } +void MergeMPIBoundaries(int width, int rank, int world_size, int local_row_start, int local_row_end, + const std::vector &local_labels, std::vector &parent, + std::vector &rnk) { + const bool has_rows = local_row_start < local_row_end; + const int first_row = local_row_start; + const int last_row = has_rows ? (local_row_end - 1) : local_row_start; + + if (rank > 0) { + ExchangeAndMergeRow(width, rank - 1, 0, 1, has_rows, first_row, local_labels, parent, rnk); + } + + if (rank < world_size - 1) { + ExchangeAndMergeRow(width, rank + 1, 1, 0, has_rows, last_row, local_labels, parent, rnk); } } diff --git a/tasks/kondrashova_v_marking_components/report.md b/tasks/kondrashova_v_marking_components/report.md index 3e4c8f727..db7a3941e 100644 --- a/tasks/kondrashova_v_marking_components/report.md +++ b/tasks/kondrashova_v_marking_components/report.md @@ -1,322 +1,3 @@ -# Маркировка компонент связности на бинарном изображении (SEQ, OMP, TBB, STL, ALL) - -**Вариант № 1** -**Студент:** Кондрашова Виктория Андреевна -**Группа:** 3823Б1ПР1 -**Преподаватель:** — - ---- - -## Введение - -Маркировка компонент связности на бинарном изображении является базовой задачей -компьютерного зрения и анализа изображений. Она используется для поиска объектов, -оценки их количества, размеров и формы в задачах распознавания, медицинской -визуализации и подготовки данных к дальнейшей обработке. - -Параллельные реализации позволяют обрабатывать изображения большого размера -быстрее, распределяя работу между потоками или процессами. В рамках работы -рассмотрены реализации для OpenMP, TBB, STL и гибридная версия с MPI + OpenMP. - -## Постановка задачи - -**Цель работы:** -Реализовать последовательную и параллельные версии алгоритма маркировки -компонент связности на бинарном изображении, протестировать корректность и -оценить производительность. - -**Определение задачи:** -На вход подается бинарное изображение, где `0` обозначает пиксель объекта, а -`1` — фон. Требуется пронумеровать все связные компоненты по 4-связности и -вычислить их количество. - -**Ограничения:** - -- Ширина и высота изображения неотрицательны. -- Массив данных содержит `width * height` элементов. -- Используется 4-связность (по горизонтали и вертикали). - -## Описание алгоритма (последовательная версия) - -Последовательный алгоритм выполняет обход изображения слева направо и сверху -вниз. При обнаружении неразмеченного пикселя объекта запускается BFS (поиск в -ширину), который размечает всю соответствующую компоненту связности. Для BFS -используется очередь, а каждое посещенное значение получает метку текущей -компоненты. После завершения обхода счетчик компонент увеличивается. - -## Схемы распараллеливания - -### OpenMP-версия - -Изображение делится на горизонтальные полосы по числу потоков. Каждый поток -размечает свою полосу, создавая локальные метки. Для объединения компонент, -пересекающих границы полос, используется структура DSU (Union-Find). После -объединения выполняется финальная нормализация меток. - -### TBB-версия - -Разделение по полосам аналогично OpenMP, однако параллельный цикл выполняется -через `tbb::parallel_for`. DSU используется для глобального объединения меток, -а последующая перенумерация выполняется в одном потоке. - -### STL-версия - -Используются `std::thread` для обработки полос. Каждая полоса размечается в -собственном потоке, затем выполняется объединение соседних полос через DSU и -перенумерация меток. - -### ALL-версия (MPI + OpenMP) - -Изображение распределяется между MPI-процессами по горизонтальным полосам. -Внутри каждого процесса применяются потоки OpenMP для локальной разметки. -После локальной разметки выполняется обмен граничными строками, объединение -меток на границах, затем `MPI_Allreduce` собирает глобальный массив меток. -На ранге 0 проводится итоговое объединение и перенумерация меток. - -## Окружение - -Сборка выполнялась с помощью CMake в режиме Release. Для корректного запуска -MPI-версии использовался `mpiexec` с двумя процессами и одним потоком OpenMP -на процесс. - -## Экспериментальные результаты - -### Аппаратное и программное окружение - -|Параметр|Значение| -|-----------|-----------| -|Процессор|AMD Ryzen 5 5600H| -|Операционная система|Windows 10 Pro 22H2| -|Компилятор|MSVC 19.37 (Visual Studio 2022)| -|Тип сборки|Release| - -### Проверка корректности - -Для SEQ/OMP/TBB/STL выполнены 52 функциональных теста из -`tests/functional/main.cpp`, все тесты пройдены успешно. -Для ALL-версии выполнены 13 тестов из отдельного MPI-раннера -при запуске `mpiexec -n 2`, все тесты пройдены успешно. - -### Оценка производительности - -Производительность измерялась на изображениях размером 512×512 для шести типов -данных (all ones, all zeros, chessboard, sparse dots, stripes, blocks). -Замерялось время выполнения в режиме `task_run`, ускорение рассчитано -относительно SEQ для каждого изображения. - -|Изображение|Версия|Время, с|Ускорение| -|-----------|-----------|-----------|-----------| -|all ones|SEQ|0.000319|1.000| -|all ones|OMP|0.003854|0.083| -|all ones|TBB|0.003229|0.099| -|all ones|STL|0.004231|0.075| -|all ones|ALL (2 MPI)|0.004511|0.071| -|all zeros|SEQ|0.001509|1.000| -|all zeros|OMP|0.004510|0.335| -|all zeros|TBB|0.004198|0.359| -|all zeros|STL|0.005421|0.278| -|all zeros|ALL (2 MPI)|0.004339|0.348| -|chessboard|SEQ|0.009387|1.000| -|chessboard|OMP|0.003941|2.382| -|chessboard|TBB|0.003911|2.400| -|chessboard|STL|0.004667|2.011| -|chessboard|ALL (2 MPI)|0.003977|2.361| -|sparse dots|SEQ|0.000575|1.000| -|sparse dots|OMP|0.003556|0.162| -|sparse dots|TBB|0.003075|0.187| -|sparse dots|STL|0.003491|0.165| -|sparse dots|ALL (2 MPI)|0.003415|0.168| -|stripes|SEQ|0.000717|1.000| -|stripes|OMP|0.003375|0.212| -|stripes|TBB|0.003381|0.212| -|stripes|STL|0.003763|0.191| -|stripes|ALL (2 MPI)|0.003683|0.195| -|blocks|SEQ|0.000770|1.000| -|blocks|OMP|0.003507|0.220| -|blocks|TBB|0.003877|0.199| -|blocks|STL|0.004431|0.174| -|blocks|ALL (2 MPI)|0.003790|0.203| - -## Выводы - -Реализованы последовательная и четыре параллельные версии алгоритма -маркировки компонент связности. Все реализации прошли функциональное -тестирование и дали корректные результаты. - -На изображении типа chessboard параллельные версии показывают ускорение -порядка 2–2.4 раза, что объясняется равномерной нагрузкой и большим числом -компонент. На более простых изображениях накладные расходы на синхронизацию -превышают выигрыш от распараллеливания, что приводит к замедлению. - -Гибридная ALL-версия корректно работает при запуске через MPI и показывает -сопоставимые результаты с потоковыми версиями на указанной конфигурации. - -## Источники - -1. Лекции по курсу «Параллельное программирование». -2. OpenMP Application Program Interface, Version 5.2. -3. oneTBB Developer Guide. -4. MPI Standard, Version 4.1. -# Маркировка компонент связности на бинарном изображении (SEQ, OMP, TBB, STL, ALL) - -**Вариант № 1** -**Студент:** Кондрашова Виктория Андреевна -**Группа:** 3823Б1ПР1 -**Преподаватель:** — - ---- - -## Введение - -Маркировка компонент связности на бинарном изображении является базовой задачей -компьютерного зрения и анализа изображений. Она используется для поиска объектов, -оценки их количества, размеров и формы в задачах распознавания, медицинской -визуализации и подготовки данных к дальнейшей обработке. - -Параллельные реализации позволяют обрабатывать изображения большого размера -быстрее, распределяя работу между потоками или процессами. В рамках работы -рассмотрены реализации для OpenMP, TBB, STL и гибридная версия с MPI + OpenMP. - -## Постановка задачи - -**Цель работы:** -Реализовать последовательную и параллельные версии алгоритма маркировки -компонент связности на бинарном изображении, протестировать корректность и -оценить производительность. - -**Определение задачи:** -На вход подается бинарное изображение, где `0` обозначает пиксель объекта, а -`1` — фон. Требуется пронумеровать все связные компоненты по 4-связности и -вычислить их количество. - -**Ограничения:** - -- Ширина и высота изображения неотрицательны. -- Массив данных содержит `width * height` элементов. -- Используется 4-связность (по горизонтали и вертикали). - -## Описание алгоритма (последовательная версия) - -Последовательный алгоритм выполняет обход изображения слева направо и сверху -вниз. При обнаружении неразмеченного пикселя объекта запускается BFS (поиск в -ширину), который размечает всю соответствующую компоненту связности. Для BFS -используется очередь, а каждое посещенное значение получает метку текущей -компоненты. После завершения обхода счетчик компонент увеличивается. - -## Схемы распараллеливания - -### OpenMP-версия - -Изображение делится на горизонтальные полосы по числу потоков. Каждый поток -размечает свою полосу, создавая локальные метки. Для объединения компонент, -пересекающих границы полос, используется структура DSU (Union-Find). После -объединения выполняется финальная нормализация меток. - -### TBB-версия - -Разделение по полосам аналогично OpenMP, однако параллельный цикл выполняется -через `tbb::parallel_for`. DSU используется для глобального объединения меток, -а последующая перенумерация выполняется в одном потоке. - -### STL-версия - -Используются `std::thread` для обработки полос. Каждая полоса размечается в -собственном потоке, затем выполняется объединение соседних полос через DSU и -перенумерация меток. - -### ALL-версия (MPI + OpenMP) - -Изображение распределяется между MPI-процессами по горизонтальным полосам. -Внутри каждого процесса применяются потоки OpenMP для локальной разметки. -После локальной разметки выполняется обмен граничными строками, объединение -меток на границах, затем `MPI_Allreduce` собирает глобальный массив меток. -На ранге 0 проводится итоговое объединение и перенумерация меток. - -## Окружение - -Сборка выполнялась с помощью CMake в режиме Release. Для корректного запуска -MPI-версии использовался `mpiexec` с двумя процессами и одним потоком OpenMP -на процесс. - -## Экспериментальные результаты - -### Аппаратное и программное окружение - -|Параметр|Значение| -|-----------|-----------| -|Процессор|AMD Ryzen 5 5600H| -|Операционная система|Windows 10 Pro 22H2| -|Компилятор|MSVC 19.37 (Visual Studio 2022)| -|Тип сборки|Release| - -### Проверка корректности - -Для SEQ/OMP/TBB/STL выполнены 52 функциональных теста из -`tests/functional/main.cpp`, все тесты пройдены успешно. -Для ALL-версии выполнены 13 тестов из отдельного MPI-раннера -при запуске `mpiexec -n 2`, все тесты пройдены успешно. - -### Оценка производительности - -Производительность измерялась на изображениях размером 512×512 для шести типов -данных (all ones, all zeros, chessboard, sparse dots, stripes, blocks). -Замерялось время выполнения в режиме `task_run`, ускорение рассчитано -относительно SEQ для каждого изображения. - -|Изображение|Версия|Время, с|Ускорение| -|-----------|-----------|-----------|-----------| -|all ones|SEQ|0.000319|1.000| -|all ones|OMP|0.003854|0.083| -|all ones|TBB|0.003229|0.099| -|all ones|STL|0.004231|0.075| -|all ones|ALL (2 MPI)|0.004511|0.071| -|all zeros|SEQ|0.001509|1.000| -|all zeros|OMP|0.004510|0.335| -|all zeros|TBB|0.004198|0.359| -|all zeros|STL|0.005421|0.278| -|all zeros|ALL (2 MPI)|0.004339|0.348| -|chessboard|SEQ|0.009387|1.000| -|chessboard|OMP|0.003941|2.382| -|chessboard|TBB|0.003911|2.400| -|chessboard|STL|0.004667|2.011| -|chessboard|ALL (2 MPI)|0.003977|2.361| -|sparse dots|SEQ|0.000575|1.000| -|sparse dots|OMP|0.003556|0.162| -|sparse dots|TBB|0.003075|0.187| -|sparse dots|STL|0.003491|0.165| -|sparse dots|ALL (2 MPI)|0.003415|0.168| -|stripes|SEQ|0.000717|1.000| -|stripes|OMP|0.003375|0.212| -|stripes|TBB|0.003381|0.212| -|stripes|STL|0.003763|0.191| -|stripes|ALL (2 MPI)|0.003683|0.195| -|blocks|SEQ|0.000770|1.000| -|blocks|OMP|0.003507|0.220| -|blocks|TBB|0.003877|0.199| -|blocks|STL|0.004431|0.174| -|blocks|ALL (2 MPI)|0.003790|0.203| - -## Выводы - -Реализованы последовательная и четыре параллельные версии алгоритма -маркировки компонент связности. Все реализации прошли функциональное -тестирование и дали корректные результаты. - -На изображении типа chessboard параллельные версии показывают ускорение -порядка 2–2.4 раза, что объясняется равномерной нагрузкой и большим числом -компонент. На более простых изображениях накладные расходы на синхронизацию -превышают выигрыш от распараллеливания, что приводит к замедлению. - -Гибридная ALL-версия корректно работает при запуске через MPI и показывает -сопоставимые результаты с потоковыми версиями на указанной конфигурации. - -## Источники - -1. Лекции по курсу «Параллельное программирование». -2. OpenMP Application Program Interface, Version 5.2. -3. oneTBB Developer Guide. -4. MPI Standard, Version 4.1. - # Маркировка компонент связности на бинарном изображении - Студент: Кондрашова Виктория Андреевна From 0f87346d90af4688c42273b6b7d9ac58e7dad18f Mon Sep 17 00:00:00 2001 From: Victoria Date: Sat, 18 Apr 2026 17:04:56 +0300 Subject: [PATCH 18/22] done --- tasks/kondrashova_v_marking_components/all/src/ops_all.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp index fc60b5e9e..e51685aaa 100644 --- a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp +++ b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp @@ -216,15 +216,14 @@ void ExchangeAndMergeRow(int width, int neighbor_rank, int send_tag, int recv_ta FillRowLabels(width, row_index, local_labels, send_row); } - MPI_Sendrecv(send_row.data(), width, MPI_INT, neighbor_rank, send_tag, recv_row.data(), width, MPI_INT, - neighbor_rank, recv_tag, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + MPI_Sendrecv(send_row.data(), width, MPI_INT, neighbor_rank, send_tag, recv_row.data(), width, MPI_INT, neighbor_rank, + recv_tag, MPI_COMM_WORLD, MPI_STATUS_IGNORE); MergeBoundaryLabels(width, send_row, recv_row, parent, rnk); } void MergeMPIBoundaries(int width, int rank, int world_size, int local_row_start, int local_row_end, - const std::vector &local_labels, std::vector &parent, - std::vector &rnk) { + const std::vector &local_labels, std::vector &parent, std::vector &rnk) { const bool has_rows = local_row_start < local_row_end; const int first_row = local_row_start; const int last_row = has_rows ? (local_row_end - 1) : local_row_start; From 1ef7299041946a1b2ce68ab860aa610cff60fb3a Mon Sep 17 00:00:00 2001 From: Victoria Date: Sat, 18 Apr 2026 17:09:45 +0300 Subject: [PATCH 19/22] done Made-with: Cursor --- .../all/src/ops_all.cpp | 86 +++++----- .../report.md | 159 ------------------ 2 files changed, 42 insertions(+), 203 deletions(-) diff --git a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp index 27d65ec7b..e51685aaa 100644 --- a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp +++ b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp @@ -38,7 +38,7 @@ bool KondrashovaVTaskALL::PreProcessingImpl() { int has_valid_input = 0; if (rank_ == 0) { - has_valid_input = width_ > 0 && height_ > 0 && static_cast(image_.size()) == (width_ * height_); + has_valid_input = (width_ > 0 && height_ > 0 && static_cast(image_.size()) == (width_ * height_)) ? 1 : 0; } MPI_Bcast(&has_valid_input, 1, MPI_INT, 0, MPI_COMM_WORLD); @@ -54,7 +54,7 @@ bool KondrashovaVTaskALL::PreProcessingImpl() { image_.resize(static_cast(width_) * static_cast(height_)); } - MPI_Bcast(image_.data(), width_ * height_, MPI_UINT8_T, 0, MPI_COMM_WORLD); + MPI_Bcast(image_.data(), width_ * height_, MPI_UNSIGNED_CHAR, 0, MPI_COMM_WORLD); labels_1d_.assign(static_cast(width_) * static_cast(height_), 0); @@ -158,7 +158,7 @@ void MergeBoundaries(int row_start, int row_end, int width, int num_threads, con std::vector &parent, std::vector &rnk) { const int rows = row_end - row_start; for (int tid = 1; tid < num_threads; ++tid) { - const int boundary_row = row_start + (tid * rows) / num_threads; + const int boundary_row = row_start + ((tid * rows) / num_threads); if (boundary_row <= row_start || boundary_row >= row_end) { continue; } @@ -189,53 +189,51 @@ int Relabel(int total, const std::vector &local_labels, std::vector &p return count; } -void MergeMPIBoundaries(int width, int rank, int world_size, int local_row_start, int local_row_end, - const std::vector &local_labels, std::vector &parent, std::vector &rnk) { - if (rank > 0) { - std::vector send_row(static_cast(width), 0); - std::vector recv_row(static_cast(width)); +void FillRowLabels(int width, int row, const std::vector &local_labels, std::vector &row_labels) { + for (int jj = 0; jj < width; ++jj) { + row_labels[static_cast(jj)] = + local_labels[(static_cast(row) * static_cast(width)) + static_cast(jj)]; + } +} - if (local_row_start < local_row_end) { - for (int jj = 0; jj < width; ++jj) { - send_row[static_cast(jj)] = - local_labels[static_cast(local_row_start) * static_cast(width) + static_cast(jj)]; - } +void MergeBoundaryLabels(int width, const std::vector &send_row, const std::vector &recv_row, + std::vector &parent, std::vector &rnk) { + for (int jj = 0; jj < width; ++jj) { + const int label_cur = send_row[static_cast(jj)]; + const int label_neighbor = recv_row[static_cast(jj)]; + if (label_cur != 0 && label_neighbor != 0 && label_cur != label_neighbor) { + Unite(parent, rnk, label_cur, label_neighbor); } + } +} - MPI_Sendrecv(send_row.data(), width, MPI_INT, rank - 1, 0, recv_row.data(), width, MPI_INT, rank - 1, 1, - MPI_COMM_WORLD, MPI_STATUS_IGNORE); +void ExchangeAndMergeRow(int width, int neighbor_rank, int send_tag, int recv_tag, bool has_rows, int row_index, + const std::vector &local_labels, std::vector &parent, std::vector &rnk) { + std::vector send_row(static_cast(width), 0); + std::vector recv_row(static_cast(width)); - for (int jj = 0; jj < width; ++jj) { - int label_cur = send_row[static_cast(jj)]; - int label_prev = recv_row[static_cast(jj)]; - if (label_cur != 0 && label_prev != 0 && label_cur != label_prev) { - Unite(parent, rnk, label_cur, label_prev); - } - } + if (has_rows) { + FillRowLabels(width, row_index, local_labels, send_row); } - if (rank < world_size - 1) { - std::vector send_row(static_cast(width), 0); - std::vector recv_row(static_cast(width)); - - if (local_row_start < local_row_end) { - const int last_row = local_row_end - 1; - for (int jj = 0; jj < width; ++jj) { - send_row[static_cast(jj)] = - local_labels[static_cast(last_row) * static_cast(width) + static_cast(jj)]; - } - } + MPI_Sendrecv(send_row.data(), width, MPI_INT, neighbor_rank, send_tag, recv_row.data(), width, MPI_INT, neighbor_rank, + recv_tag, MPI_COMM_WORLD, MPI_STATUS_IGNORE); - MPI_Sendrecv(send_row.data(), width, MPI_INT, rank + 1, 1, recv_row.data(), width, MPI_INT, rank + 1, 0, - MPI_COMM_WORLD, MPI_STATUS_IGNORE); + MergeBoundaryLabels(width, send_row, recv_row, parent, rnk); +} - for (int jj = 0; jj < width; ++jj) { - int label_cur = send_row[static_cast(jj)]; - int label_next = recv_row[static_cast(jj)]; - if (label_cur != 0 && label_next != 0 && label_cur != label_next) { - Unite(parent, rnk, label_cur, label_next); - } - } +void MergeMPIBoundaries(int width, int rank, int world_size, int local_row_start, int local_row_end, + const std::vector &local_labels, std::vector &parent, std::vector &rnk) { + const bool has_rows = local_row_start < local_row_end; + const int first_row = local_row_start; + const int last_row = has_rows ? (local_row_end - 1) : local_row_start; + + if (rank > 0) { + ExchangeAndMergeRow(width, rank - 1, 0, 1, has_rows, first_row, local_labels, parent, rnk); + } + + if (rank < world_size - 1) { + ExchangeAndMergeRow(width, rank + 1, 1, 0, has_rows, last_row, local_labels, parent, rnk); } } @@ -266,8 +264,8 @@ bool KondrashovaVTaskALL::RunImpl() { firstprivate(mpi_row_start, mpi_rows, max_labels_per_thread) { const int tid = omp_get_thread_num(); - const int omp_row_start = mpi_row_start + (tid * mpi_rows) / num_threads; - const int omp_row_end = mpi_row_start + ((tid + 1) * mpi_rows) / num_threads; + const int omp_row_start = mpi_row_start + ((tid * mpi_rows) / num_threads); + const int omp_row_end = mpi_row_start + (((tid + 1) * mpi_rows) / num_threads); const int label_offset = (rank_ * num_threads * max_labels_per_thread) + (tid * max_labels_per_thread); ScanStripe(omp_row_start, omp_row_end, width, label_offset, image, local_labels); } diff --git a/tasks/kondrashova_v_marking_components/report.md b/tasks/kondrashova_v_marking_components/report.md index d96bc83ed..8fb8fd9f0 100644 --- a/tasks/kondrashova_v_marking_components/report.md +++ b/tasks/kondrashova_v_marking_components/report.md @@ -1,160 +1,3 @@ -# Маркировка компонент связности на бинарном изображении (SEQ, OMP, TBB, STL, ALL) - -**Вариант № 29** -**Студент:** Кондрашова Виктория Андреевна -**Группа:** 3823Б1ПР1 -**Технологии:** SEQ, OMP, TBB, STL, ALL (MPI + OpenMP) - ---- - -## Введение - -Маркировка компонент связности на бинарном изображении является базовой задачей -компьютерного зрения и анализа изображений. Она используется для поиска объектов, -оценки их количества, размеров и формы в задачах распознавания, медицинской -визуализации и подготовки данных к дальнейшей обработке. - -Параллельные реализации позволяют обрабатывать изображения большого размера -быстрее, распределяя работу между потоками или процессами. В рамках работы -рассмотрены реализации для OpenMP, TBB, STL и гибридная версия с MPI + OpenMP. - -## Постановка задачи - -**Цель работы:** -Реализовать последовательную и параллельные версии алгоритма маркировки -компонент связности на бинарном изображении, протестировать корректность и -оценить производительность. - -**Определение задачи:** -На вход подается бинарное изображение, где `0` обозначает пиксель объекта, а -`1` — фон. Требуется пронумеровать все связные компоненты по 4-связности и -вычислить их количество. - -**Ограничения:** - -- Ширина и высота изображения неотрицательны. -- Массив данных содержит `width * height` элементов. -- Используется 4-связность (по горизонтали и вертикали). - -## Описание алгоритма (последовательная версия) - -Последовательный алгоритм выполняет обход изображения слева направо и сверху -вниз. При обнаружении неразмеченного пикселя объекта запускается BFS (поиск в -ширину), который размечает всю соответствующую компоненту связности. Для BFS -используется очередь, а каждое посещенное значение получает метку текущей -компоненты. После завершения обхода счетчик компонент увеличивается. - -## Схемы распараллеливания - -### OpenMP-версия - -Изображение делится на горизонтальные полосы по числу потоков. Каждый поток -размечает свою полосу, создавая локальные метки. Для объединения компонент, -пересекающих границы полос, используется структура DSU (Union-Find). После -объединения выполняется финальная нормализация меток. - -### TBB-версия - -Разделение по полосам аналогично OpenMP, однако параллельный цикл выполняется -через `tbb::parallel_for`. DSU используется для глобального объединения меток, -а последующая перенумерация выполняется в одном потоке. - -### STL-версия - -Используются `std::thread` для обработки полос. Каждая полоса размечается в -собственном потоке, затем выполняется объединение соседних полос через DSU и -перенумерация меток. - -### ALL-версия (MPI + OpenMP) - -Изображение распределяется между MPI-процессами по горизонтальным полосам. -Внутри каждого процесса применяются потоки OpenMP для локальной разметки. -После локальной разметки выполняется обмен граничными строками, объединение -меток на границах, затем `MPI_Allreduce` собирает глобальный массив меток. -На ранге 0 проводится итоговое объединение и перенумерация меток. - -## Окружение - -Сборка выполнялась с помощью CMake в режиме Release. Для корректного запуска -MPI-версии использовался `mpiexec` с двумя процессами и одним потоком OpenMP -на процесс. - -## Экспериментальные результаты - -### Аппаратное и программное окружение - -|Параметр|Значение| -|-----------|-----------| -|Процессор|AMD Ryzen 5 5600H| -|Операционная система|Windows 10 Pro 22H2| -|Компилятор|MSVC 19.37 (Visual Studio 2022)| -|Тип сборки|Release| - -### Проверка корректности - -Для SEQ/OMP/TBB/STL/ALL выполнены 65 функциональных тестов из -`tests/functional/main.cpp`, все тесты пройдены успешно. - -### Оценка производительности - -Производительность измерялась на изображениях размером 512×512 для шести типов -данных (all ones, all zeros, chessboard, sparse dots, stripes, blocks). -Замерялось время выполнения в режиме `task_run`, ускорение рассчитано -относительно SEQ для каждого изображения. - -|Изображение|Версия|Время, с|Ускорение| -|-----------|-----------|-----------|-----------| -|all ones|SEQ|0.000319|1.000| -|all ones|OMP|0.003854|0.083| -|all ones|TBB|0.003229|0.099| -|all ones|STL|0.004231|0.075| -|all ones|ALL (2 MPI)|0.004511|0.071| -|all zeros|SEQ|0.001509|1.000| -|all zeros|OMP|0.004510|0.335| -|all zeros|TBB|0.004198|0.359| -|all zeros|STL|0.005421|0.278| -|all zeros|ALL (2 MPI)|0.004339|0.348| -|chessboard|SEQ|0.009387|1.000| -|chessboard|OMP|0.003941|2.382| -|chessboard|TBB|0.003911|2.400| -|chessboard|STL|0.004667|2.011| -|chessboard|ALL (2 MPI)|0.003977|2.361| -|sparse dots|SEQ|0.000575|1.000| -|sparse dots|OMP|0.003556|0.162| -|sparse dots|TBB|0.003075|0.187| -|sparse dots|STL|0.003491|0.165| -|sparse dots|ALL (2 MPI)|0.003415|0.168| -|stripes|SEQ|0.000717|1.000| -|stripes|OMP|0.003375|0.212| -|stripes|TBB|0.003381|0.212| -|stripes|STL|0.003763|0.191| -|stripes|ALL (2 MPI)|0.003683|0.195| -|blocks|SEQ|0.000770|1.000| -|blocks|OMP|0.003507|0.220| -|blocks|TBB|0.003877|0.199| -|blocks|STL|0.004431|0.174| -|blocks|ALL (2 MPI)|0.003790|0.203| - -## Выводы - -Реализованы последовательная и четыре параллельные версии алгоритма -маркировки компонент связности. Все реализации прошли функциональное -тестирование и дали корректные результаты. - -На изображении типа chessboard параллельные версии показывают ускорение -порядка 2–2.4 раза, что объясняется равномерной нагрузкой и большим числом -компонент. На более простых изображениях накладные расходы на синхронизацию -превышают выигрыш от распараллеливания, что приводит к замедлению. - -Гибридная ALL-версия корректно работает при запуске через MPI и показывает -сопоставимые результаты с потоковыми версиями на указанной конфигурации. - -## Источники - -1. Лекции по курсу «Параллельное программирование». -2. OpenMP Application Program Interface, Version 5.2. -3. oneTBB Developer Guide. -4. MPI Standard, Version 4.1. # Маркировка компонент связности на бинарном изображении - Студент: Кондрашова Виктория Андреевна @@ -396,8 +239,6 @@ OpenMP-версия создает параллельную область, в Гибридная ALL-версия корректно работает при запуске через MPI и показывает сопоставимые результаты с потоковыми версиями на указанной конфигурации. - - ## 9. Источники 1. OpenMP API Specification — From 65d56fc87e861492e32c598fe5f92a23003cd872 Mon Sep 17 00:00:00 2001 From: Victoria Kondrashova Date: Sat, 18 Apr 2026 17:28:54 +0300 Subject: [PATCH 20/22] Update report.md --- tasks/kondrashova_v_marking_components/report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/kondrashova_v_marking_components/report.md b/tasks/kondrashova_v_marking_components/report.md index 8fb8fd9f0..0e428f490 100644 --- a/tasks/kondrashova_v_marking_components/report.md +++ b/tasks/kondrashova_v_marking_components/report.md @@ -2,7 +2,7 @@ - Студент: Кондрашова Виктория Андреевна - Группа: 3823Б1ПР1 -- Технологии: SEQ, OMP, TBB, STL, ALL (MPI + OpenMP) +- Технологии: SEQ, OMP, TBB, STL, ALL ## 1. Введение From 5b206391309b73642e4b653862fe96956d3a0d8b Mon Sep 17 00:00:00 2001 From: Victoria Kondrashova Date: Sat, 18 Apr 2026 21:14:10 +0300 Subject: [PATCH 21/22] Update report.md --- tasks/kondrashova_v_marking_components/report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasks/kondrashova_v_marking_components/report.md b/tasks/kondrashova_v_marking_components/report.md index 0e428f490..bde17adb3 100644 --- a/tasks/kondrashova_v_marking_components/report.md +++ b/tasks/kondrashova_v_marking_components/report.md @@ -12,7 +12,7 @@ Цель работы: реализовать последовательную и параллельные версии алгоритма маркировки компонент связности для бинарного изображения с использованием -технологий OpenMP, Intel TBB, `std::thread` и комбинированной схемы MPI + OpenMP. +технологий OpenMP, TBB, STL и комбинированной схемы MPI + OpenMP. ## 2. Постановка задачи @@ -101,7 +101,7 @@ OpenMP-версия создает параллельную область, в - `common/include/common.hpp` — типы входных и выходных данных - `seq/` — последовательная реализация на BFS - `omp/` — реализация с OpenMP -- `tbb/` — реализация с Intel TBB +- `tbb/` — реализация с TBB - `stl/` — реализация на `std::thread` - `all/` — гибридная реализация MPI + OpenMP - `tests/functional/main.cpp` — функциональные тесты From 3a7c3638f7d807284bb3eaf2a86bb32f46c43d94 Mon Sep 17 00:00:00 2001 From: Victoria Date: Sun, 19 Apr 2026 00:25:15 +0300 Subject: [PATCH 22/22] fix build --- .../all/src/ops_all.cpp | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp index e51685aaa..7b451be78 100644 --- a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp +++ b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp @@ -302,6 +302,16 @@ bool KondrashovaVTaskALL::RunImpl() { GetOutput().count = Relabel(total, all_local_labels, global_parent, relabel_map, labels_1d_); } + int global_count = GetOutput().count; + MPI_Bcast(&global_count, 1, MPI_INT, 0, MPI_COMM_WORLD); + GetOutput().count = global_count; + if (total > 0) { + if (labels_1d_.size() != static_cast(total)) { + labels_1d_.assign(static_cast(total), 0); + } + MPI_Bcast(labels_1d_.data(), total, MPI_INT, 0, MPI_COMM_WORLD); + } + return true; } @@ -311,13 +321,11 @@ bool KondrashovaVTaskALL::PostProcessingImpl() { return true; } - if (rank_ == 0) { - GetOutput().labels.assign(height_, std::vector(width_, 0)); - for (int ii = 0; ii < height_; ++ii) { - for (int jj = 0; jj < width_; ++jj) { - auto idx = (static_cast(ii) * static_cast(width_)) + static_cast(jj); - GetOutput().labels[ii][jj] = labels_1d_[idx]; - } + GetOutput().labels.assign(height_, std::vector(width_, 0)); + for (int ii = 0; ii < height_; ++ii) { + for (int jj = 0; jj < width_; ++jj) { + auto idx = (static_cast(ii) * static_cast(width_)) + static_cast(jj); + GetOutput().labels[ii][jj] = labels_1d_[idx]; } } return true;