// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include "base/atomicops.h" #include "base/bind.h" #include "base/callback.h" #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/strings/stringprintf.h" #include "base/synchronization/atomic_flag.h" #include "base/synchronization/waitable_event.h" #include "base/threading/platform_thread.h" #include "base/threading/sequenced_task_runner_handle.h" #include "base/time/time.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/perf/perf_test.h" namespace base { namespace { // A thread that waits for the caller to signal an event before proceeding to // call Action::Run(). class PostingThread { public: class Action { public: virtual ~Action() = default; // Called after the thread is started and |start_event_| is signalled. virtual void Run() = 0; protected: Action() = default; private: DISALLOW_COPY_AND_ASSIGN(Action); }; // Creates a PostingThread where the thread waits on |start_event| before // calling action->Run(). If a thread is returned, the thread is guaranteed to // be allocated and running and the caller must call Join() before destroying // the PostingThread. static std::unique_ptr Create(WaitableEvent* start_event, std::unique_ptr action) { auto posting_thread = WrapUnique(new PostingThread(start_event, std::move(action))); if (!posting_thread->Start()) return nullptr; return posting_thread; } ~PostingThread() { DCHECK_EQ(!thread_handle_.is_null(), join_called_); } void Join() { PlatformThread::Join(thread_handle_); join_called_ = true; } private: class Delegate final : public PlatformThread::Delegate { public: Delegate(PostingThread* outer, std::unique_ptr action) : outer_(outer), action_(std::move(action)) { DCHECK(outer_); DCHECK(action_); } ~Delegate() override = default; private: void ThreadMain() override { outer_->thread_started_.Signal(); outer_->start_event_->Wait(); action_->Run(); } PostingThread* const outer_; const std::unique_ptr action_; DISALLOW_COPY_AND_ASSIGN(Delegate); }; PostingThread(WaitableEvent* start_event, std::unique_ptr delegate) : start_event_(start_event), thread_started_(WaitableEvent::ResetPolicy::MANUAL, WaitableEvent::InitialState::NOT_SIGNALED), delegate_(this, std::move(delegate)) { DCHECK(start_event_); } bool Start() { bool thread_created = PlatformThread::Create(0, &delegate_, &thread_handle_); if (thread_created) thread_started_.Wait(); return thread_created; } bool join_called_ = false; WaitableEvent* const start_event_; WaitableEvent thread_started_; Delegate delegate_; PlatformThreadHandle thread_handle_; DISALLOW_COPY_AND_ASSIGN(PostingThread); }; class MessageLoopPerfTest : public ::testing::TestWithParam { public: MessageLoopPerfTest() : message_loop_task_runner_(SequencedTaskRunnerHandle::Get()), run_posting_threads_(WaitableEvent::ResetPolicy::MANUAL, WaitableEvent::InitialState::NOT_SIGNALED) {} static std::string ParamInfoToString( ::testing::TestParamInfo param_info) { return PostingThreadCountToString(param_info.param); } static std::string PostingThreadCountToString(int posting_threads) { // Special case 1 thread for thread vs threads. if (posting_threads == 1) return "1_Posting_Thread"; return StringPrintf("%d_Posting_Threads", posting_threads); } protected: class ContinuouslyPostTasks final : public PostingThread::Action { public: ContinuouslyPostTasks(MessageLoopPerfTest* outer) : outer_(outer) { DCHECK(outer_); } ~ContinuouslyPostTasks() override = default; private: void Run() override { RepeatingClosure task_to_run = BindRepeating([](size_t* num_tasks_run) { ++*num_tasks_run; }, &outer_->num_tasks_run_); while (!outer_->stop_posting_threads_.IsSet()) { outer_->message_loop_task_runner_->PostTask(FROM_HERE, task_to_run); subtle::NoBarrier_AtomicIncrement(&outer_->num_tasks_posted_, 1); } } MessageLoopPerfTest* const outer_; DISALLOW_COPY_AND_ASSIGN(ContinuouslyPostTasks); }; void SetUp() override { // This check is here because we can't ASSERT_TRUE in the constructor. ASSERT_TRUE(message_loop_task_runner_); } // Runs ActionType::Run() on |num_posting_threads| and requests test // termination around |duration|. template void RunTest(const int num_posting_threads, TimeDelta duration) { std::vector> threads; for (int i = 0; i < num_posting_threads; ++i) { threads.emplace_back(PostingThread::Create( &run_posting_threads_, std::make_unique(this))); // Don't assert here to simplify the code that requires a Join() call for // every created PostingThread. EXPECT_TRUE(threads[i]); } RunLoop run_loop; message_loop_task_runner_->PostDelayedTask( FROM_HERE, BindOnce( [](RunLoop* run_loop, AtomicFlag* stop_posting_threads) { stop_posting_threads->Set(); run_loop->Quit(); }, &run_loop, &stop_posting_threads_), duration); TimeTicks post_task_start = TimeTicks::Now(); run_posting_threads_.Signal(); TimeTicks run_loop_start = TimeTicks::Now(); run_loop.Run(); tasks_run_duration_ = TimeTicks::Now() - run_loop_start; for (auto& thread : threads) thread->Join(); tasks_posted_duration_ = TimeTicks::Now() - post_task_start; } size_t num_tasks_posted() const { return subtle::NoBarrier_Load(&num_tasks_posted_); } TimeDelta tasks_posted_duration() const { return tasks_posted_duration_; } size_t num_tasks_run() const { return num_tasks_run_; } TimeDelta tasks_run_duration() const { return tasks_run_duration_; } private: MessageLoop message_loop_; // Accessed on multiple threads, thread-safe or constant: const scoped_refptr message_loop_task_runner_; WaitableEvent run_posting_threads_; AtomicFlag stop_posting_threads_; subtle::AtomicWord num_tasks_posted_ = 0; // Accessed only on the test case thread: TimeDelta tasks_posted_duration_; TimeDelta tasks_run_duration_; size_t num_tasks_run_ = 0; DISALLOW_COPY_AND_ASSIGN(MessageLoopPerfTest); }; } // namespace TEST_P(MessageLoopPerfTest, PostTaskRate) { // Measures the average rate of posting tasks from different threads and the // average rate that the message loop is running those tasks. RunTest(GetParam(), TimeDelta::FromSeconds(3)); perf_test::PrintResult("task_posting", "", PostingThreadCountToString(GetParam()), tasks_posted_duration().InMicroseconds() / static_cast(num_tasks_posted()), "us/task", true); perf_test::PrintResult("task_running", "", PostingThreadCountToString(GetParam()), tasks_run_duration().InMicroseconds() / static_cast(num_tasks_run()), "us/task", true); } INSTANTIATE_TEST_CASE_P(, MessageLoopPerfTest, ::testing::Values(1, 5, 10), MessageLoopPerfTest::ParamInfoToString); } // namespace base