From eca67878432d324b9dd486281d2b56ad5c2cbc92 Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Wed, 20 May 2026 19:10:08 +0300 Subject: [PATCH 01/18] TBB --- .../tests/functional/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp b/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp index f55570dcb6..157b1d41b8 100644 --- a/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp @@ -39,7 +39,7 @@ class YushkovaPRunFuncTestsThreads : public ppc::util::BaseRunFuncTests expected = input_data_; - std::ranges::sort(expected); + std::sort(expected.begin(), expected.end()); return output_data == expected; } From 0e5da695890811be4fcdd40eebf62793246c0c61 Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Wed, 20 May 2026 19:14:58 +0300 Subject: [PATCH 02/18] TBB --- .../tbb/src/ops_tbb.cpp | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp b/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp index 6aa26d7ed9..f6297f3b91 100644 --- a/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp @@ -171,6 +171,7 @@ bool YushkovaPHoareSortingSimpleMergingTBB::PreProcessingImpl() { bool YushkovaPHoareSortingSimpleMergingTBB::RunImpl() { const size_t size = data_.size(); +<<<<<<< HEAD if (size == 0) { GetOutput().clear(); @@ -188,10 +189,58 @@ bool YushkovaPHoareSortingSimpleMergingTBB::RunImpl() { } if (std::ranges::is_sorted(data_)) { +======= + if (size <= 1) { +>>>>>>> 68f721f8 (TBB) GetOutput() = data_; return true; } +<<<<<<< HEAD +======= + const size_t block_count = (size + kBlockSize - 1) / kBlockSize; + + oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, block_count), + [this, size](const oneapi::tbb::blocked_range &range) { + for (size_t block_index = range.begin(); block_index != range.end(); ++block_index) { + const size_t block_start = block_index * kBlockSize; + const size_t block_end = std::min(block_start + kBlockSize, size); + if (block_end > block_start + 1) { + HoareQuickSort(data_, static_cast(block_start), + static_cast(block_end - 1)); + } + } + }); + + std::vector merged_data(size); + + for (size_t merge_width = kBlockSize; merge_width < size; merge_width *= 2) { + const size_t merge_count = (size + (2 * merge_width) - 1) / (2 * merge_width); + + oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, merge_count), + [this, size, merge_width, &merged_data](const oneapi::tbb::blocked_range &range) { + for (size_t merge_index = range.begin(); merge_index != range.end(); ++merge_index) { + const size_t left = merge_index * 2 * merge_width; + const size_t middle = std::min(left + merge_width, size); + const size_t right = std::min(left + 2 * merge_width, size); + if (middle < right) { + SimpleMerge(data_, merged_data, left, middle, right); + } else { + std::copy(data_.begin() + static_cast(left), + data_.begin() + static_cast(right), + merged_data.begin() + static_cast(left)); + } + } + }); + data_.swap(merged_data); + } + + if (std::is_sorted(data_.begin(), data_.end())) { + GetOutput().assign(data_.begin(), data_.end()); + return true; + } + +>>>>>>> 68f721f8 (TBB) return false; } From 5ccea5822b33ddd4fed8d9da7eeae899e6949593 Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Fri, 15 May 2026 18:33:20 +0300 Subject: [PATCH 03/18] TBB --- .../tbb/src/ops_tbb.cpp | 50 ------------------- 1 file changed, 50 deletions(-) diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp b/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp index f6297f3b91..39ad261083 100644 --- a/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp @@ -171,8 +171,6 @@ bool YushkovaPHoareSortingSimpleMergingTBB::PreProcessingImpl() { bool YushkovaPHoareSortingSimpleMergingTBB::RunImpl() { const size_t size = data_.size(); -<<<<<<< HEAD - if (size == 0) { GetOutput().clear(); return true; @@ -189,58 +187,10 @@ bool YushkovaPHoareSortingSimpleMergingTBB::RunImpl() { } if (std::ranges::is_sorted(data_)) { -======= - if (size <= 1) { ->>>>>>> 68f721f8 (TBB) GetOutput() = data_; return true; } -<<<<<<< HEAD -======= - const size_t block_count = (size + kBlockSize - 1) / kBlockSize; - - oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, block_count), - [this, size](const oneapi::tbb::blocked_range &range) { - for (size_t block_index = range.begin(); block_index != range.end(); ++block_index) { - const size_t block_start = block_index * kBlockSize; - const size_t block_end = std::min(block_start + kBlockSize, size); - if (block_end > block_start + 1) { - HoareQuickSort(data_, static_cast(block_start), - static_cast(block_end - 1)); - } - } - }); - - std::vector merged_data(size); - - for (size_t merge_width = kBlockSize; merge_width < size; merge_width *= 2) { - const size_t merge_count = (size + (2 * merge_width) - 1) / (2 * merge_width); - - oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, merge_count), - [this, size, merge_width, &merged_data](const oneapi::tbb::blocked_range &range) { - for (size_t merge_index = range.begin(); merge_index != range.end(); ++merge_index) { - const size_t left = merge_index * 2 * merge_width; - const size_t middle = std::min(left + merge_width, size); - const size_t right = std::min(left + 2 * merge_width, size); - if (middle < right) { - SimpleMerge(data_, merged_data, left, middle, right); - } else { - std::copy(data_.begin() + static_cast(left), - data_.begin() + static_cast(right), - merged_data.begin() + static_cast(left)); - } - } - }); - data_.swap(merged_data); - } - - if (std::is_sorted(data_.begin(), data_.end())) { - GetOutput().assign(data_.begin(), data_.end()); - return true; - } - ->>>>>>> 68f721f8 (TBB) return false; } From be58cefa1a1240ed16af0324c20c74c391468023 Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Wed, 20 May 2026 19:28:19 +0300 Subject: [PATCH 04/18] TBB --- .../tbb/src/ops_tbb.cpp | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp b/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp index 39ad261083..ae17e473fe 100644 --- a/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp @@ -47,7 +47,11 @@ int YushkovaPHoareSortingSimpleMergingTBB::HoarePartition(std::vector &valu } void YushkovaPHoareSortingSimpleMergingTBB::HoareQuickSort(std::vector &values, int left, int right) { +<<<<<<< HEAD if (left >= right || static_cast(left) >= values.size() || static_cast(right) >= values.size()) { +======= + if (left >= right || static_cast(right) >= values.size()) { +>>>>>>> 9b3a1122 (TBB) return; } @@ -176,6 +180,7 @@ bool YushkovaPHoareSortingSimpleMergingTBB::RunImpl() { return true; } +<<<<<<< HEAD if (size == 1) { GetOutput().assign(1, data_[0]); return true; @@ -187,6 +192,54 @@ bool YushkovaPHoareSortingSimpleMergingTBB::RunImpl() { } if (std::ranges::is_sorted(data_)) { +======= + const size_t block_count = (size + kBlockSize - 1) / kBlockSize; + + oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, block_count), + [this, size](const oneapi::tbb::blocked_range &range) { + for (size_t block_index = range.begin(); block_index != range.end(); ++block_index) { + const size_t block_start = block_index * kBlockSize; + const size_t block_end = std::min(block_start + kBlockSize, size); + if (block_end > block_start + 1 && block_end <= size) { + HoareQuickSort(data_, static_cast(block_start), static_cast(block_end - 1)); + } + } + }); + + std::vector merged_data(size); + + for (size_t merge_width = kBlockSize; merge_width < size; merge_width *= 2) { + const size_t merge_count = (size + (2 * merge_width) - 1) / (2 * merge_width); + + oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, merge_count), + [this, size, merge_width, &merged_data](const oneapi::tbb::blocked_range &range) { + for (size_t merge_index = range.begin(); merge_index != range.end(); ++merge_index) { + const size_t left = merge_index * 2 * merge_width; + const size_t middle = std::min(left + merge_width, size); + const size_t right = std::min(left + 2 * merge_width, size); + + if (left >= size) { + continue; + } + + if (middle < right) { + SimpleMerge(data_, merged_data, left, middle, right); + } else if (left < right) { + const ptrdiff_t left_offset = static_cast(left); + const ptrdiff_t right_offset = static_cast(right); + const ptrdiff_t dest_offset = static_cast(left); + + if (left_offset >= 0 && right_offset <= static_cast(size) && dest_offset >= 0) { + std::copy(data_.begin() + left_offset, data_.begin() + right_offset, merged_data.begin() + dest_offset); + } + } + } + }); + data_.swap(merged_data); + } + + if (std::is_sorted(data_.begin(), data_.end())) { +>>>>>>> 9b3a1122 (TBB) GetOutput() = data_; return true; } From 50f38f2fb186f43d5717e7299e6b7849269f29d6 Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Fri, 15 May 2026 20:02:04 +0300 Subject: [PATCH 05/18] TBB --- .../tbb/src/ops_tbb.cpp | 54 +------------------ 1 file changed, 1 insertion(+), 53 deletions(-) diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp b/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp index ae17e473fe..6aa26d7ed9 100644 --- a/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/src/ops_tbb.cpp @@ -47,11 +47,7 @@ int YushkovaPHoareSortingSimpleMergingTBB::HoarePartition(std::vector &valu } void YushkovaPHoareSortingSimpleMergingTBB::HoareQuickSort(std::vector &values, int left, int right) { -<<<<<<< HEAD if (left >= right || static_cast(left) >= values.size() || static_cast(right) >= values.size()) { -======= - if (left >= right || static_cast(right) >= values.size()) { ->>>>>>> 9b3a1122 (TBB) return; } @@ -175,12 +171,12 @@ bool YushkovaPHoareSortingSimpleMergingTBB::PreProcessingImpl() { bool YushkovaPHoareSortingSimpleMergingTBB::RunImpl() { const size_t size = data_.size(); + if (size == 0) { GetOutput().clear(); return true; } -<<<<<<< HEAD if (size == 1) { GetOutput().assign(1, data_[0]); return true; @@ -192,54 +188,6 @@ bool YushkovaPHoareSortingSimpleMergingTBB::RunImpl() { } if (std::ranges::is_sorted(data_)) { -======= - const size_t block_count = (size + kBlockSize - 1) / kBlockSize; - - oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, block_count), - [this, size](const oneapi::tbb::blocked_range &range) { - for (size_t block_index = range.begin(); block_index != range.end(); ++block_index) { - const size_t block_start = block_index * kBlockSize; - const size_t block_end = std::min(block_start + kBlockSize, size); - if (block_end > block_start + 1 && block_end <= size) { - HoareQuickSort(data_, static_cast(block_start), static_cast(block_end - 1)); - } - } - }); - - std::vector merged_data(size); - - for (size_t merge_width = kBlockSize; merge_width < size; merge_width *= 2) { - const size_t merge_count = (size + (2 * merge_width) - 1) / (2 * merge_width); - - oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0, merge_count), - [this, size, merge_width, &merged_data](const oneapi::tbb::blocked_range &range) { - for (size_t merge_index = range.begin(); merge_index != range.end(); ++merge_index) { - const size_t left = merge_index * 2 * merge_width; - const size_t middle = std::min(left + merge_width, size); - const size_t right = std::min(left + 2 * merge_width, size); - - if (left >= size) { - continue; - } - - if (middle < right) { - SimpleMerge(data_, merged_data, left, middle, right); - } else if (left < right) { - const ptrdiff_t left_offset = static_cast(left); - const ptrdiff_t right_offset = static_cast(right); - const ptrdiff_t dest_offset = static_cast(left); - - if (left_offset >= 0 && right_offset <= static_cast(size) && dest_offset >= 0) { - std::copy(data_.begin() + left_offset, data_.begin() + right_offset, merged_data.begin() + dest_offset); - } - } - } - }); - data_.swap(merged_data); - } - - if (std::is_sorted(data_.begin(), data_.end())) { ->>>>>>> 9b3a1122 (TBB) GetOutput() = data_; return true; } From e7d7d9eb808010996710333c5f055e8cf2477077 Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Sat, 16 May 2026 16:32:55 +0300 Subject: [PATCH 06/18] TBB --- .../tests/functional/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp b/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp index 157b1d41b8..f55570dcb6 100644 --- a/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp @@ -39,7 +39,7 @@ class YushkovaPRunFuncTestsThreads : public ppc::util::BaseRunFuncTests expected = input_data_; - std::sort(expected.begin(), expected.end()); + std::ranges::sort(expected); return output_data == expected; } From 5b4e6ff435bbbd4ceaf4a782937f77ebb2c64e18 Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Sat, 16 May 2026 17:32:59 +0300 Subject: [PATCH 07/18] Rerun tests From 326cd9da7a84bd0ebb4c03622925bfa905855692 Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Sat, 16 May 2026 18:41:52 +0300 Subject: [PATCH 08/18] TBB From 0d8da7744011f1d395bf3c41961ddb927774ce87 Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Sat, 16 May 2026 20:01:28 +0300 Subject: [PATCH 09/18] TBB From 2db15ee8975f24083297cb31fc79b190b6e57cb9 Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Mon, 18 May 2026 02:44:14 +0300 Subject: [PATCH 10/18] TBB --- .../tests/functional/main.cpp | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp b/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp index f55570dcb6..f2d83fad98 100644 --- a/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -61,7 +62,7 @@ TEST_P(YushkovaPRunFuncTestsThreads, HoareSortSimpleMergingSEQ) { ExecuteTest(GetParam()); } -const std::array kTestParam = { +const std::array kTestParam = { std::make_tuple(std::vector{42}, "single"), std::make_tuple(std::vector{2, 1}, "two_elements"), std::make_tuple(std::vector{1, 2, 3, 4, 5}, "already_sorted"), @@ -71,7 +72,44 @@ const std::array kTestParam = { std::make_tuple(std::vector{10, 3, 8, 6, 4, 9, 2, 7, 1, 5}, "random_10"), std::make_tuple(std::vector{100, 1, 50, 2, 75, 3, 60, 4, 20, 5, 30}, "odd_count"), std::make_tuple(std::vector{9, 9, 8, 8, 7, 7, 6, 6, 5, 5}, "pair_duplicates"), - std::make_tuple(std::vector{1000, -1000, 500, -500, 0, 250, -250}, "wide_range")}; + std::make_tuple(std::vector{1000, -1000, 500, -500, 0, 250, -250}, "wide_range"), + std::make_tuple(std::vector(64, 7), "one_block_equal"), + std::make_tuple([] { + std::vector values(64); + std::iota(values.begin(), values.end(), 0); + return values; + }(), "one_block_sorted"), + std::make_tuple([] { + std::vector values(64); + std::iota(values.rbegin(), values.rend(), 0); + return values; + }(), "one_block_reverse"), + std::make_tuple([] { + std::vector values(65); + std::iota(values.begin(), values.end(), -32); + return values; + }(), "crosses_block_65"), + std::make_tuple([] { + std::vector values(128); + for (std::size_t i = 0; i < values.size(); ++i) { + values[i] = static_cast(values.size() - i); + } + return values; + }(), "crosses_two_blocks_128"), + std::make_tuple([] { + std::vector values(129); + for (std::size_t i = 0; i < values.size(); ++i) { + values[i] = static_cast((i % 7) - 3); + } + return values; + }(), "crosses_two_blocks_129"), + std::make_tuple([] { + std::vector values(130); + for (std::size_t i = 0; i < values.size(); ++i) { + values[i] = static_cast(130 - i); + } + return values; + }(), "crosses_two_blocks_130")}; const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask( kTestParam, PPC_SETTINGS_yushkova_p_hoare_sorting_simple_merging), From ae9f14c45885d8391b5f7c2973f4272dcf2b4d15 Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Mon, 18 May 2026 02:47:25 +0300 Subject: [PATCH 11/18] TBB From cadca43ebc322d40e5e24d17649ca9d7685ceb7d Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Mon, 18 May 2026 16:59:13 +0300 Subject: [PATCH 12/18] TBB --- .../tests/functional/main.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp b/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp index f2d83fad98..4f4d2dd1fd 100644 --- a/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -76,17 +77,17 @@ const std::array kTestParam = { std::make_tuple(std::vector(64, 7), "one_block_equal"), std::make_tuple([] { std::vector values(64); - std::iota(values.begin(), values.end(), 0); + std::ranges::iota(values, 0); return values; }(), "one_block_sorted"), std::make_tuple([] { std::vector values(64); - std::iota(values.rbegin(), values.rend(), 0); + std::ranges::iota(std::views::reverse(values), 0); return values; }(), "one_block_reverse"), std::make_tuple([] { std::vector values(65); - std::iota(values.begin(), values.end(), -32); + std::ranges::iota(values, -32); return values; }(), "crosses_block_65"), std::make_tuple([] { From df37791625d685a2dbbaa77a62dafe2ff8aa9ffe Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Mon, 18 May 2026 17:29:32 +0300 Subject: [PATCH 13/18] TBB --- .../tests/functional/main.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp b/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp index 4f4d2dd1fd..b8c724ab81 100644 --- a/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp @@ -4,8 +4,6 @@ #include #include #include -#include -#include #include #include #include @@ -77,17 +75,24 @@ const std::array kTestParam = { std::make_tuple(std::vector(64, 7), "one_block_equal"), std::make_tuple([] { std::vector values(64); - std::ranges::iota(values, 0); + for (std::size_t i = 0; i < values.size(); ++i) { + values[i] = static_cast(i); + } return values; }(), "one_block_sorted"), std::make_tuple([] { std::vector values(64); - std::ranges::iota(std::views::reverse(values), 0); + for (std::size_t i = 0; i < values.size(); ++i) { + values[i] = static_cast(i); + } + std::ranges::reverse(values); return values; }(), "one_block_reverse"), std::make_tuple([] { std::vector values(65); - std::ranges::iota(values, -32); + for (std::size_t i = 0; i < values.size(); ++i) { + values[i] = static_cast(i) - 32; + } return values; }(), "crosses_block_65"), std::make_tuple([] { From 9af4953371e7fb6060ddd4b5e07475e5b0d9693b Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Mon, 18 May 2026 20:42:51 +0300 Subject: [PATCH 14/18] Rerun tests From 6108d1bbc3532a77f6a25595aa6d267b705cee94 Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Thu, 21 May 2026 03:21:42 +0300 Subject: [PATCH 15/18] ALL --- .../all/include/ops_all.hpp | 34 ++ .../all/src/ops_all.cpp | 295 ++++++++++++++++++ .../tests/functional/main.cpp | 60 +--- .../tests/performance/main.cpp | 14 +- 4 files changed, 356 insertions(+), 47 deletions(-) create mode 100644 tasks/yushkova_p_hoare_sorting_simple_merging/all/include/ops_all.hpp create mode 100644 tasks/yushkova_p_hoare_sorting_simple_merging/all/src/ops_all.cpp diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/all/include/ops_all.hpp b/tasks/yushkova_p_hoare_sorting_simple_merging/all/include/ops_all.hpp new file mode 100644 index 0000000000..ac84b7da68 --- /dev/null +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/all/include/ops_all.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" +#include "yushkova_p_hoare_sorting_simple_merging/common/include/common.hpp" + +namespace yushkova_p_hoare_sorting_simple_merging { + +class YushkovaPHoareSortingSimpleMergingALL : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kALL; + } + explicit YushkovaPHoareSortingSimpleMergingALL(const InType &in); + + private: + static int HoarePartition(std::vector &values, int left, int right); + static void HoareQuickSort(std::vector &values, int left, int right); + static void SimpleMerge(const std::vector &source, std::vector &destination, std::size_t left, + std::size_t middle, std::size_t right); + static void SortLocalStlParallel(std::vector &values); + static void MergeGatheredChunks(std::vector &values, const std::vector &chunk_sizes, + const std::vector &offsets); + static void BroadcastVector(std::vector &values, int rank); + + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace yushkova_p_hoare_sorting_simple_merging diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/all/src/ops_all.cpp b/tasks/yushkova_p_hoare_sorting_simple_merging/all/src/ops_all.cpp new file mode 100644 index 0000000000..96df88d692 --- /dev/null +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/all/src/ops_all.cpp @@ -0,0 +1,295 @@ +#include "yushkova_p_hoare_sorting_simple_merging/all/include/ops_all.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "yushkova_p_hoare_sorting_simple_merging/common/include/common.hpp" + +namespace yushkova_p_hoare_sorting_simple_merging { + +namespace { + +constexpr std::size_t kBlockSize = 64; + +std::size_t GetThreadCount(std::size_t task_count) { + if (task_count == 0) { + return 0; + } + const unsigned int hardware_threads = std::thread::hardware_concurrency(); + const std::size_t available_threads = hardware_threads == 0 ? 2 : static_cast(hardware_threads); + return std::min(task_count, available_threads); +} + +template +void RunSequentialTasks(std::size_t task_count, Function function) { + for (std::size_t task_index = 0; task_index < task_count; ++task_index) { + function(task_index); + } +} + +template +void RunThreadWorker(std::size_t thread_index, std::size_t thread_count, std::size_t task_count, Function &function, + std::exception_ptr &exception_ptr, std::mutex &exception_mutex) { + try { + for (std::size_t task_index = thread_index; task_index < task_count; task_index += thread_count) { + function(task_index); + } + } catch (...) { + std::scoped_lock lock(exception_mutex); + if (!exception_ptr) { + exception_ptr = std::current_exception(); + } + } +} + +template +void RunInThreads(std::size_t task_count, Function function) { + const std::size_t thread_count = GetThreadCount(task_count); + if (thread_count <= 1) { + RunSequentialTasks(task_count, function); + return; + } + + std::vector threads; + threads.reserve(thread_count); + std::exception_ptr exception_ptr; + std::mutex exception_mutex; + for (std::size_t thread_index = 0; thread_index < thread_count; ++thread_index) { + threads.emplace_back([thread_index, thread_count, task_count, &function, &exception_ptr, &exception_mutex]() { + RunThreadWorker(thread_index, thread_count, task_count, function, exception_ptr, exception_mutex); + }); + } + + for (auto &thread : threads) { + thread.join(); + } + + if (exception_ptr) { + std::rethrow_exception(exception_ptr); + } +} + +std::vector MakeIntVector(const std::vector &values) { + std::vector result(values.size()); + for (std::size_t i = 0; i < values.size(); ++i) { + result[i] = static_cast(values[i]); + } + return result; +} + +void BuildDistribution(std::size_t total_size, int mpi_size, std::vector &chunk_sizes, + std::vector &offsets) { + const std::size_t base_size = total_size / static_cast(mpi_size); + const std::size_t remainder = total_size % static_cast(mpi_size); + for (int rank = 0; rank < mpi_size; ++rank) { + const auto index = static_cast(rank); + chunk_sizes[index] = base_size + (index < remainder ? 1U : 0U); + offsets[index] = rank == 0 ? 0 : offsets[index - 1] + chunk_sizes[index - 1]; + } +} + +} // namespace + +YushkovaPHoareSortingSimpleMergingALL::YushkovaPHoareSortingSimpleMergingALL(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput().clear(); +} + +int YushkovaPHoareSortingSimpleMergingALL::HoarePartition(std::vector &values, int left, int right) { + const int pivot = values[left + ((right - left) / 2)]; + int i = left - 1; + int j = right + 1; + + while (true) { + ++i; + while (values[i] < pivot) { + ++i; + } + + --j; + while (values[j] > pivot) { + --j; + } + + if (i >= j) { + return j; + } + + std::swap(values[i], values[j]); + } +} + +void YushkovaPHoareSortingSimpleMergingALL::HoareQuickSort(std::vector &values, int left, int right) { + std::stack> ranges; + ranges.emplace(left, right); + + while (!ranges.empty()) { + auto [current_left, current_right] = ranges.top(); + ranges.pop(); + + if (current_left >= current_right) { + continue; + } + + const int partition_index = HoarePartition(values, current_left, current_right); + + if ((partition_index - current_left) > (current_right - (partition_index + 1))) { + ranges.emplace(current_left, partition_index); + ranges.emplace(partition_index + 1, current_right); + } else { + ranges.emplace(partition_index + 1, current_right); + ranges.emplace(current_left, partition_index); + } + } +} + +void YushkovaPHoareSortingSimpleMergingALL::SimpleMerge(const std::vector &source, std::vector &destination, + std::size_t left, std::size_t middle, std::size_t right) { + std::size_t left_index = left; + std::size_t right_index = middle; + std::size_t destination_index = left; + + while (left_index < middle && right_index < right) { + if (source[left_index] <= source[right_index]) { + destination[destination_index++] = source[left_index++]; + } else { + destination[destination_index++] = source[right_index++]; + } + } + + while (left_index < middle) { + destination[destination_index++] = source[left_index++]; + } + + while (right_index < right) { + destination[destination_index++] = source[right_index++]; + } +} + +void YushkovaPHoareSortingSimpleMergingALL::SortLocalStlParallel(std::vector &values) { + if (values.size() <= 1) { + return; + } + + const std::size_t size = values.size(); + const std::size_t block_count = (size + kBlockSize - 1) / kBlockSize; + + RunInThreads(block_count, [&values, size](std::size_t block_index) { + const std::size_t block_start = block_index * kBlockSize; + const std::size_t block_end = std::min(block_start + kBlockSize, size); + if ((block_end - block_start) > 1) { + HoareQuickSort(values, static_cast(block_start), static_cast(block_end - 1)); + } + }); + + for (std::size_t merge_width = kBlockSize; merge_width < size; merge_width *= 2) { + std::vector merged_data(size); + const std::size_t merge_count = (size + (2 * merge_width) - 1) / (2 * merge_width); + + RunInThreads(merge_count, [&values, size, merge_width, &merged_data](std::size_t merge_index) { + const std::size_t left = merge_index * 2 * merge_width; + const std::size_t middle = std::min(left + merge_width, size); + const std::size_t right = std::min(left + (2 * merge_width), size); + + if (middle < right) { + SimpleMerge(values, merged_data, left, middle, right); + } else { + std::copy(values.begin() + static_cast(left), + values.begin() + static_cast(right), + merged_data.begin() + static_cast(left)); + } + }); + + values.swap(merged_data); + } +} + +void YushkovaPHoareSortingSimpleMergingALL::MergeGatheredChunks(std::vector &values, + const std::vector &chunk_sizes, + const std::vector &offsets) { + std::vector merged_data(values.size()); + for (std::size_t rank = 1; rank < chunk_sizes.size(); ++rank) { + const std::size_t left = 0; + const std::size_t middle = offsets[rank]; + const std::size_t right = middle + chunk_sizes[rank]; + SimpleMerge(values, merged_data, left, middle, right); + std::copy(merged_data.begin(), merged_data.begin() + static_cast(right), values.begin()); + } +} + +void YushkovaPHoareSortingSimpleMergingALL::BroadcastVector(std::vector &values, int rank) { + auto size = static_cast(values.size()); + MPI_Bcast(&size, 1, MPI_UINT64_T, 0, MPI_COMM_WORLD); + if (rank != 0) { + values.resize(static_cast(size)); + } + if (size > 0) { + MPI_Bcast(values.data(), static_cast(size), MPI_INT, 0, MPI_COMM_WORLD); + } +} + +bool YushkovaPHoareSortingSimpleMergingALL::ValidationImpl() { + return !GetInput().empty(); +} + +bool YushkovaPHoareSortingSimpleMergingALL::PreProcessingImpl() { + GetOutput() = GetInput(); + return true; +} + +bool YushkovaPHoareSortingSimpleMergingALL::RunImpl() { + int rank = 0; + int mpi_size = 1; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &mpi_size); + + const std::size_t total_size = GetOutput().size(); + if (total_size <= 1) { + return true; + } + + std::vector chunk_sizes(static_cast(mpi_size)); + std::vector offsets(static_cast(mpi_size)); + BuildDistribution(total_size, mpi_size, chunk_sizes, offsets); + + const std::vector send_counts = MakeIntVector(chunk_sizes); + const std::vector send_offsets = MakeIntVector(offsets); + + std::vector local_data(chunk_sizes[static_cast(rank)]); + MPI_Scatterv(rank == 0 ? GetOutput().data() : nullptr, send_counts.data(), send_offsets.data(), MPI_INT, + local_data.data(), send_counts[static_cast(rank)], MPI_INT, 0, MPI_COMM_WORLD); + + SortLocalStlParallel(local_data); + + std::vector gathered_data; + if (rank == 0) { + gathered_data.resize(total_size); + } + MPI_Gatherv(local_data.data(), static_cast(local_data.size()), MPI_INT, + rank == 0 ? gathered_data.data() : nullptr, send_counts.data(), send_offsets.data(), MPI_INT, 0, + MPI_COMM_WORLD); + + if (rank == 0) { + MergeGatheredChunks(gathered_data, chunk_sizes, offsets); + GetOutput() = std::move(gathered_data); + } + + BroadcastVector(GetOutput(), rank); + return std::ranges::is_sorted(GetOutput()); +} + +bool YushkovaPHoareSortingSimpleMergingALL::PostProcessingImpl() { + return !GetOutput().empty() && std::ranges::is_sorted(GetOutput()); +} + +} // namespace yushkova_p_hoare_sorting_simple_merging diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp b/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp index b8c724ab81..31c0ca10a5 100644 --- a/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/tests/functional/main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -10,6 +11,7 @@ #include "util/include/func_test_util.hpp" #include "util/include/util.hpp" +#include "yushkova_p_hoare_sorting_simple_merging/all/include/ops_all.hpp" #include "yushkova_p_hoare_sorting_simple_merging/common/include/common.hpp" #include "yushkova_p_hoare_sorting_simple_merging/omp/include/ops_omp.hpp" #include "yushkova_p_hoare_sorting_simple_merging/seq/include/ops_seq.hpp" @@ -31,6 +33,14 @@ class YushkovaPRunFuncTestsThreads : public ppc::util::BaseRunFuncTests kTestParam = { +const std::array kTestParam = { std::make_tuple(std::vector{42}, "single"), std::make_tuple(std::vector{2, 1}, "two_elements"), std::make_tuple(std::vector{1, 2, 3, 4, 5}, "already_sorted"), @@ -71,51 +81,7 @@ const std::array kTestParam = { std::make_tuple(std::vector{10, 3, 8, 6, 4, 9, 2, 7, 1, 5}, "random_10"), std::make_tuple(std::vector{100, 1, 50, 2, 75, 3, 60, 4, 20, 5, 30}, "odd_count"), std::make_tuple(std::vector{9, 9, 8, 8, 7, 7, 6, 6, 5, 5}, "pair_duplicates"), - std::make_tuple(std::vector{1000, -1000, 500, -500, 0, 250, -250}, "wide_range"), - std::make_tuple(std::vector(64, 7), "one_block_equal"), - std::make_tuple([] { - std::vector values(64); - for (std::size_t i = 0; i < values.size(); ++i) { - values[i] = static_cast(i); - } - return values; - }(), "one_block_sorted"), - std::make_tuple([] { - std::vector values(64); - for (std::size_t i = 0; i < values.size(); ++i) { - values[i] = static_cast(i); - } - std::ranges::reverse(values); - return values; - }(), "one_block_reverse"), - std::make_tuple([] { - std::vector values(65); - for (std::size_t i = 0; i < values.size(); ++i) { - values[i] = static_cast(i) - 32; - } - return values; - }(), "crosses_block_65"), - std::make_tuple([] { - std::vector values(128); - for (std::size_t i = 0; i < values.size(); ++i) { - values[i] = static_cast(values.size() - i); - } - return values; - }(), "crosses_two_blocks_128"), - std::make_tuple([] { - std::vector values(129); - for (std::size_t i = 0; i < values.size(); ++i) { - values[i] = static_cast((i % 7) - 3); - } - return values; - }(), "crosses_two_blocks_129"), - std::make_tuple([] { - std::vector values(130); - for (std::size_t i = 0; i < values.size(); ++i) { - values[i] = static_cast(130 - i); - } - return values; - }(), "crosses_two_blocks_130")}; + std::make_tuple(std::vector{1000, -1000, 500, -500, 0, 250, -250}, "wide_range")}; const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask( kTestParam, PPC_SETTINGS_yushkova_p_hoare_sorting_simple_merging), @@ -123,6 +89,8 @@ const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask( kTestParam, PPC_SETTINGS_yushkova_p_hoare_sorting_simple_merging), + ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_yushkova_p_hoare_sorting_simple_merging), ppc::util::AddFuncTask( kTestParam, PPC_SETTINGS_yushkova_p_hoare_sorting_simple_merging)); diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/tests/performance/main.cpp b/tasks/yushkova_p_hoare_sorting_simple_merging/tests/performance/main.cpp index 588a79dc7f..594ac0f639 100644 --- a/tasks/yushkova_p_hoare_sorting_simple_merging/tests/performance/main.cpp +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/tests/performance/main.cpp @@ -1,10 +1,13 @@ #include +#include #include #include #include #include "util/include/perf_test_util.hpp" +#include "util/include/util.hpp" +#include "yushkova_p_hoare_sorting_simple_merging/all/include/ops_all.hpp" #include "yushkova_p_hoare_sorting_simple_merging/common/include/common.hpp" #include "yushkova_p_hoare_sorting_simple_merging/omp/include/ops_omp.hpp" #include "yushkova_p_hoare_sorting_simple_merging/seq/include/ops_seq.hpp" @@ -30,6 +33,14 @@ class YushkovaPRunPerfTestsThreads : public ppc::util::BaseRunPerfTests b; }) == output_data.end(); @@ -48,7 +59,8 @@ namespace { const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + YushkovaPHoareSortingSimpleMergingSTL, YushkovaPHoareSortingSimpleMergingALL, + YushkovaPHoareSortingSimpleMergingTBB>( PPC_SETTINGS_yushkova_p_hoare_sorting_simple_merging); const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); From 564e6fc0b87eb6976ec08eceeabc960fc9099dc5 Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Fri, 22 May 2026 23:32:09 +0300 Subject: [PATCH 16/18] reports --- .../all/report.md | 110 ++++++++++++++ .../omp/report.md | 143 ++++++++++++++++++ .../report.md | 105 +++++++++++++ .../seq/report.md | 100 ++++++++++++ .../stl/report.md | 91 +++++++++++ .../tbb/report.md | 94 ++++++++++++ 6 files changed, 643 insertions(+) create mode 100644 tasks/yushkova_p_hoare_sorting_simple_merging/all/report.md create mode 100644 tasks/yushkova_p_hoare_sorting_simple_merging/omp/report.md create mode 100644 tasks/yushkova_p_hoare_sorting_simple_merging/report.md create mode 100644 tasks/yushkova_p_hoare_sorting_simple_merging/seq/report.md create mode 100644 tasks/yushkova_p_hoare_sorting_simple_merging/stl/report.md create mode 100644 tasks/yushkova_p_hoare_sorting_simple_merging/tbb/report.md diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/all/report.md b/tasks/yushkova_p_hoare_sorting_simple_merging/all/report.md new file mode 100644 index 0000000000..bed149e8b1 --- /dev/null +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/all/report.md @@ -0,0 +1,110 @@ +# Сортировка Хоара с простым слиянием - ALL + +- **Студент:** Юшкова Полина Александровна, 3823Б1ПР2 +- **Технология:** ALL (MPI + STL) +- **Вариант:** 13 + +## 1. Контекст + +ALL-версия объединяет MPI между процессами и `std::thread` внутри каждого процесса. MPI используется для распределения +частей массива между rank-ами, а локальная STL-схема сортирует фрагмент внутри процесса. + +## 2. Постановка задачи + +- **Входные данные:** непустой `std::vector`, доступный на корневом rank. +- **Выходные данные:** отсортированный по неубыванию `std::vector`. +- **Baseline:** последовательная версия с временем `T_seq(task_run) = 0.0030497600 s`, + `T_seq(pipeline) = 0.0083235000 s`. + +## 3. Базовый алгоритм + +Корневой rank хранит входной массив. Данные распределяются между rank-ами, каждый rank локально сортирует свой фрагмент +STL-схемой (блоки по 64 и простое слияние по уровням), затем фрагменты собираются на rank 0 и последовательно досливаются +в один общий массив. После этого итоговый вектор рассылается всем процессам. + +## 4. Межпроцессная схема + +`MPI_Comm_rank` и `MPI_Comm_size` определяют роль процесса и число rank-ов. `BuildDistribution` делит `total_size` почти +поровну: первые `remainder` rank-ов получают на один элемент больше. `MPI_Scatterv` отправляет локальные куски, +`MPI_Gatherv` собирает отсортированные куски на rank 0, затем `BroadcastVector` рассылает итог всем rank-ам через +`MPI_Bcast`. + +Фрагмент распределения и обмена: + +```cpp +std::vector chunk_sizes(static_cast(mpi_size)); +std::vector offsets(static_cast(mpi_size)); +BuildDistribution(total_size, mpi_size, chunk_sizes, offsets); + +const std::vector send_counts = MakeIntVector(chunk_sizes); +const std::vector send_offsets = MakeIntVector(offsets); + +std::vector local_data(chunk_sizes[static_cast(rank)]); +MPI_Scatterv(rank == 0 ? GetOutput().data() : nullptr, send_counts.data(), + send_offsets.data(), MPI_INT, local_data.data(), + send_counts[static_cast(rank)], MPI_INT, 0, + MPI_COMM_WORLD); + +SortLocalStlParallel(local_data); + +std::vector gathered_data; +if (rank == 0) { + gathered_data.resize(total_size); +} +MPI_Gatherv(local_data.data(), static_cast(local_data.size()), MPI_INT, + rank == 0 ? gathered_data.data() : nullptr, send_counts.data(), + send_offsets.data(), MPI_INT, 0, MPI_COMM_WORLD); +``` + +## 5. Внутрипроцессная схема + +Локальная сортировка выполняется функцией `SortLocalStlParallel`: + +- разбиение на блоки по 64 элемента; +- сортировка каждого блока quicksort Хоара; +- уровни простого слияния по независимым парам диапазонов. + +Задачи на каждом уровне выполняются через `RunInThreads` с распределением `task_index += thread_count`. Число потоков +выбирается по `std::thread::hardware_concurrency()` и числу задач (если `hardware_concurrency()==0`, используется +fallback `2`). + +В коде явного `MPI_Barrier` нет; синхронизация возникает на коллективных операциях `MPI_Scatterv`, `MPI_Gatherv` и +`MPI_Bcast`. + +## 6. Детали реализации + +`ValidationImpl` проверяет непустой вход. `PreProcessingImpl` копирует вход в выходной буфер. `RunImpl` выполняет +MPI-распределение, локальную сортировку, сбор, финальное последовательное слияние на rank 0 и broadcast результата. +`PostProcessingImpl` проверяет, что выход непустой и отсортирован. + +Файлы реализации: `all/include/ops_all.hpp`, `all/src/ops_all.cpp`. + +## 7. Проверка корректности + +ALL-backend зарегистрирован в общем функциональном тесте. Корректность подтверждается сравнением с эталоном +`std::ranges::sort` и дополнительными проверками `std::ranges::is_sorted`. + +## 8. Экспериментальная среда + +- **ОС:** Windows +- **MPI:** Microsoft MPI (`mpiexec`) +- **Compiler:** clang++ (MSVC toolchain), C++23 +- **Размер входных данных:** `N=100000` +- **Диапазон значений:** `[-1000000, 1000000]` +- **Baseline TaskRun:** `0.0030497600 s` +- **Baseline pipeline:** `0.0083235000 s` +- **Число повторов:** 5 + +## 9. Результаты + +- backend: all; ranks: 1; threads_per_rank: 12; total_workers: 12; time: 0.0159865600 s; speedup: 0.191; efficiency: 0.016; notes: `mpiexec -n 1`, local STL auto-threads, `TaskRun`. +- backend: all; ranks: 2; threads_per_rank: 12; total_workers: 24; time: 0.0155627800 s; speedup: 0.196; efficiency: 0.008; notes: `mpiexec -n 2`, local STL auto-threads, `TaskRun`. +- backend: all; ranks: 4; threads_per_rank: 12; total_workers: 48; time: 0.0125462200 s; speedup: 0.243; efficiency: 0.005; notes: `mpiexec -n 4`, local STL auto-threads, `TaskRun`. +- backend: all; ranks: 1; threads_per_rank: 12; total_workers: 12; time: 0.0173728400 s; speedup: 0.479; efficiency: 0.040; notes: `mpiexec -n 1`, local STL auto-threads, `pipeline`. +- backend: all; ranks: 4; threads_per_rank: 12; total_workers: 48; time: 0.0142562000 s; speedup: 0.584; efficiency: 0.012; notes: `mpiexec -n 4`, local STL auto-threads, `pipeline`. + +## 10. Выводы + +ALL-версия добавляет стоимость `Scatterv/Gatherv/Bcast` и финальное слияние на rank 0. На `N=100000` итоговое время +существенно зависит от коммуникаций и накладных расходов на создание локальных потоков, поэтому эффективность по +`total_workers` получается низкой, даже если локальная сортировка внутри ранга выполняется параллельно. diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/omp/report.md b/tasks/yushkova_p_hoare_sorting_simple_merging/omp/report.md new file mode 100644 index 0000000000..8005744d99 --- /dev/null +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/omp/report.md @@ -0,0 +1,143 @@ +# Сортировка Хоара с простым слиянием - OMP + +- Студент: Юшкова Полина Александровна +- Группа: 3823Б1ПР2 +- Технология: OMP +- Вариант: 13 + +## 1. Контекст + +OpenMP-версия расширяет базовую сортировку Хоара за счет параллельной обработки независимых блоков и последующего простого слияния. Основная идея заключается в том, чтобы разделить входной массив на несколько частей, отсортировать их одновременно, а затем объединить уже упорядоченные части в один итоговый массив. + +## 2. Постановка задачи + +В этой работе я реализовала параллельную сортировку массива целых чисел с использованием OpenMP. В моей реализации: + +- корректно обрабатывать входные данные; +- разбивать массив на независимые участки; +- выполнять локальную сортировку каждого участка алгоритмом Хоара; +- выполнять последующее слияние отсортированных частей; +- возвращать отсортированный массив и подтверждать корректность результата. + +## 3. Базовый алгоритм + +В основе решения лежат две классические операции: + +1. **Разбиение Хоара**. Опорный элемент выбирается из середины текущего диапазона. Два указателя двигаются навстречу друг другу, после чего элементы, нарушающие порядок относительно опорного значения, меняются местами. +2. **Простое слияние**. После локальной сортировки блоков два отсортированных диапазона объединяются в один результирующий массив последовательным сравнением текущих элементов. + +Для сортировки используется не рекурсивная версия, а итеративная реализация с собственным стеком диапазонов. Это упрощает контроль над глубиной вызовов и делает алгоритм удобнее для применения внутри блоков. + +## 4. Схема распараллеливания + +Параллелизм применяется только на этапе локальной сортировки блоков: + +- входной массив делится на `chunks`, где число блоков ограничивается количеством доступных потоков и размером массива; +- каждый блок сортируется независимо в директиве `#pragma omp parallel for`; +- после завершения параллельной фазы выполняется последовательное слияние блоков слева направо. + +Такой подход позволяет избежать гонок данных на этапе сортировки, потому что каждый поток работает только со своим диапазоном индексов. + +## 5. Детали реализации + +Реализация находится в файлах: + +- `omp/include/ops_omp.hpp` +- `omp/src/ops_omp.cpp` + +Ключевые этапы работы: + +- в конструкторе устанавливается тип задачи и копируется входной массив; +- `ValidationImpl()` принимает только непустой вход; +- `PreProcessingImpl()` переносит данные из входа в выход; +- `RunImpl()` запускает сортировку и слияние; +- `PostProcessingImpl()` дополнительно проверяет, что результат не пустой и отсортирован. + +### 5.1 Сортировка блока + +Функция `HoareQuickSort()` использует стек пар границ. Для каждого диапазона вызывается `HoarePartition()`, после чего поддиапазоны добавляются обратно в стек. Это позволяет обойтись без рекурсии. + +### 5.2 Слияние + +Функция `Merge()` объединяет два уже отсортированных участка массива: + +- левый участок: `[left, mid]`; +- правый участок: `[mid + 1, right]`. + +Временный буфер `merged` заполняется по мере выбора минимального элемента из двух текущих позиций, а затем записывается обратно в исходный массив. + +### 5.3 Управление числом блоков + +Число блоков определяется как: + +- `max_threads = max(1, omp_get_max_threads())`; +- `chunks = min(max_threads, n)`. + +Если массив слишком мал, сортировка выполняется без распараллеливания, чтобы не создавать лишние накладные расходы. + +### 5.4 Основной фрагмент + +```cpp +const int max_threads = std::max(1, omp_get_max_threads()); +const int chunks = std::min(max_threads, n); + +std::vector borders(static_cast(chunks + 1)); +for (int i = 0; i <= chunks; ++i) { + borders[static_cast(i)] = (i * n) / chunks; +} + +#pragma omp parallel for default(none) shared(values, borders, chunks) +for (int chunk = 0; chunk < chunks; ++chunk) { + const int left = borders[static_cast(chunk)]; + const int right = borders[static_cast(chunk) + 1] - 1; + if (left < right) { + HoareQuickSort(values, left, right); + } +} +``` + +## 6. Проверка корректности + +Корректность проверяется на нескольких уровнях: + +- `ValidationImpl()` не пропускает пустой массив; +- функциональные тесты сравнивают результат с эталонной сортировкой; +- `PostProcessingImpl()` проверяет, что выходной массив отсортирован; +- тесты покрывают как маленькие входы, так и массивы с повторяющимися значениями, отрицательными числами и уже отсортированными данными. + +## 7. Экспериментальная среда + +- ОС: Windows +- Компилятор: C++17 +- Технология параллелизма: OpenMP +- Набор тестов: Google Test +- Среда измерений: `ppc_perf_tests` + +## 8. Результаты + +Измерения выполнены на массиве из `100000` целых чисел. + +| Mode | Threads | Time, s | Speedup | Efficiency | +|------|---------|---------|---------|------------| +| seq | 1 | 0.003516 | 1.000 | 100.0% | +| omp | 2 | 0.001898 | 1.853 | 92.6% | +| omp | 4 | 0.001549 | 2.269 | 56.7% | +| omp | 8 | 0.001683 | 2.089 | 26.1% | + +Анализ результатов следует делать по двум критериям: + +- наличие ускорения относительно последовательной версии; +- изменение эффективности при росте числа потоков. + +В моей реализации выигрыш от OpenMP заметен уже на размере `100000`, однако рост числа потоков после 4 начинает снижать эффективность. Это связано с тем, что часть работы остается последовательной, а также с накладными расходами на создание и синхронизацию потоков. + +## 9. Выводы + +В ходе работы я реализовала OpenMP-версию сортировки Хоара с простым слиянием. Алгоритм корректно: + +- разделяет входной массив на независимые блоки; +- сортирует каждый блок отдельно; +- объединяет отсортированные части в итоговый массив; +- проходит функциональные тесты на корректность. + +Моя реализация является простой и предсказуемой по структуре, а параллелизм применяется только там, где нет конфликтов по данным. Это делает решение удобным для дальнейшего сравнения с последовательной и другими параллельными версиями. diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/report.md b/tasks/yushkova_p_hoare_sorting_simple_merging/report.md new file mode 100644 index 0000000000..98bd0e3bac --- /dev/null +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/report.md @@ -0,0 +1,105 @@ +# Сортировка Хоара с простым слиянием - сводный отчет + +- **Студент:** Юшкова Полина Александровна, 3823Б1ПР2 +- **Технологии:** SEQ, OMP, TBB, STL, ALL (MPI + STL) +- **Вариант:** 13 + +## 1. Контекст + +Работа сравнивает реализации сортировки `std::vector` по неубыванию для backend-ов `seq`, `omp`, `tbb`, `stl` и +`all`. Последовательная версия задает baseline, потоковые версии проверяют разные модели внутрипроцессного +распараллеливания, а ALL добавляет MPI-уровень. + +## 2. Постановка задачи + +- **Входные данные:** непустой объект `std::vector`. +- **Выходные данные:** объект `std::vector` того же размера, элементы которого переставлены в порядке неубывания. +- **Эталон корректности:** совпадение с результатом `std::ranges::sort` в функциональных тестах. +- **Baseline:** `seq`, `T_seq(task_run) = 0.0030497600 s`, `T_seq(pipeline) = 0.0083235000 s`. + +Единые типы входа и выхода заданы в `common/include/common.hpp`. + +## 3. Базовый алгоритм + +Базовое вычислительное ядро использует сортировку Хоара: опорный элемент берется из середины диапазона, затем диапазон +разбивается двумя индексами и сортируется итеративным quicksort. + +Параллельные реализации используют принцип простого слияния: + +- локальная сортировка независимых частей (половин или блоков по 64 элемента); +- уровни слияния объединяют соседние отсортированные диапазоны. + +## 4. Схемы распараллеливания + +- **SEQ:** две половины, сортировка Хоара, `SimpleMerge`. +- **OMP:** деление массива на `chunks`, сортировка блоков в `#pragma omp parallel for`, затем последовательное слияние. +- **TBB:** `oneapi::tbb::parallel_for(blocked_range)` для сортировки блоков по 64 и для каждого прохода слияния. +- **STL:** при `PPC_NUM_THREADS > 1` используется 2 worker-а (один `std::thread` + текущий поток) для сортировки двух + половин, затем merge. +- **ALL:** MPI распределяет фрагменты между rank-ами (`Scatterv/Gatherv/Bcast`), а локальная часть каждого rank-а + сортирует фрагмент STL-схемой (блоки по 64 и уровни слияния). + +## 5. Детали методики + +Замеры выполнены на `N=100000` случайных `int` из диапазона `[-1000000, 1000000]`, число повторов - 5. + +- `TaskRun`: повторяется только `Run()` после одного `PreProcessing()`. +- `pipeline`: каждый повтор проходит полный pipeline (`Validation` → `PreProcessing` → `Run` → `PostProcessing`). + +Speedup считался как `T_seq / T_backend`, efficiency - как `speedup / workers`. + +## 6. Проверка корректности + +Функциональные тесты регистрируют все backend-ы (`seq/omp/stl/tbb/all`) и сравнивают результат с `std::ranges::sort`. +Дополнительно в реализациях используются проверки `std::ranges::is_sorted`. + +## 7. Экспериментальная среда + +- **ОС:** Windows +- **Размер входных данных:** `N=100000` +- **Диапазон значений:** `[-1000000, 1000000]` +- **Число повторов:** 5 +- **ALL:** `mpiexec` (Microsoft MPI) +- **Auto workers (ALL локально):** `std::thread::hardware_concurrency() = 12` на тестовой машине + +## 8. Результаты TaskRun + +- backend: seq; time: 0.0030497600 s; speedup: 1.000; efficiency: 1.000; notes: baseline. +- backend: omp, 1 thread; time: 0.0029892200 s; speedup: 1.020; efficiency: 1.020; notes: `PPC_NUM_THREADS=1`. +- backend: omp, 2 threads; time: 0.0021860800 s; speedup: 1.395; efficiency: 0.698; notes: `PPC_NUM_THREADS=2`. +- backend: omp, 4 threads; time: 0.0018365600 s; speedup: 1.661; efficiency: 0.415; notes: `PPC_NUM_THREADS=4`. +- backend: stl, workers: 1; time: 0.0028924000 s; speedup: 1.054; efficiency: 1.054; notes: `PPC_NUM_THREADS=1`. +- backend: stl, workers: 2; time: 0.0021834000 s; speedup: 1.397; efficiency: 0.698; notes: `PPC_NUM_THREADS=4` (включает параллельную ветку). +- backend: tbb, 1 worker; time: 0.0076817000 s; speedup: 0.397; efficiency: 0.397; notes: `PPC_NUM_THREADS=1`, `global_control`. +- backend: tbb, 2 workers; time: 0.0048485400 s; speedup: 0.629; efficiency: 0.315; notes: `PPC_NUM_THREADS=2`, `global_control`. +- backend: tbb, 4 workers; time: 0.0028551400 s; speedup: 1.068; efficiency: 0.267; notes: `PPC_NUM_THREADS=4`, `global_control`. +- backend: all; ranks: 1; threads_per_rank: 12; total_workers: 12; time: 0.0159865600 s; speedup: 0.191; efficiency: 0.016; notes: `mpiexec -n 1`. +- backend: all; ranks: 2; threads_per_rank: 12; total_workers: 24; time: 0.0155627800 s; speedup: 0.196; efficiency: 0.008; notes: `mpiexec -n 2`. +- backend: all; ranks: 4; threads_per_rank: 12; total_workers: 48; time: 0.0125462200 s; speedup: 0.243; efficiency: 0.005; notes: `mpiexec -n 4`. + +## 9. Результаты Pipeline + +- backend: seq; time: 0.0083235000 s; speedup: 1.000; efficiency: 1.000; notes: baseline. +- backend: omp, 1 thread; time: 0.0112251400 s; speedup: 0.742; efficiency: 0.742; notes: `PPC_NUM_THREADS=1`. +- backend: omp, 4 threads; time: 0.0037493600 s; speedup: 2.220; efficiency: 0.555; notes: `PPC_NUM_THREADS=4`. +- backend: stl, workers: 1; time: 0.0086819200 s; speedup: 0.959; efficiency: 0.959; notes: `PPC_NUM_THREADS=1`. +- backend: stl, workers: 2; time: 0.0055119600 s; speedup: 1.510; efficiency: 0.755; notes: `PPC_NUM_THREADS=4`. +- backend: tbb, 1 worker; time: 0.0113887600 s; speedup: 0.731; efficiency: 0.731; notes: `PPC_NUM_THREADS=1`, `global_control`. +- backend: tbb, 4 workers; time: 0.0037098400 s; speedup: 2.244; efficiency: 0.561; notes: `PPC_NUM_THREADS=4`, `global_control`. +- backend: all; ranks: 1; threads_per_rank: 12; total_workers: 12; time: 0.0173728400 s; speedup: 0.479; efficiency: 0.040; notes: `mpiexec -n 1`. +- backend: all; ranks: 4; threads_per_rank: 12; total_workers: 48; time: 0.0142562000 s; speedup: 0.584; efficiency: 0.012; notes: `mpiexec -n 4`. + +## 10. Интерпретация результатов + +`seq` служит baseline. `omp` показывает ускорение на `TaskRun` и заметный выигрыш на pipeline при 4 потоках, так как часть +работы распараллеливается по независимым блокам. `stl` ускоряет сортировку половин только при включении параллельной +ветки (2 worker-а). `tbb` на `N=100000` чувствителен к накладным расходам `parallel_for` и выделения буферов слияния: +в `TaskRun` speedup заметен только при 4 потоках, а в pipeline - уверенный выигрыш при 4 потоках. +`all` добавляет стоимость `Scatterv/Gatherv/Bcast` и финальное слияние на rank 0, из-за чего эффективность по +`total_workers` остается низкой. + +## 11. Выводы + +Для измеренного размера `N=100000` лучший pipeline-результат среди потоковых backend-ов показали `tbb(4)` и `omp(4)` +(времена порядка `0.0037 s`). ALL-версия на `4` rank-ах не ускоряет baseline на этом размере из-за MPI-обменов и +накладных расходов локальной сортировки. diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/seq/report.md b/tasks/yushkova_p_hoare_sorting_simple_merging/seq/report.md new file mode 100644 index 0000000000..fa44d9d63d --- /dev/null +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/seq/report.md @@ -0,0 +1,100 @@ +# Сортировка Хоара с простым слиянием - SEQ + +- **Студент:** Юшкова Полина Александровна, 3823Б1ПР2 +- **Технология:** SEQ +- **Вариант:** 13 + +## 1. Контекст + +Последовательная версия является базовым эталоном для всех остальных реализаций задачи. Она сортирует `std::vector` +по неубыванию и не использует параллельных примитивов, поэтому ее время берется как `T_seq` при расчете ускорения и +эффективности параллельных версий. + +## 2. Постановка задачи + +- **Входные данные:** непустой объект `std::vector`. +- **Выходные данные:** объект `std::vector` того же размера, элементы которого переставлены в порядке неубывания. +- **Эталон корректности:** результат `std::ranges::sort` в функциональных тестах. + +Единые типы входа и выхода заданы в `common/include/common.hpp`. + +## 3. Базовый алгоритм + +Вычислительное ядро использует разбиение Хоара и итеративный quicksort. Опорный элемент выбирается из середины диапазона, +два индекса двигаются навстречу друг другу, а элементы меняются местами до пересечения индексов. + +В моей реализации дополнительно используется простое слияние: массив делится на две половины, каждая половина +сортируется алгоритмом Хоара, затем половины объединяются процедурой merge. + +Средняя сложность сортировки составляет `O(n log n)`, худшая - `O(n^2)`. Дополнительная память тратится на стек +диапазонов и временные буферы при слиянии, суммарно до `O(n)`. + +Фрагмент реализации разбиения: + +```cpp +int YushkovaPHoareSortingSimpleMergingSEQ::HoarePartition( + std::vector &values, int left, int right) { + int pivot = values[left + ((right - left) / 2)]; + int i = left - 1; + int j = right + 1; + while (true) { + ++i; + while (values[i] < pivot) { + ++i; + } + --j; + while (values[j] > pivot) { + --j; + } + if (i >= j) { + return j; + } + std::swap(values[i], values[j]); + } +} +``` + +## 4. Детали реализации + +`ValidationImpl` принимает только непустой вход. `PreProcessingImpl` копирует вход в выходной буфер, чтобы сортировать +результат без изменения исходного контейнера. + +`RunImpl` пропускает массивы размера `0/1`, затем: + +1. Делит вход на `left` и `right`. +2. Вызывает `HoareQuickSort` для каждой части (если размер больше 1). +3. Объединяет результат функцией `SimpleMerge`. +4. Проверяет `std::ranges::is_sorted`. + +`PostProcessingImpl` повторно проверяет непустой и отсортированный выход. Это фиксирует базовый pipeline задачи: +валидация, подготовка, сортировка и проверка результата. + +Файлы реализации: `seq/include/ops_seq.hpp`, `seq/src/ops_seq.cpp`. + +## 5. Проверка корректности + +Функциональные тесты строят эталон через `std::ranges::sort(expected)` и сравнивают его с выходом задачи. Набор содержит +10 сценариев (один элемент, два элемента, уже отсортированный, обратный порядок, дубликаты, смешанные знаки и т.д.). + +## 6. Экспериментальная среда + +- **ОС:** Windows +- **Compiler:** MinGW g++ (C++23) +- **Флаги:** `-O2` +- **Размер входных данных:** `N=100000` +- **Диапазон значений:** `[-1000000, 1000000]` +- **Число повторов:** 5 + +Важно для интерпретации: `TaskRun` повторяет только `Run()` после одного `PreProcessing()`. + +## 7. Результаты + +Baseline измерен локальным замером на `N=100000` (5 повторов). + +- backend: seq; time: 0.0030497600 s; speedup: 1.000; efficiency: 1.000; notes: `N=100000`, `task_run`. +- backend: seq; time: 0.0083235000 s; speedup: 1.000; efficiency: 1.000; notes: `N=100000`, `pipeline`. + +## 8. Выводы + +Последовательная версия подтверждает корректность алгоритма сравнением с `std::ranges::sort` и задает baseline для всех +параллельных backend-ов. Для `N=100000` время `task_run` составило `0.0030497600 s`, а pipeline-замер - `0.0083235000 s`. diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/stl/report.md b/tasks/yushkova_p_hoare_sorting_simple_merging/stl/report.md new file mode 100644 index 0000000000..930b54851c --- /dev/null +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/stl/report.md @@ -0,0 +1,91 @@ +# Сортировка Хоара с простым слиянием - STL + +- **Студент:** Юшкова Полина Александровна, 3823Б1ПР2 +- **Технология:** STL +- **Вариант:** 13 + +## 1. Контекст + +STL-версия использует `std::thread` для параллельной сортировки частей массива и последующего простого слияния. +Реализация ориентирована на минимальные накладные расходы: при `PPC_NUM_THREADS == 1` сортировка выполняется +последовательно, а при `PPC_NUM_THREADS > 1` создается один дополнительный поток для сортировки одной из половин массива. + +## 2. Постановка задачи + +- **Входные данные:** непустой объект `std::vector`. +- **Выходные данные:** отсортированный по неубыванию `std::vector`. +- **Baseline:** последовательная версия с временем `T_seq(task_run) = 0.0030497600 s`, + `T_seq(pipeline) = 0.0083235000 s`. + +## 3. Базовый алгоритм + +Массив делится на две половины. Каждая половина сортируется итеративным quicksort Хоара, после чего результат +объединяется через простое слияние (merge). Для merge используется `std::ranges::merge` в выходной буфер. + +## 4. Схема распараллеливания + +Параллельная ветка включается, если `ppc::util::GetNumThreads()` (читается из `PPC_NUM_THREADS`) больше 1. +Фактически при этом создаются **2 worker-а**: один поток сортирует левую половину, а текущий поток сортирует правую. + +Для корректного проброса исключений из дочернего потока используется `std::exception_ptr`. + +Фрагмент распараллеливания: + +```cpp +std::exception_ptr exception_ptr; +std::thread left_worker([&] { + try { + SortHalfIfNeeded(left); + } catch (...) { + exception_ptr = std::current_exception(); + } +}); + +try { + SortHalfIfNeeded(right); +} catch (...) { + if (!exception_ptr) { + exception_ptr = std::current_exception(); + } +} + +left_worker.join(); +if (exception_ptr) { + std::rethrow_exception(exception_ptr); +} +``` + +## 5. Детали реализации + +`ValidationImpl` проверяет непустой вход. `PreProcessingImpl` копирует вход в выходной буфер. +`RunImpl` делит массив пополам, сортирует половины (последовательно или параллельно) и выполняет `SimpleMerge`. +`PostProcessingImpl` дополнительно проверяет, что выход непустой и отсортирован. + +Файлы реализации: `stl/include/ops_stl.hpp`, `stl/src/ops_stl.cpp`. + +## 6. Проверка корректности + +STL-backend подключен в общий список задач функционального теста, а эталон строится через `std::ranges::sort`. +Дополнительно в `RunImpl` и `PostProcessingImpl` используется `std::ranges::is_sorted`. + +## 7. Экспериментальная среда + +- **ОС:** Windows +- **Compiler:** MinGW g++ (C++23) +- **Флаги:** `-O2` +- **Размер входных данных:** `N=100000` +- **Диапазон значений:** `[-1000000, 1000000]` +- **Число повторов:** 5 + +## 8. Результаты + +- workers: 1; time: 0.0028924000 s; speedup: 1.054; efficiency: 1.054; notes: `TaskRun`; `PPC_NUM_THREADS=1`. +- workers: 1; time: 0.0086819200 s; speedup: 0.959; efficiency: 0.959; notes: `pipeline`; `PPC_NUM_THREADS=1`. +- workers: 2; time: 0.0021834000 s; speedup: 1.397; efficiency: 0.698; notes: `TaskRun`; `PPC_NUM_THREADS=4` (включает параллельную ветку). +- workers: 2; time: 0.0055119600 s; speedup: 1.510; efficiency: 0.755; notes: `pipeline`; `PPC_NUM_THREADS=4` (включает параллельную ветку). + +## 9. Выводы + +STL-версия повторяет базовые фазы решения: сортировка двух независимых половин и простое слияние. +При `PPC_NUM_THREADS > 1` используется 2 потока, что дает ускорение относительно SEQ на `N=100000`: +для `task_run` время составило `0.0021834000 s` (speedup `1.397`), для pipeline - `0.0055119600 s` (speedup `1.510`). diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/report.md b/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/report.md new file mode 100644 index 0000000000..99f2d9e443 --- /dev/null +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/tbb/report.md @@ -0,0 +1,94 @@ +# Сортировка Хоара с простым слиянием - TBB + +- **Студент:** Юшкова Полина Александровна, 3823Б1ПР2 +- **Технология:** TBB +- **Вариант:** 13 + +## 1. Контекст + +TBB-версия переносит две независимые фазы алгоритма на `oneapi::tbb`: сортировку блоков и слияние соседних +отсортированных диапазонов. В этой реализации я проверяю, насколько эффективно планировщик TBB распределяет работу на +входе `N=100000`. + +## 2. Постановка задачи + +- **Входные данные:** непустой объект `std::vector`. +- **Выходные данные:** отсортированный по неубыванию `std::vector`. +- **Baseline:** последовательная версия с временем `T_seq(task_run) = 0.0030497600 s`, + `T_seq(pipeline) = 0.0083235000 s`. + +## 3. Базовый алгоритм + +Массив `int` сортируется блоками по 64 элемента, затем соседние отсортированные диапазоны объединяются простым слиянием. +Локальная сортировка использует схему Хоара: pivot из середины, два индекса и обмен до пересечения. + +## 4. Схема распараллеливания + +Используется `oneapi::tbb::parallel_for` с `oneapi::tbb::blocked_range`: + +- для сортировки блоков по индексам `block_index`; +- для каждого уровня слияния - по индексам `merge_index` независимых пар диапазонов. + +Grainsize и partitioner явно не задаются, используются значения по умолчанию TBB. + +Фрагмент TBB-части: + +```cpp +const size_t block_count = (size + kBlockSize - 1U) / kBlockSize; +oneapi::tbb::parallel_for(oneapi::tbb::blocked_range(0U, block_count), + [&data, size](const oneapi::tbb::blocked_range &range) { + for (size_t block_index = range.begin(); block_index != range.end(); ++block_index) { + SortBlockIfNeeded(data, size, block_index); + } +}); + +for (size_t merge_width = kBlockSize; merge_width < size; merge_width *= 2) { + MergePass(data, size, merge_width); +} +``` + +## 5. Детали реализации + +`ValidationImpl`, `PreProcessingImpl`, `RunImpl` и `PostProcessingImpl` расположены в `tbb/src/ops_tbb.cpp`. + +- Сортировка блоков пишет в непересекающиеся отрезки `data_`. +- На каждом проходе слияния создается буфер `merged_data(size)` и параллельно заполняются непересекающиеся диапазоны: + чтение из `data_`, запись в `merged_data`. +- После завершения `parallel_for` выполняется `data_.swap(merged_data)`. + +Важно: `PreProcessingImpl` копирует вход в `data_`, а `GetOutput()` заполняется только если `data_` оказался +отсортированным (`std::ranges::is_sorted(data_)`). + +Файлы реализации: `tbb/include/ops_tbb.hpp`, `tbb/src/ops_tbb.cpp`. + +## 6. Проверка корректности + +TBB-backend подключен в общий список задач функционального теста, а эталон строится через `std::ranges::sort`. +Внутри реализации дополнительно используется проверка `std::ranges::is_sorted`. + +## 7. Экспериментальная среда + +- **ОС:** Windows +- **Compiler:** C++ +- **Размер входных данных:** `N=100000` +- **Диапазон значений:** `[-1000000, 1000000]` +- **Baseline TaskRun:** `0.0030497600 s` +- **Baseline pipeline:** `0.0083235000 s` +- **Число повторов:** 5 по умолчанию + +## 8. Результаты + +Замеры выполнены на `N=100000` (5 повторов) с ограничением конкуренции через +`oneapi::tbb::global_control(max_allowed_parallelism, PPC_NUM_THREADS)`. + +- threads: 1; time: 0.0076817000 s; speedup: 0.397; efficiency: 0.397; notes: `TaskRun`, `PPC_NUM_THREADS=1`. +- threads: 2; time: 0.0048485400 s; speedup: 0.629; efficiency: 0.315; notes: `TaskRun`, `PPC_NUM_THREADS=2`. +- threads: 4; time: 0.0028551400 s; speedup: 1.068; efficiency: 0.267; notes: `TaskRun`, `PPC_NUM_THREADS=4`. +- threads: 1; time: 0.0113887600 s; speedup: 0.731; efficiency: 0.731; notes: `pipeline`, `PPC_NUM_THREADS=1`. +- threads: 4; time: 0.0037098400 s; speedup: 2.244; efficiency: 0.561; notes: `pipeline`, `PPC_NUM_THREADS=4`. + +## 9. Выводы + +TBB-версия реализует независимую сортировку блоков и независимое слияние на каждом уровне, поэтому масштабируется лучше +простых вариантов, если объем работы достаточно большой. Для `N=100000` итог зависит от накладных расходов `parallel_for` +и выделения временных буферов на проходах слияния; для других размеров нужны отдельные замеры. From 91260d94ac7dbec8d1e4bb31a30c6f4d6c8216b3 Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Fri, 22 May 2026 23:44:49 +0300 Subject: [PATCH 17/18] reports --- .../all/report.md | 15 ++++-- .../omp/report.md | 47 ++++++++++++------- .../report.md | 36 +++++++++----- .../stl/report.md | 6 ++- 4 files changed, 67 insertions(+), 37 deletions(-) diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/all/report.md b/tasks/yushkova_p_hoare_sorting_simple_merging/all/report.md index bed149e8b1..f4cf3ee3e8 100644 --- a/tasks/yushkova_p_hoare_sorting_simple_merging/all/report.md +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/all/report.md @@ -97,11 +97,16 @@ ALL-backend зарегистрирован в общем функциональ ## 9. Результаты -- backend: all; ranks: 1; threads_per_rank: 12; total_workers: 12; time: 0.0159865600 s; speedup: 0.191; efficiency: 0.016; notes: `mpiexec -n 1`, local STL auto-threads, `TaskRun`. -- backend: all; ranks: 2; threads_per_rank: 12; total_workers: 24; time: 0.0155627800 s; speedup: 0.196; efficiency: 0.008; notes: `mpiexec -n 2`, local STL auto-threads, `TaskRun`. -- backend: all; ranks: 4; threads_per_rank: 12; total_workers: 48; time: 0.0125462200 s; speedup: 0.243; efficiency: 0.005; notes: `mpiexec -n 4`, local STL auto-threads, `TaskRun`. -- backend: all; ranks: 1; threads_per_rank: 12; total_workers: 12; time: 0.0173728400 s; speedup: 0.479; efficiency: 0.040; notes: `mpiexec -n 1`, local STL auto-threads, `pipeline`. -- backend: all; ranks: 4; threads_per_rank: 12; total_workers: 48; time: 0.0142562000 s; speedup: 0.584; efficiency: 0.012; notes: `mpiexec -n 4`, local STL auto-threads, `pipeline`. +- backend: all; ranks: 1; threads_per_rank: 12; total_workers: 12; time: 0.0159865600 s; speedup: 0.191; + efficiency: 0.016; notes: `mpiexec -n 1`, local STL auto-threads, `TaskRun`. +- backend: all; ranks: 2; threads_per_rank: 12; total_workers: 24; time: 0.0155627800 s; speedup: 0.196; + efficiency: 0.008; notes: `mpiexec -n 2`, local STL auto-threads, `TaskRun`. +- backend: all; ranks: 4; threads_per_rank: 12; total_workers: 48; time: 0.0125462200 s; speedup: 0.243; + efficiency: 0.005; notes: `mpiexec -n 4`, local STL auto-threads, `TaskRun`. +- backend: all; ranks: 1; threads_per_rank: 12; total_workers: 12; time: 0.0173728400 s; speedup: 0.479; + efficiency: 0.040; notes: `mpiexec -n 1`, local STL auto-threads, `pipeline`. +- backend: all; ranks: 4; threads_per_rank: 12; total_workers: 48; time: 0.0142562000 s; speedup: 0.584; + efficiency: 0.012; notes: `mpiexec -n 4`, local STL auto-threads, `pipeline`. ## 10. Выводы diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/omp/report.md b/tasks/yushkova_p_hoare_sorting_simple_merging/omp/report.md index 8005744d99..24d0d0022a 100644 --- a/tasks/yushkova_p_hoare_sorting_simple_merging/omp/report.md +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/omp/report.md @@ -7,7 +7,9 @@ ## 1. Контекст -OpenMP-версия расширяет базовую сортировку Хоара за счет параллельной обработки независимых блоков и последующего простого слияния. Основная идея заключается в том, чтобы разделить входной массив на несколько частей, отсортировать их одновременно, а затем объединить уже упорядоченные части в один итоговый массив. +OpenMP-версия расширяет базовую сортировку Хоара за счет параллельной обработки независимых блоков и последующего +простого слияния. Я делю входной массив на несколько частей, сортирую их одновременно, а затем объединяю уже +упорядоченные части в один итоговый массив. ## 2. Постановка задачи @@ -23,20 +25,25 @@ OpenMP-версия расширяет базовую сортировку Хо В основе решения лежат две классические операции: -1. **Разбиение Хоара**. Опорный элемент выбирается из середины текущего диапазона. Два указателя двигаются навстречу друг другу, после чего элементы, нарушающие порядок относительно опорного значения, меняются местами. -2. **Простое слияние**. После локальной сортировки блоков два отсортированных диапазона объединяются в один результирующий массив последовательным сравнением текущих элементов. +1. **Разбиение Хоара**. Опорный элемент выбирается из середины текущего диапазона. Два указателя двигаются навстречу + друг другу, после чего элементы, нарушающие порядок относительно опорного значения, меняются местами. +2. **Простое слияние**. После локальной сортировки блоков два отсортированных диапазона объединяются в один + результирующий массив последовательным сравнением текущих элементов. -Для сортировки используется не рекурсивная версия, а итеративная реализация с собственным стеком диапазонов. Это упрощает контроль над глубиной вызовов и делает алгоритм удобнее для применения внутри блоков. +Для сортировки используется не рекурсивная версия, а итеративная реализация с собственным стеком диапазонов. +Это упрощает контроль над глубиной вызовов и делает алгоритм удобнее для применения внутри блоков. ## 4. Схема распараллеливания Параллелизм применяется только на этапе локальной сортировки блоков: -- входной массив делится на `chunks`, где число блоков ограничивается количеством доступных потоков и размером массива; +- входной массив делится на `chunks`; +- число блоков ограничивается количеством доступных потоков и размером массива; - каждый блок сортируется независимо в директиве `#pragma omp parallel for`; - после завершения параллельной фазы выполняется последовательное слияние блоков слева направо. -Такой подход позволяет избежать гонок данных на этапе сортировки, потому что каждый поток работает только со своим диапазоном индексов. +Такой подход позволяет избежать гонок данных на этапе сортировки, потому что каждый поток работает только со своим +диапазоном индексов. ## 5. Детали реализации @@ -55,7 +62,8 @@ OpenMP-версия расширяет базовую сортировку Хо ### 5.1 Сортировка блока -Функция `HoareQuickSort()` использует стек пар границ. Для каждого диапазона вызывается `HoarePartition()`, после чего поддиапазоны добавляются обратно в стек. Это позволяет обойтись без рекурсии. +Функция `HoareQuickSort()` использует стек пар границ. Для каждого диапазона вызывается `HoarePartition()`. +После этого поддиапазоны добавляются обратно в стек, что позволяет обойтись без рекурсии. ### 5.2 Слияние @@ -64,7 +72,8 @@ OpenMP-версия расширяет базовую сортировку Хо - левый участок: `[left, mid]`; - правый участок: `[mid + 1, right]`. -Временный буфер `merged` заполняется по мере выбора минимального элемента из двух текущих позиций, а затем записывается обратно в исходный массив. +Временный буфер `merged` заполняется по мере выбора минимального элемента из двух текущих позиций. Затем данные +записываются обратно в исходный массив. ### 5.3 Управление числом блоков @@ -73,7 +82,7 @@ OpenMP-версия расширяет базовую сортировку Хо - `max_threads = max(1, omp_get_max_threads())`; - `chunks = min(max_threads, n)`. -Если массив слишком мал, сортировка выполняется без распараллеливания, чтобы не создавать лишние накладные расходы. +Если массив слишком мал, сортировка выполняется без распараллеливания. Так я избегаю лишних накладных расходов. ### 5.4 Основной фрагмент @@ -103,7 +112,8 @@ for (int chunk = 0; chunk < chunks; ++chunk) { - `ValidationImpl()` не пропускает пустой массив; - функциональные тесты сравнивают результат с эталонной сортировкой; - `PostProcessingImpl()` проверяет, что выходной массив отсортирован; -- тесты покрывают как маленькие входы, так и массивы с повторяющимися значениями, отрицательными числами и уже отсортированными данными. +- тесты покрывают маленькие входы; +- отдельно проверяются массивы с повторяющимися значениями, отрицательными числами и уже отсортированными данными. ## 7. Экспериментальная среда @@ -117,19 +127,19 @@ for (int chunk = 0; chunk < chunks; ++chunk) { Измерения выполнены на массиве из `100000` целых чисел. -| Mode | Threads | Time, s | Speedup | Efficiency | -|------|---------|---------|---------|------------| -| seq | 1 | 0.003516 | 1.000 | 100.0% | -| omp | 2 | 0.001898 | 1.853 | 92.6% | -| omp | 4 | 0.001549 | 2.269 | 56.7% | -| omp | 8 | 0.001683 | 2.089 | 26.1% | +- mode: seq; threads: 1; time: 0.003516 s; speedup: 1.000; efficiency: 100.0%. +- mode: omp; threads: 2; time: 0.001898 s; speedup: 1.853; efficiency: 92.6%. +- mode: omp; threads: 4; time: 0.001549 s; speedup: 2.269; efficiency: 56.7%. +- mode: omp; threads: 8; time: 0.001683 s; speedup: 2.089; efficiency: 26.1%. Анализ результатов следует делать по двум критериям: - наличие ускорения относительно последовательной версии; - изменение эффективности при росте числа потоков. -В моей реализации выигрыш от OpenMP заметен уже на размере `100000`, однако рост числа потоков после 4 начинает снижать эффективность. Это связано с тем, что часть работы остается последовательной, а также с накладными расходами на создание и синхронизацию потоков. +В моей реализации выигрыш от OpenMP заметен уже на размере `100000`. При этом рост числа потоков после 4 начинает +снижать эффективность, потому что часть работы остается последовательной. Также влияют накладные расходы на создание +и синхронизацию потоков. ## 9. Выводы @@ -140,4 +150,5 @@ for (int chunk = 0; chunk < chunks; ++chunk) { - объединяет отсортированные части в итоговый массив; - проходит функциональные тесты на корректность. -Моя реализация является простой и предсказуемой по структуре, а параллелизм применяется только там, где нет конфликтов по данным. Это делает решение удобным для дальнейшего сравнения с последовательной и другими параллельными версиями. +Моя реализация является простой и предсказуемой по структуре. Параллелизм применяется только там, где нет конфликтов +по данным, поэтому решение удобно сравнивать с последовательной и другими параллельными версиями. diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/report.md b/tasks/yushkova_p_hoare_sorting_simple_merging/report.md index 98bd0e3bac..7ffe6a103b 100644 --- a/tasks/yushkova_p_hoare_sorting_simple_merging/report.md +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/report.md @@ -69,13 +69,20 @@ Speedup считался как `T_seq / T_backend`, efficiency - как `speedu - backend: omp, 2 threads; time: 0.0021860800 s; speedup: 1.395; efficiency: 0.698; notes: `PPC_NUM_THREADS=2`. - backend: omp, 4 threads; time: 0.0018365600 s; speedup: 1.661; efficiency: 0.415; notes: `PPC_NUM_THREADS=4`. - backend: stl, workers: 1; time: 0.0028924000 s; speedup: 1.054; efficiency: 1.054; notes: `PPC_NUM_THREADS=1`. -- backend: stl, workers: 2; time: 0.0021834000 s; speedup: 1.397; efficiency: 0.698; notes: `PPC_NUM_THREADS=4` (включает параллельную ветку). -- backend: tbb, 1 worker; time: 0.0076817000 s; speedup: 0.397; efficiency: 0.397; notes: `PPC_NUM_THREADS=1`, `global_control`. -- backend: tbb, 2 workers; time: 0.0048485400 s; speedup: 0.629; efficiency: 0.315; notes: `PPC_NUM_THREADS=2`, `global_control`. -- backend: tbb, 4 workers; time: 0.0028551400 s; speedup: 1.068; efficiency: 0.267; notes: `PPC_NUM_THREADS=4`, `global_control`. -- backend: all; ranks: 1; threads_per_rank: 12; total_workers: 12; time: 0.0159865600 s; speedup: 0.191; efficiency: 0.016; notes: `mpiexec -n 1`. -- backend: all; ranks: 2; threads_per_rank: 12; total_workers: 24; time: 0.0155627800 s; speedup: 0.196; efficiency: 0.008; notes: `mpiexec -n 2`. -- backend: all; ranks: 4; threads_per_rank: 12; total_workers: 48; time: 0.0125462200 s; speedup: 0.243; efficiency: 0.005; notes: `mpiexec -n 4`. +- backend: stl, workers: 2; time: 0.0021834000 s; speedup: 1.397; efficiency: 0.698; + notes: `PPC_NUM_THREADS=4` включает параллельную ветку. +- backend: tbb, 1 worker; time: 0.0076817000 s; speedup: 0.397; efficiency: 0.397; + notes: `PPC_NUM_THREADS=1`, `global_control`. +- backend: tbb, 2 workers; time: 0.0048485400 s; speedup: 0.629; efficiency: 0.315; + notes: `PPC_NUM_THREADS=2`, `global_control`. +- backend: tbb, 4 workers; time: 0.0028551400 s; speedup: 1.068; efficiency: 0.267; + notes: `PPC_NUM_THREADS=4`, `global_control`. +- backend: all; ranks: 1; threads_per_rank: 12; total_workers: 12; time: 0.0159865600 s; + speedup: 0.191; efficiency: 0.016; notes: `mpiexec -n 1`. +- backend: all; ranks: 2; threads_per_rank: 12; total_workers: 24; time: 0.0155627800 s; + speedup: 0.196; efficiency: 0.008; notes: `mpiexec -n 2`. +- backend: all; ranks: 4; threads_per_rank: 12; total_workers: 48; time: 0.0125462200 s; + speedup: 0.243; efficiency: 0.005; notes: `mpiexec -n 4`. ## 9. Результаты Pipeline @@ -83,11 +90,16 @@ Speedup считался как `T_seq / T_backend`, efficiency - как `speedu - backend: omp, 1 thread; time: 0.0112251400 s; speedup: 0.742; efficiency: 0.742; notes: `PPC_NUM_THREADS=1`. - backend: omp, 4 threads; time: 0.0037493600 s; speedup: 2.220; efficiency: 0.555; notes: `PPC_NUM_THREADS=4`. - backend: stl, workers: 1; time: 0.0086819200 s; speedup: 0.959; efficiency: 0.959; notes: `PPC_NUM_THREADS=1`. -- backend: stl, workers: 2; time: 0.0055119600 s; speedup: 1.510; efficiency: 0.755; notes: `PPC_NUM_THREADS=4`. -- backend: tbb, 1 worker; time: 0.0113887600 s; speedup: 0.731; efficiency: 0.731; notes: `PPC_NUM_THREADS=1`, `global_control`. -- backend: tbb, 4 workers; time: 0.0037098400 s; speedup: 2.244; efficiency: 0.561; notes: `PPC_NUM_THREADS=4`, `global_control`. -- backend: all; ranks: 1; threads_per_rank: 12; total_workers: 12; time: 0.0173728400 s; speedup: 0.479; efficiency: 0.040; notes: `mpiexec -n 1`. -- backend: all; ranks: 4; threads_per_rank: 12; total_workers: 48; time: 0.0142562000 s; speedup: 0.584; efficiency: 0.012; notes: `mpiexec -n 4`. +- backend: stl, workers: 2; time: 0.0055119600 s; speedup: 1.510; efficiency: 0.755; + notes: `PPC_NUM_THREADS=4`. +- backend: tbb, 1 worker; time: 0.0113887600 s; speedup: 0.731; efficiency: 0.731; + notes: `PPC_NUM_THREADS=1`, `global_control`. +- backend: tbb, 4 workers; time: 0.0037098400 s; speedup: 2.244; efficiency: 0.561; + notes: `PPC_NUM_THREADS=4`, `global_control`. +- backend: all; ranks: 1; threads_per_rank: 12; total_workers: 12; time: 0.0173728400 s; + speedup: 0.479; efficiency: 0.040; notes: `mpiexec -n 1`. +- backend: all; ranks: 4; threads_per_rank: 12; total_workers: 48; time: 0.0142562000 s; + speedup: 0.584; efficiency: 0.012; notes: `mpiexec -n 4`. ## 10. Интерпретация результатов diff --git a/tasks/yushkova_p_hoare_sorting_simple_merging/stl/report.md b/tasks/yushkova_p_hoare_sorting_simple_merging/stl/report.md index 930b54851c..a1d6dd1191 100644 --- a/tasks/yushkova_p_hoare_sorting_simple_merging/stl/report.md +++ b/tasks/yushkova_p_hoare_sorting_simple_merging/stl/report.md @@ -81,8 +81,10 @@ STL-backend подключен в общий список задач функц - workers: 1; time: 0.0028924000 s; speedup: 1.054; efficiency: 1.054; notes: `TaskRun`; `PPC_NUM_THREADS=1`. - workers: 1; time: 0.0086819200 s; speedup: 0.959; efficiency: 0.959; notes: `pipeline`; `PPC_NUM_THREADS=1`. -- workers: 2; time: 0.0021834000 s; speedup: 1.397; efficiency: 0.698; notes: `TaskRun`; `PPC_NUM_THREADS=4` (включает параллельную ветку). -- workers: 2; time: 0.0055119600 s; speedup: 1.510; efficiency: 0.755; notes: `pipeline`; `PPC_NUM_THREADS=4` (включает параллельную ветку). +- workers: 2; time: 0.0021834000 s; speedup: 1.397; efficiency: 0.698; notes: `TaskRun`; + `PPC_NUM_THREADS=4` включает параллельную ветку. +- workers: 2; time: 0.0055119600 s; speedup: 1.510; efficiency: 0.755; notes: `pipeline`; + `PPC_NUM_THREADS=4` включает параллельную ветку. ## 9. Выводы From 20690734b1fda70d392aa8b6335d5fcd63955437 Mon Sep 17 00:00:00 2001 From: Proxy-hue Date: Wed, 27 May 2026 19:17:38 +0300 Subject: [PATCH 18/18] Trigger CI