diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0ab4d7e8c8..d308dc2367 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -416,6 +416,7 @@ if (TARGET TBB::tbb) tbb_add_test(SUBDIR tbb NAME test_task_group DEPENDENCIES TBB::tbb) tbb_add_test(SUBDIR tbb NAME test_concurrent_hash_map DEPENDENCIES TBB::tbb) tbb_add_test(SUBDIR tbb NAME test_task_arena DEPENDENCIES TBB::tbb) + tbb_add_test(SUBDIR tbb NAME test_parallel_phase DEPENDENCIES TBB::tbb) tbb_add_test(SUBDIR tbb NAME test_enumerable_thread_specific DEPENDENCIES TBB::tbb) tbb_add_test(SUBDIR tbb NAME test_concurrent_queue DEPENDENCIES TBB::tbb) tbb_add_test(SUBDIR tbb NAME test_resumable_tasks DEPENDENCIES TBB::tbb) diff --git a/test/tbb/test_parallel_phase.cpp b/test/tbb/test_parallel_phase.cpp new file mode 100644 index 0000000000..df6119bfed --- /dev/null +++ b/test/tbb/test_parallel_phase.cpp @@ -0,0 +1,254 @@ +#define TBB_PREVIEW_PARALLEL_PHASE 1 + +#include "common/test.h" +#include "common/utils.h" +#include "common/spin_barrier.h" + +#include "tbb/task_arena.h" +#include "tbb/parallel_for.h" + +struct dummy_func { + void operator()() const { + } +}; + +template +std::size_t measure_median_start_time(tbb::task_arena* ta, const F1& start = F1{}, const F2& end = F2{}) { + std::size_t num_threads = ta ? ta->max_concurrency() : tbb::this_task_arena::max_concurrency(); + std::size_t num_runs = 1000; + std::vector longest_start_times; + longest_start_times.reserve(num_runs); + + std::vector start_times(num_threads); + utils::SpinBarrier barrier(num_threads); + auto measure_start_time = [&] (std::size_t) { + start_times[tbb::this_task_arena::current_thread_index()] = std::chrono::steady_clock::now(); + barrier.wait(); + }; + + auto get_longest_start = [&] (std::chrono::steady_clock::time_point start_time) { + std::size_t longest_time = 0; + for (auto& time : start_times) { + auto diff = std::chrono::duration_cast(time - start_time); + longest_time = std::max(longest_time, std::size_t(diff.count())); + } + return longest_time; + }; + + auto work = [&] { + auto start_time = std::chrono::steady_clock::now(); + start(); + tbb::parallel_for(std::size_t(0), num_threads, measure_start_time, tbb::static_partitioner{}); + end(); + longest_start_times.push_back(get_longest_start(start_time)); + }; + + for (std::size_t i = 0; i < num_runs; ++i) { + if (ta) { + ta->execute(work); + } else { + work(); + } + utils::doDummyWork(i*500); + } + return utils::median(longest_start_times.begin(), longest_start_times.end()); +} + +template +class start_time_collection_base { + friend Impl; +public: + start_time_collection_base(tbb::task_arena& ta, std::size_t ntrials) : + arena(&ta), num_trials(ntrials), start_times(ntrials) {} + + explicit start_time_collection_base(std::size_t ntrials) : + arena(nullptr), num_trials(ntrials), start_times(ntrials) {} + + std::vector measure() { + for (std::size_t i = 0; i < num_trials; ++i) { + std::size_t median_start_time = static_cast(this)->measure_impl(); + start_times[i] = median_start_time; + } + return start_times; + } +protected: + tbb::task_arena* arena; + std::size_t num_trials; + std::vector start_times; +}; + +class start_time_collection : public start_time_collection_base { + using base = start_time_collection_base; + using base::base; + friend base; + + std::size_t measure_impl() { + return measure_median_start_time(arena); + }; +}; + +class start_time_collection_phase_wrapped + : public start_time_collection_base +{ + using base = start_time_collection_base; + using base::base; + friend base; + + std::size_t measure_impl() { + arena->start_parallel_phase(); + auto median_start_time = measure_median_start_time(arena); + arena->end_parallel_phase(/*with_fast_leave*/true); + return median_start_time; + }; +}; + +class start_time_collection_scoped_phase_wrapped + : public start_time_collection_base +{ + using base = start_time_collection_base; + using base::base; + friend base; + + std::size_t measure_impl() { + tbb::task_arena::scoped_parallel_phase phase{*arena}; + auto median_start_time = measure_median_start_time(arena); + return median_start_time; + }; +}; + +class start_time_collection_sequenced_phases + : public start_time_collection_base +{ + using base = start_time_collection_base; + using base::base; + friend base; + + std::size_t measure_impl() { + std::size_t median_start_time; + if (arena) { + median_start_time = measure_median_start_time(arena, + [this] { arena->start_parallel_phase(); }, + [this] { arena->end_parallel_phase(/*with_fast_leave=*/true); }); + } else { + median_start_time = measure_median_start_time(arena, + [] { tbb::this_task_arena::start_parallel_phase(); }, + [] { tbb::this_task_arena::end_parallel_phase(/*with_fast_leave=*/true); }); + } + return median_start_time; + }; +}; + +class start_time_collection_sequenced_scoped_phases + : public start_time_collection_base +{ + using base = start_time_collection_base; + using base::base; + friend base; + + std::size_t measure_impl() { + tbb::task_arena::scoped_parallel_phase* phase = nullptr; + auto median_start_time = measure_median_start_time(arena, + [this, &phase] { + phase = new tbb::task_arena::scoped_parallel_phase{*arena, /*with_fast_leave=*/true}; + }, + [&phase] { + delete phase; + }); + return median_start_time; + }; +}; + +//! \brief \ref interface \ref requirement +TEST_CASE("Check that workers leave faster with leave_policy::fast") { + tbb::task_arena ta_automatic_leave { + tbb::task_arena::automatic, 1, + tbb::task_arena::priority::normal, + tbb::task_arena::leave_policy::automatic + }; + tbb::task_arena ta_fast_leave { + tbb::task_arena::automatic, 1, + tbb::task_arena::priority::normal, + tbb::task_arena::leave_policy::fast + }; + start_time_collection st_collector1{ta_automatic_leave, /*num_trials=*/20}; + start_time_collection st_collector2{ta_fast_leave, /*num_trials=*/20}; + + auto times_automatic = st_collector1.measure(); + auto times_fast = st_collector2.measure(); + + auto median_automatic = utils::median(times_automatic.begin(), times_automatic.end()); + auto median_fast = utils::median(times_fast.begin(), times_fast.end()); + + WARN_MESSAGE(median_automatic < median_fast, + "Expected workers to start new work faster with delayed leave"); +} + +//! \brief \ref interface \ref requirement +TEST_CASE("Parallel Phase retains workers in task_arena") { + tbb::task_arena ta_fast1 { + tbb::task_arena::automatic, 1, + tbb::task_arena::priority::normal, + tbb::task_arena::leave_policy::fast + }; + tbb::task_arena ta_fast2 { + tbb::task_arena::automatic, 1, + tbb::task_arena::priority::normal, + tbb::task_arena::leave_policy::fast + }; + start_time_collection_phase_wrapped st_collector1{ta_fast1, /*num_trials=*/20}; + start_time_collection_scoped_phase_wrapped st_collector_scoped{ta_fast1, /*num_trials=*/20}; + start_time_collection st_collector2{ta_fast2, /*num_trials=*/20}; + + auto times1 = st_collector1.measure(); + auto times2 = st_collector2.measure(); + auto times_scoped = st_collector_scoped.measure(); + + auto median1 = utils::median(times1.begin(), times1.end()); + auto median2 = utils::median(times2.begin(), times2.end()); + auto median_scoped = utils::median(times_scoped.begin(), times_scoped.end()); + + WARN_MESSAGE(median1 < median2, + "Expected workers start new work faster when using parallel_phase"); + + WARN_MESSAGE(median_scoped < median2, + "Expected workers start new work faster when using scoped parallel_phase"); +} + +//! \brief \ref interface \ref requirement +TEST_CASE("Test one-time fast leave") { + tbb::task_arena ta1{}; + tbb::task_arena ta2{}; + start_time_collection st_collector1{ta1, /*num_trials=*/10}; + start_time_collection_sequenced_phases st_collector2{ta2, /*num_trials=*/10}; + start_time_collection_sequenced_scoped_phases st_collector_scoped{ta2, /*num_trials=*/10}; + + auto times1 = st_collector1.measure(); + auto times2 = st_collector2.measure(); + auto times_scoped = st_collector_scoped.measure(); + + auto median1 = utils::median(times1.begin(), times1.end()); + auto median2 = utils::median(times2.begin(), times2.end()); + auto median_scoped = utils::median(times_scoped.begin(), times_scoped.end()); + + WARN_MESSAGE(median1 < median2, + "Expected one-time fast leave setting to slow workers to start new work"); + + WARN_MESSAGE(median1 < median_scoped, + "Expected one-time fast leave setting to slow workers to start new work"); +} + +//! \brief \ref interface \ref requirement +TEST_CASE("Test parallel phase with this_task_arena") { + start_time_collection st_collector1{/*num_trials=*/10}; + start_time_collection_sequenced_phases st_collector2{/*num_trials=*/10}; + + auto times1 = st_collector1.measure(); + auto times2 = st_collector2.measure(); + + auto median1 = utils::median(times1.begin(), times1.end()); + auto median2 = utils::median(times2.begin(), times2.end()); + + WARN_MESSAGE(median1 < median2, + "Expected one-time fast leave setting to slow workers to start new work"); +} + diff --git a/test/tbb/test_task_arena.cpp b/test/tbb/test_task_arena.cpp index c89cdebff4..6bd93d4c0e 100644 --- a/test/tbb/test_task_arena.cpp +++ b/test/tbb/test_task_arena.cpp @@ -2068,252 +2068,3 @@ TEST_CASE("worker threads occupy slots in correct range") { while (counter < 42) { utils::yield(); } } - -#if TBB_PREVIEW_PARALLEL_PHASE - -struct dummy_func { - void operator()() const { - } -}; - -template -std::size_t measure_median_start_time(tbb::task_arena* ta, const F1& start = F1{}, const F2& end = F2{}) { - std::size_t num_threads = ta ? ta->max_concurrency() : tbb::this_task_arena::max_concurrency(); - std::size_t num_runs = 1000; - std::vector longest_start_times; - longest_start_times.reserve(num_runs); - - std::vector start_times(num_threads); - utils::SpinBarrier barrier(num_threads); - auto measure_start_time = [&] (std::size_t) { - start_times[tbb::this_task_arena::current_thread_index()] = std::chrono::steady_clock::now(); - barrier.wait(); - }; - - auto get_longest_start = [&] (std::chrono::steady_clock::time_point start_time) { - std::size_t longest_time = 0; - for (auto& time : start_times) { - auto diff = std::chrono::duration_cast(time - start_time); - longest_time = std::max(longest_time, std::size_t(diff.count())); - } - return longest_time; - }; - - auto work = [&] { - auto start_time = std::chrono::steady_clock::now(); - start(); - tbb::parallel_for(std::size_t(0), num_threads, measure_start_time, tbb::static_partitioner{}); - end(); - longest_start_times.push_back(get_longest_start(start_time)); - }; - - for (std::size_t i = 0; i < num_runs; ++i) { - if (ta) { - ta->execute(work); - } else { - work(); - } - utils::doDummyWork(i*500); - } - return utils::median(longest_start_times.begin(), longest_start_times.end()); -} - -template -class start_time_collection_base { - friend Impl; -public: - start_time_collection_base(tbb::task_arena& ta, std::size_t ntrials) : - arena(&ta), num_trials(ntrials), start_times(ntrials) {} - - explicit start_time_collection_base(std::size_t ntrials) : - arena(nullptr), num_trials(ntrials), start_times(ntrials) {} - - std::vector measure() { - for (std::size_t i = 0; i < num_trials; ++i) { - std::size_t median_start_time = static_cast(this)->measure_impl(); - start_times[i] = median_start_time; - } - return start_times; - } -protected: - tbb::task_arena* arena; - std::size_t num_trials; - std::vector start_times; -}; - -class start_time_collection : public start_time_collection_base { - using base = start_time_collection_base; - using base::base; - friend base; - - std::size_t measure_impl() { - return measure_median_start_time(arena); - }; -}; - -class start_time_collection_phase_wrapped - : public start_time_collection_base -{ - using base = start_time_collection_base; - using base::base; - friend base; - - std::size_t measure_impl() { - arena->start_parallel_phase(); - auto median_start_time = measure_median_start_time(arena); - arena->end_parallel_phase(/*with_fast_leave*/true); - return median_start_time; - }; -}; - -class start_time_collection_scoped_phase_wrapped - : public start_time_collection_base -{ - using base = start_time_collection_base; - using base::base; - friend base; - - std::size_t measure_impl() { - tbb::task_arena::scoped_parallel_phase phase{*arena}; - auto median_start_time = measure_median_start_time(arena); - return median_start_time; - }; -}; - -class start_time_collection_sequenced_phases - : public start_time_collection_base -{ - using base = start_time_collection_base; - using base::base; - friend base; - - std::size_t measure_impl() { - std::size_t median_start_time; - if (arena) { - median_start_time = measure_median_start_time(arena, - [this] { arena->start_parallel_phase(); }, - [this] { arena->end_parallel_phase(/*with_fast_leave=*/true); }); - } else { - median_start_time = measure_median_start_time(arena, - [] { tbb::this_task_arena::start_parallel_phase(); }, - [] { tbb::this_task_arena::end_parallel_phase(/*with_fast_leave=*/true); }); - } - return median_start_time; - }; -}; - -class start_time_collection_sequenced_scoped_phases - : public start_time_collection_base -{ - using base = start_time_collection_base; - using base::base; - friend base; - - std::size_t measure_impl() { - tbb::task_arena::scoped_parallel_phase* phase = nullptr; - auto median_start_time = measure_median_start_time(arena, - [this, &phase] { - phase = new tbb::task_arena::scoped_parallel_phase{*arena, /*with_fast_leave=*/true}; - }, - [&phase] { - delete phase; - }); - return median_start_time; - }; -}; - -//! \brief \ref interface \ref requirement -TEST_CASE("Check that workers leave faster with leave_policy::fast") { - tbb::task_arena ta_automatic_leave { - tbb::task_arena::automatic, 1, - tbb::task_arena::priority::normal, - tbb::task_arena::leave_policy::automatic - }; - tbb::task_arena ta_fast_leave { - tbb::task_arena::automatic, 1, - tbb::task_arena::priority::normal, - tbb::task_arena::leave_policy::fast - }; - start_time_collection st_collector1{ta_automatic_leave, /*num_trials=*/20}; - start_time_collection st_collector2{ta_fast_leave, /*num_trials=*/20}; - - auto times_automatic = st_collector1.measure(); - auto times_fast = st_collector2.measure(); - - auto median_automatic = utils::median(times_automatic.begin(), times_automatic.end()); - auto median_fast = utils::median(times_fast.begin(), times_fast.end()); - - WARN_MESSAGE(median_automatic < median_fast, - "Expected workers to start new work faster with delayed leave"); -} - -//! \brief \ref interface \ref requirement -TEST_CASE("Parallel Phase retains workers in task_arena") { - tbb::task_arena ta_fast1 { - tbb::task_arena::automatic, 1, - tbb::task_arena::priority::normal, - tbb::task_arena::leave_policy::fast - }; - tbb::task_arena ta_fast2 { - tbb::task_arena::automatic, 1, - tbb::task_arena::priority::normal, - tbb::task_arena::leave_policy::fast - }; - start_time_collection_phase_wrapped st_collector1{ta_fast1, /*num_trials=*/20}; - start_time_collection_scoped_phase_wrapped st_collector_scoped{ta_fast1, /*num_trials=*/20}; - start_time_collection st_collector2{ta_fast2, /*num_trials=*/20}; - - auto times1 = st_collector1.measure(); - auto times2 = st_collector2.measure(); - auto times_scoped = st_collector_scoped.measure(); - - auto median1 = utils::median(times1.begin(), times1.end()); - auto median2 = utils::median(times2.begin(), times2.end()); - auto median_scoped = utils::median(times_scoped.begin(), times_scoped.end()); - - WARN_MESSAGE(median1 < median2, - "Expected workers start new work faster when using parallel_phase"); - - WARN_MESSAGE(median_scoped < median2, - "Expected workers start new work faster when using scoped parallel_phase"); -} - -//! \brief \ref interface \ref requirement -TEST_CASE("Test one-time fast leave") { - tbb::task_arena ta1{}; - tbb::task_arena ta2{}; - start_time_collection st_collector1{ta1, /*num_trials=*/10}; - start_time_collection_sequenced_phases st_collector2{ta2, /*num_trials=*/10}; - start_time_collection_sequenced_scoped_phases st_collector_scoped{ta2, /*num_trials=*/10}; - - auto times1 = st_collector1.measure(); - auto times2 = st_collector2.measure(); - auto times_scoped = st_collector_scoped.measure(); - - auto median1 = utils::median(times1.begin(), times1.end()); - auto median2 = utils::median(times2.begin(), times2.end()); - auto median_scoped = utils::median(times_scoped.begin(), times_scoped.end()); - - WARN_MESSAGE(median1 < median2, - "Expected one-time fast leave setting to slow workers to start new work"); - - WARN_MESSAGE(median1 < median_scoped, - "Expected one-time fast leave setting to slow workers to start new work"); -} - -//! \brief \ref interface \ref requirement -TEST_CASE("Test parallel phase with this_task_arena") { - start_time_collection st_collector1{/*num_trials=*/10}; - start_time_collection_sequenced_phases st_collector2{/*num_trials=*/10}; - - auto times1 = st_collector1.measure(); - auto times2 = st_collector2.measure(); - - auto median1 = utils::median(times1.begin(), times1.end()); - auto median2 = utils::median(times2.begin(), times2.end()); - - WARN_MESSAGE(median1 < median2, - "Expected one-time fast leave setting to slow workers to start new work"); -} - -#endif