diff --git a/tasks/artyushkina_markirovka/settings.json b/tasks/artyushkina_markirovka/settings.json index d01ca3d534..6bcc256d38 100644 --- a/tasks/artyushkina_markirovka/settings.json +++ b/tasks/artyushkina_markirovka/settings.json @@ -4,7 +4,8 @@ "mpi": "enabled", "omp": "enabled", "seq": "enabled", - "stl": "enabled" + "stl": "enabled", + "tbb": "enabled" }, "tasks_type": "processes" } diff --git a/tasks/artyushkina_markirovka/tbb/include/ops_tbb.hpp b/tasks/artyushkina_markirovka/tbb/include/ops_tbb.hpp new file mode 100644 index 0000000000..765fafbee5 --- /dev/null +++ b/tasks/artyushkina_markirovka/tbb/include/ops_tbb.hpp @@ -0,0 +1,48 @@ +#ifndef ARTYUSHKINA_MARKIROVKA_TBB_INCLUDE_OPS_TBB_HPP_ +#define ARTYUSHKINA_MARKIROVKA_TBB_INCLUDE_OPS_TBB_HPP_ + +#include + +#include + +#include "artyushkina_markirovka/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace artyushkina_markirovka { + +class MarkingComponentsTBB : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kTBB; + } + explicit MarkingComponentsTBB(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + int FindRoot(int label); + void UnionLabels(int label1, int label2); + + void InitLabelsTbb(); + void MergeHorizontalPairsTbb(); + void MergeVerticalPairsTbb(); + void MergeDiagonalPairsTbb(); + void FinalizeRootsTbb(); + void NormalizeLabelsTbb(); + + int rows_ = 0; + int cols_ = 0; + std::vector labels_; + std::vector parent_; + InType input_; + int current_label_ = 0; + + mutable tbb::spin_mutex dsu_mutex_; +}; + +} // namespace artyushkina_markirovka + +#endif diff --git a/tasks/artyushkina_markirovka/tbb/src/ops_tbb.cpp b/tasks/artyushkina_markirovka/tbb/src/ops_tbb.cpp new file mode 100644 index 0000000000..ddb9c4a77d --- /dev/null +++ b/tasks/artyushkina_markirovka/tbb/src/ops_tbb.cpp @@ -0,0 +1,188 @@ +#include "artyushkina_markirovka/tbb/include/ops_tbb.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +#include "artyushkina_markirovka/common/include/common.hpp" + +namespace artyushkina_markirovka { + +MarkingComponentsTBB::MarkingComponentsTBB(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = OutType(); +} + +bool MarkingComponentsTBB::ValidationImpl() { + return GetInput().size() >= 2; +} + +bool MarkingComponentsTBB::PreProcessingImpl() { + const auto &input = GetInput(); + rows_ = static_cast(input[0]); + cols_ = static_cast(input[1]); + input_ = input; + + int total_pixels = rows_ * cols_; + labels_.assign(total_pixels, 0); + parent_.resize(total_pixels + 1); + for (int i = 0; i <= total_pixels; ++i) { + parent_[i] = i; + } + + current_label_ = 0; + return true; +} + +int MarkingComponentsTBB::FindRoot(int label) { + int root = label; + while (parent_[root] != root) { + root = parent_[root]; + } + int current = label; + while (parent_[current] != current) { + int next = parent_[current]; + parent_[current] = root; + current = next; + } + return root; +} + +void MarkingComponentsTBB::UnionLabels(int label1, int label2) { + tbb::spin_mutex::scoped_lock lock(dsu_mutex_); + int root1 = FindRoot(label1); + int root2 = FindRoot(label2); + if (root1 != root2) { + if (root1 < root2) { + parent_[root2] = root1; + } else { + parent_[root1] = root2; + } + } +} + +void MarkingComponentsTBB::InitLabelsTbb() { + int total_pixels = rows_ * cols_; + tbb::parallel_for(0, total_pixels, [this](int idx) { + size_t input_idx = static_cast(idx) + 2; + if (input_[input_idx] == 0) { + labels_[idx] = idx + 1; + } + }); +} + +void MarkingComponentsTBB::MergeHorizontalPairsTbb() { + tbb::parallel_for(0, rows_, [this](int y_coord) { + for (int x_coord = 0; x_coord < cols_ - 1; ++x_coord) { + int idx = (y_coord * cols_) + x_coord; + if (labels_[idx] != 0 && labels_[idx + 1] != 0) { + UnionLabels(labels_[idx], labels_[idx + 1]); + } + } + }); +} + +void MarkingComponentsTBB::MergeVerticalPairsTbb() { + tbb::parallel_for(0, rows_ - 1, [this](int y_coord) { + for (int x_coord = 0; x_coord < cols_; ++x_coord) { + int idx = (y_coord * cols_) + x_coord; + if (labels_[idx] != 0 && labels_[idx + cols_] != 0) { + UnionLabels(labels_[idx], labels_[idx + cols_]); + } + } + }); +} + +void MarkingComponentsTBB::MergeDiagonalPairsTbb() { + tbb::parallel_for(0, rows_ - 1, [this](int y_coord) { + for (int x_coord = 0; x_coord < cols_ - 1; ++x_coord) { + int idx = (y_coord * cols_) + x_coord; + if (labels_[idx] != 0 && labels_[idx + cols_ + 1] != 0) { + UnionLabels(labels_[idx], labels_[idx + cols_ + 1]); + } + if (x_coord > 0) { + if (labels_[idx] != 0 && labels_[idx + cols_ - 1] != 0) { + UnionLabels(labels_[idx], labels_[idx + cols_ - 1]); + } + } + } + }); +} + +void MarkingComponentsTBB::FinalizeRootsTbb() { + int total_pixels = rows_ * cols_; + tbb::parallel_for(0, total_pixels, [this](int i) { + if (labels_[i] != 0) { + labels_[i] = FindRoot(labels_[i]); + } + }); +} + +void MarkingComponentsTBB::NormalizeLabelsTbb() { + int total_pixels = rows_ * cols_; + std::vector unique_roots; + for (int i = 0; i < total_pixels; ++i) { + if (labels_[i] != 0) { + unique_roots.push_back(labels_[i]); + } + } + + if (unique_roots.empty()) { + return; + } + + std::ranges::sort(unique_roots); + auto last = std::ranges::unique(unique_roots); + unique_roots.erase(last.begin(), last.end()); + + std::vector mapping(total_pixels + 1, 0); + int next_id = 1; + for (int root : unique_roots) { + mapping[root] = next_id++; + } + + for (int i = 0; i < total_pixels; ++i) { + if (labels_[i] != 0) { + labels_[i] = mapping[labels_[i]]; + } + } + current_label_ = next_id - 1; +} + +bool MarkingComponentsTBB::RunImpl() { + int total_pixels = rows_ * cols_; + if (total_pixels <= 0) { + return true; + } + + InitLabelsTbb(); + MergeHorizontalPairsTbb(); + MergeVerticalPairsTbb(); + MergeDiagonalPairsTbb(); + FinalizeRootsTbb(); + NormalizeLabelsTbb(); + + return true; +} + +bool MarkingComponentsTBB::PostProcessingImpl() { + OutType &output = GetOutput(); + output.clear(); + + output.push_back(static_cast(rows_)); + output.push_back(static_cast(cols_)); + + for (int label : labels_) { + output.push_back(static_cast(label)); + } + + return true; +} + +} // namespace artyushkina_markirovka diff --git a/tasks/artyushkina_markirovka/tests/functional/main.cpp b/tasks/artyushkina_markirovka/tests/functional/main.cpp index 6f04d1aaa4..788c82ea22 100644 --- a/tasks/artyushkina_markirovka/tests/functional/main.cpp +++ b/tasks/artyushkina_markirovka/tests/functional/main.cpp @@ -11,6 +11,7 @@ #include "artyushkina_markirovka/omp/include/ops_omp.hpp" #include "artyushkina_markirovka/seq/include/ops_seq.hpp" #include "artyushkina_markirovka/stl/include/ops_stl.hpp" +#include "artyushkina_markirovka/tbb/include/ops_tbb.hpp" #include "util/include/func_test_util.hpp" #include "util/include/util.hpp" @@ -290,6 +291,88 @@ class ArtyushkinaMarkirovkaFuncTestsSTL : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::to_string(std::get<0>(test_param)) + "_" + std::get<1>(test_param); + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + int test_id = std::get<0>(params); + + switch (test_id) { + case 0: { + input_data_ = {3, 3, 0, 0, 255, 0, 255, 255, 255, 255, 0}; + expected_ = {3, 3, 1, 1, 0, 1, 0, 0, 0, 0, 2}; + break; + } + case 1: { + input_data_ = {3, 3, 0, 0, 0, 0, 255, 0, 0, 0, 255}; + expected_ = {3, 3, 1, 1, 1, 1, 0, 1, 1, 1, 0}; + break; + } + case 2: { + input_data_ = {2, 3, 255, 255, 255, 255, 255, 255}; + expected_ = {2, 3, 0, 0, 0, 0, 0, 0}; + break; + } + case 3: { + input_data_ = {2, 2, 0, 0, 0, 0}; + expected_ = {2, 2, 1, 1, 1, 1}; + break; + } + case 4: { + input_data_ = {3, 4, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0}; + expected_ = {3, 4, 1, 1, 1, 1, 0, 0, 0, 0, 2, 2, 2, 2}; + break; + } + case 6: { + input_data_ = {2, 2, 0, 255, 255, 0}; + expected_ = {2, 2, 1, 0, 0, 1}; + break; + } + default: + break; + } + } + + bool CheckTestOutputData(OutType &output_data) final { + int test_id = std::get<0>(std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam())); + + if (test_id == 6) { + std::cout << "\n=== INPUT MATRIX (test " << test_id << ") ===\n"; + PrintInputMatrix(input_data_); + std::cout << "========================\n\n"; + PrintMatrix(output_data, "=== OUTPUT LABELS ===\n"); + } + + if (output_data != expected_) { + std::cout << "Expected: "; + for (auto val : expected_) { + std::cout << static_cast(val) << ' '; + } + std::cout << '\n'; + std::cout << "Actual : "; + for (auto val : output_data) { + std::cout << val << ' '; + } + std::cout << '\n'; + } + + return output_data == expected_; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; + OutType expected_; +}; + class ArtyushkinaMarkirovkaFuncTestsALL : public ppc::util::BaseRunFuncTests { public: static std::string PrintTestParam(const TestType &test_param) { @@ -436,6 +519,26 @@ const auto kPerfTestNameSTL = ArtyushkinaMarkirovkaFuncTestsSTL::PrintFuncTestNa INSTANTIATE_TEST_SUITE_P(ComponentLabelingSTL, ArtyushkinaMarkirovkaFuncTestsSTL, kGtestValuesSTL, kPerfTestNameSTL); +TEST_P(ArtyushkinaMarkirovkaFuncTestsTBB, MarkingComponentsTBB) { + ExecuteTest(GetParam()); +} + +const std::array kTestParamTBB = {std::make_tuple(0, "L_shaped_component_8connectivity"), + std::make_tuple(1, "diagonal_connected_components"), + std::make_tuple(2, "all_background"), + std::make_tuple(3, "all_objects"), + std::make_tuple(4, "two_horizontal_bars"), + std::make_tuple(6, "diagonal_connectivity_check")}; + +const auto kTestTasksListTBB = + ppc::util::AddFuncTask(kTestParamTBB, PPC_SETTINGS_artyushkina_markirovka); + +const auto kGtestValuesTBB = ppc::util::ExpandToValues(kTestTasksListTBB); + +const auto kPerfTestNameTBB = ArtyushkinaMarkirovkaFuncTestsTBB::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(ComponentLabelingTBB, ArtyushkinaMarkirovkaFuncTestsTBB, kGtestValuesTBB, kPerfTestNameTBB); + TEST_P(ArtyushkinaMarkirovkaFuncTestsALL, MarkingComponentsALL) { ExecuteTest(GetParam()); } diff --git a/tasks/artyushkina_markirovka/tests/performance/main.cpp b/tasks/artyushkina_markirovka/tests/performance/main.cpp index 825edb0f70..028283f578 100644 --- a/tasks/artyushkina_markirovka/tests/performance/main.cpp +++ b/tasks/artyushkina_markirovka/tests/performance/main.cpp @@ -10,6 +10,7 @@ #include "artyushkina_markirovka/omp/include/ops_omp.hpp" #include "artyushkina_markirovka/seq/include/ops_seq.hpp" #include "artyushkina_markirovka/stl/include/ops_stl.hpp" +#include "artyushkina_markirovka/tbb/include/ops_tbb.hpp" #include "util/include/perf_test_util.hpp" namespace artyushkina_markirovka { @@ -161,6 +162,16 @@ const auto kPerfTestNameSTL = ArtyushkinaMarkirovkaPerfTests::CustomPerfTestName INSTANTIATE_TEST_SUITE_P(RunModeTestsSTL, ArtyushkinaMarkirovkaPerfTests, kGtestValuesSTL, kPerfTestNameSTL); +// TBB performance tests +const auto kAllPerfTasksTBB = + ppc::util::MakeAllPerfTasks(PPC_SETTINGS_artyushkina_markirovka); + +const auto kGtestValuesTBB = ppc::util::TupleToGTestValues(kAllPerfTasksTBB); + +const auto kPerfTestNameTBB = ArtyushkinaMarkirovkaPerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTestsTBB, ArtyushkinaMarkirovkaPerfTests, kGtestValuesTBB, kPerfTestNameTBB); + const auto kAllPerfTasksALL = ppc::util::MakeAllPerfTasks(PPC_SETTINGS_artyushkina_markirovka);