You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
255 lines
7.8 KiB
255 lines
7.8 KiB
// 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 <memory>
|
|
#include <vector>
|
|
|
|
#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<PostingThread> Create(WaitableEvent* start_event,
|
|
std::unique_ptr<Action> 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> 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> action_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(Delegate);
|
|
};
|
|
|
|
PostingThread(WaitableEvent* start_event, std::unique_ptr<Action> 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<int> {
|
|
public:
|
|
MessageLoopPerfTest()
|
|
: message_loop_task_runner_(SequencedTaskRunnerHandle::Get()),
|
|
run_posting_threads_(WaitableEvent::ResetPolicy::MANUAL,
|
|
WaitableEvent::InitialState::NOT_SIGNALED) {}
|
|
|
|
static std::string ParamInfoToString(
|
|
::testing::TestParamInfo<int> 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 <typename ActionType>
|
|
void RunTest(const int num_posting_threads, TimeDelta duration) {
|
|
std::vector<std::unique_ptr<PostingThread>> threads;
|
|
for (int i = 0; i < num_posting_threads; ++i) {
|
|
threads.emplace_back(PostingThread::Create(
|
|
&run_posting_threads_, std::make_unique<ActionType>(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<SequencedTaskRunner> 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<ContinuouslyPostTasks>(GetParam(), TimeDelta::FromSeconds(3));
|
|
perf_test::PrintResult("task_posting", "",
|
|
PostingThreadCountToString(GetParam()),
|
|
tasks_posted_duration().InMicroseconds() /
|
|
static_cast<double>(num_tasks_posted()),
|
|
"us/task", true);
|
|
perf_test::PrintResult("task_running", "",
|
|
PostingThreadCountToString(GetParam()),
|
|
tasks_run_duration().InMicroseconds() /
|
|
static_cast<double>(num_tasks_run()),
|
|
"us/task", true);
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(,
|
|
MessageLoopPerfTest,
|
|
::testing::Values(1, 5, 10),
|
|
MessageLoopPerfTest::ParamInfoToString);
|
|
} // namespace base
|