// Copyright 2018 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/task/thread_pool.h" #include #include #include #include #include #include "base/barrier_closure.h" #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/functional/callback_helpers.h" #include "base/memory/raw_ptr.h" #include "base/synchronization/waitable_event.h" #include "base/task/thread_pool/thread_pool_instance.h" #include "base/threading/simple_thread.h" #include "base/time/time.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/perf/perf_result_reporter.h" namespace base { namespace internal { namespace { constexpr char kMetricPrefixThreadPool[] = "ThreadPool."; constexpr char kMetricPostTaskThroughput[] = "post_task_throughput"; constexpr char kMetricRunTaskThroughput[] = "run_task_throughput"; constexpr char kMetricNumTasksPosted[] = "num_tasks_posted"; constexpr char kStoryBindPostThenRunNoOp[] = "bind_post_then_run_noop_tasks"; constexpr char kStoryPostThenRunNoOp[] = "post_then_run_noop_tasks"; constexpr char kStoryPostThenRunNoOpManyThreads[] = "post_then_run_noop_tasks_many_threads"; constexpr char kStoryPostThenRunNoOpMoreThanRunningThreads[] = "post_then_run_noop_tasks_more_than_running_threads"; constexpr char kStoryPostRunNoOp[] = "post_run_noop_tasks"; constexpr char kStoryPostRunNoOpManyThreads[] = "post_run_noop_tasks_many_threads"; constexpr char kStoryPostRunBusyManyThreads[] = "post_run_busy_tasks_many_threads"; perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) { perf_test::PerfResultReporter reporter(kMetricPrefixThreadPool, story_name); reporter.RegisterImportantMetric(kMetricPostTaskThroughput, "runs/s"); reporter.RegisterImportantMetric(kMetricRunTaskThroughput, "runs/s"); reporter.RegisterImportantMetric(kMetricNumTasksPosted, "count"); return reporter; } enum class ExecutionMode { // Allows tasks to start running while tasks are being posted by posting // threads. kPostAndRun, // Uses an execution fence to wait for all posting threads to be done before // running tasks that were posted. kPostThenRun, }; // A thread that waits for the caller to signal an event before proceeding to // call action.Run(). class PostingThread : public SimpleThread { public: // Creates a PostingThread that waits on |start_event| before calling // action.Run(). PostingThread(WaitableEvent* start_event, base::OnceClosure action, base::OnceClosure completion) : SimpleThread("PostingThread"), start_event_(start_event), action_(std::move(action)), completion_(std::move(completion)) { Start(); } PostingThread(const PostingThread&) = delete; PostingThread& operator=(const PostingThread&) = delete; void Run() override { start_event_->Wait(); std::move(action_).Run(); std::move(completion_).Run(); } private: const raw_ptr start_event_; base::OnceClosure action_; base::OnceClosure completion_; }; class ThreadPoolPerfTest : public testing::Test { public: ThreadPoolPerfTest(const ThreadPoolPerfTest&) = delete; ThreadPoolPerfTest& operator=(const ThreadPoolPerfTest&) = delete; // Posting actions: void ContinuouslyBindAndPostNoOpTasks(size_t num_tasks) { scoped_refptr task_runner = ThreadPool::CreateTaskRunner({}); for (size_t i = 0; i < num_tasks; ++i) { ++num_tasks_pending_; ++num_posted_tasks_; task_runner->PostTask(FROM_HERE, base::BindOnce( [](std::atomic_size_t* num_task_pending) { (*num_task_pending)--; }, &num_tasks_pending_)); } } void ContinuouslyPostNoOpTasks(size_t num_tasks) { scoped_refptr task_runner = ThreadPool::CreateTaskRunner({}); base::RepeatingClosure closure = base::BindRepeating( [](std::atomic_size_t* num_task_pending) { (*num_task_pending)--; }, &num_tasks_pending_); for (size_t i = 0; i < num_tasks; ++i) { ++num_tasks_pending_; ++num_posted_tasks_; task_runner->PostTask(FROM_HERE, closure); } } void ContinuouslyPostBusyWaitTasks(size_t num_tasks, base::TimeDelta duration) { scoped_refptr task_runner = ThreadPool::CreateTaskRunner({}); base::RepeatingClosure closure = base::BindRepeating( [](std::atomic_size_t* num_task_pending, base::TimeDelta duration) { base::TimeTicks end_time = base::TimeTicks::Now() + duration; while (base::TimeTicks::Now() < end_time) { } (*num_task_pending)--; }, Unretained(&num_tasks_pending_), duration); for (size_t i = 0; i < num_tasks; ++i) { ++num_tasks_pending_; ++num_posted_tasks_; task_runner->PostTask(FROM_HERE, closure); } } protected: ThreadPoolPerfTest() { ThreadPoolInstance::Create("PerfTest"); } ~ThreadPoolPerfTest() override { ThreadPoolInstance::Set(nullptr); } void StartThreadPool(size_t num_running_threads, size_t num_posting_threads, base::RepeatingClosure post_action) { ThreadPoolInstance::Get()->Start({num_running_threads}); base::RepeatingClosure done = BarrierClosure( num_posting_threads, base::BindOnce(&ThreadPoolPerfTest::OnCompletePostingTasks, base::Unretained(this))); for (size_t i = 0; i < num_posting_threads; ++i) { threads_.emplace_back(std::make_unique( &start_posting_tasks_, post_action, done)); } } void OnCompletePostingTasks() { complete_posting_tasks_.Signal(); } void Benchmark(const std::string& story_name, ExecutionMode execution_mode) { std::optional execution_fence; if (execution_mode == ExecutionMode::kPostThenRun) { execution_fence.emplace(); } TimeTicks tasks_run_start = TimeTicks::Now(); start_posting_tasks_.Signal(); complete_posting_tasks_.Wait(); post_task_duration_ = TimeTicks::Now() - tasks_run_start; if (execution_mode == ExecutionMode::kPostThenRun) { tasks_run_start = TimeTicks::Now(); execution_fence.reset(); } // Wait for no pending tasks. ThreadPoolInstance::Get()->FlushForTesting(); tasks_run_duration_ = TimeTicks::Now() - tasks_run_start; ASSERT_EQ(0U, num_tasks_pending_); for (auto& thread : threads_) thread->Join(); ThreadPoolInstance::Get()->JoinForTesting(); auto reporter = SetUpReporter(story_name); reporter.AddResult( kMetricPostTaskThroughput, num_posted_tasks_ / static_cast(post_task_duration_.InSecondsF())); reporter.AddResult( kMetricRunTaskThroughput, num_posted_tasks_ / static_cast(tasks_run_duration_.InSecondsF())); reporter.AddResult(kMetricNumTasksPosted, num_posted_tasks_); } private: WaitableEvent start_posting_tasks_; WaitableEvent complete_posting_tasks_; TimeDelta post_task_duration_; TimeDelta tasks_run_duration_; std::atomic_size_t num_tasks_pending_{0}; std::atomic_size_t num_posted_tasks_{0}; std::vector> threads_; }; } // namespace TEST_F(ThreadPoolPerfTest, BindPostThenRunNoOpTasks) { StartThreadPool( 1, 1, BindRepeating(&ThreadPoolPerfTest::ContinuouslyBindAndPostNoOpTasks, Unretained(this), 10000)); Benchmark(kStoryBindPostThenRunNoOp, ExecutionMode::kPostThenRun); } TEST_F(ThreadPoolPerfTest, PostThenRunNoOpTasks) { StartThreadPool(1, 1, BindRepeating(&ThreadPoolPerfTest::ContinuouslyPostNoOpTasks, Unretained(this), 10000)); Benchmark(kStoryPostThenRunNoOp, ExecutionMode::kPostThenRun); } TEST_F(ThreadPoolPerfTest, PostThenRunNoOpTasksManyThreads) { StartThreadPool(4, 4, BindRepeating(&ThreadPoolPerfTest::ContinuouslyPostNoOpTasks, Unretained(this), 10000)); Benchmark(kStoryPostThenRunNoOpManyThreads, ExecutionMode::kPostThenRun); } TEST_F(ThreadPoolPerfTest, PostThenRunNoOpTasksMorePostingThanRunningThreads) { StartThreadPool(1, 4, BindRepeating(&ThreadPoolPerfTest::ContinuouslyPostNoOpTasks, Unretained(this), 10000)); Benchmark(kStoryPostThenRunNoOpMoreThanRunningThreads, ExecutionMode::kPostThenRun); } TEST_F(ThreadPoolPerfTest, PostRunNoOpTasks) { StartThreadPool(1, 1, BindRepeating(&ThreadPoolPerfTest::ContinuouslyPostNoOpTasks, Unretained(this), 10000)); Benchmark(kStoryPostRunNoOp, ExecutionMode::kPostAndRun); } TEST_F(ThreadPoolPerfTest, PostRunNoOpTasksManyThreads) { StartThreadPool(4, 4, BindRepeating(&ThreadPoolPerfTest::ContinuouslyPostNoOpTasks, Unretained(this), 10000)); Benchmark(kStoryPostRunNoOpManyThreads, ExecutionMode::kPostAndRun); } TEST_F(ThreadPoolPerfTest, PostRunBusyTasksManyThreads) { StartThreadPool( 4, 4, BindRepeating(&ThreadPoolPerfTest::ContinuouslyPostBusyWaitTasks, Unretained(this), 10000, base::Microseconds(200))); Benchmark(kStoryPostRunBusyManyThreads, ExecutionMode::kPostAndRun); } } // namespace internal } // namespace base