#include "base/threading/thread.h"
#include <stddef.h>
#include <memory>
#include <vector>
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/current_thread.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_observer.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/perf/perf_result_reporter.h"
#if BUILDFLAG(IS_POSIX)
#include <pthread.h>
#endif
namespace base {
namespace {
const int kNumRuns = 100000;
constexpr char kMetricPrefixThread[] = "Thread.";
constexpr char kMetricClockTimePerHop[] = "wall_time_per_hop";
constexpr char kMetricCpuTimePerHop[] = "cpu_time_per_hop";
constexpr char kStoryBaseTask[] = "task";
constexpr char kStoryBaseTaskWithObserver[] = "task_with_observer";
constexpr char kStoryBaseWaitableEvent[] = "waitable_event";
constexpr char kStoryBaseCondVar[] = "condition_variable";
constexpr char kStorySuffixOneThread[] = "_1_thread";
constexpr char kStorySuffixFourThreads[] = "_4_threads";
#if BUILDFLAG(IS_POSIX)
constexpr char kStoryBasePthreadCondVar[] = "pthread_condition_variable";
#endif
perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
perf_test::PerfResultReporter reporter(kMetricPrefixThread, story_name);
reporter.RegisterImportantMetric(kMetricClockTimePerHop, "us");
reporter.RegisterImportantMetric(kMetricCpuTimePerHop, "us");
return reporter;
}
class ThreadPerfTest : public testing::Test {
public:
ThreadPerfTest()
: done_(WaitableEvent::ResetPolicy::AUTOMATIC,
WaitableEvent::InitialState::NOT_SIGNALED) {}
virtual void Init() {
if (ThreadTicks::IsSupported()) {
ThreadTicks::WaitUntilInitialized();
}
}
virtual void PingPong(int hops) = 0;
virtual void Reset() {}
void TimeOnThread(base::ThreadTicks* ticks, base::WaitableEvent* done) {
*ticks = base::ThreadTicks::Now();
done->Signal();
}
base::ThreadTicks ThreadNow(const base::Thread& thread) {
base::WaitableEvent done(WaitableEvent::ResetPolicy::AUTOMATIC,
WaitableEvent::InitialState::NOT_SIGNALED);
base::ThreadTicks ticks;
thread.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&ThreadPerfTest::TimeOnThread,
base::Unretained(this), &ticks, &done));
done.Wait();
return ticks;
}
void RunPingPongTest(const std::string& story_name, unsigned num_threads) {
std::vector<base::ThreadTicks> thread_starts;
while (threads_.size() < num_threads) {
threads_.push_back(std::make_unique<base::Thread>("PingPonger"));
threads_.back()->Start();
if (base::ThreadTicks::IsSupported()) {
thread_starts.push_back(ThreadNow(*threads_.back()));
}
}
Init();
base::TimeTicks start = base::TimeTicks::Now();
PingPong(kNumRuns);
done_.Wait();
base::TimeTicks end = base::TimeTicks::Now();
base::TimeDelta thread_time;
while (threads_.size()) {
if (base::ThreadTicks::IsSupported()) {
thread_time += ThreadNow(*threads_.back()) - thread_starts.back();
thread_starts.pop_back();
}
threads_.pop_back();
}
Reset();
double us_per_task_clock = (end - start).InMicrosecondsF() / kNumRuns;
double us_per_task_cpu = thread_time.InMicrosecondsF() / kNumRuns;
auto reporter = SetUpReporter(story_name);
reporter.AddResult(kMetricClockTimePerHop, us_per_task_clock);
if (base::ThreadTicks::IsSupported()) {
reporter.AddResult(kMetricCpuTimePerHop, us_per_task_cpu);
}
}
protected:
void FinishMeasurement() { done_.Signal(); }
std::vector<std::unique_ptr<base::Thread>> threads_;
private:
base::WaitableEvent done_;
};
class TaskPerfTest : public ThreadPerfTest {
base::Thread* NextThread(int count) {
return threads_[count % threads_.size()].get();
}
void PingPong(int hops) override {
if (!hops) {
FinishMeasurement();
return;
}
NextThread(hops)->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&ThreadPerfTest::PingPong,
base::Unretained(this), hops - 1));
}
};
TEST_F(TaskPerfTest, TaskPingPong) {
RunPingPongTest(std::string(kStoryBaseTask) + kStorySuffixOneThread, 1);
RunPingPongTest(std::string(kStoryBaseTask) + kStorySuffixFourThreads, 4);
}
class MessageLoopObserver : public base::TaskObserver {
public:
void WillProcessTask(const base::PendingTask& pending_task,
bool was_blocked_or_low_priority) override {}
void DidProcessTask(const base::PendingTask& pending_task) override {}
};
MessageLoopObserver message_loop_observer;
class TaskObserverPerfTest : public TaskPerfTest {
public:
void Init() override {
TaskPerfTest::Init();
for (auto& i : threads_) {
i->task_runner()->PostTask(
FROM_HERE, BindOnce(
[](MessageLoopObserver* observer) {
CurrentThread::Get()->AddTaskObserver(observer);
},
Unretained(&message_loop_observer)));
}
}
};
TEST_F(TaskObserverPerfTest, TaskPingPong) {
RunPingPongTest(
std::string(kStoryBaseTaskWithObserver) + kStorySuffixOneThread, 1);
RunPingPongTest(
std::string(kStoryBaseTaskWithObserver) + kStorySuffixFourThreads, 4);
}
template <typename WaitableEventType>
class EventPerfTest : public ThreadPerfTest {
public:
void Init() override {
for (size_t i = 0; i < threads_.size(); i++) {
events_.push_back(std::make_unique<WaitableEventType>(
WaitableEvent::ResetPolicy::AUTOMATIC,
WaitableEvent::InitialState::NOT_SIGNALED));
}
}
void Reset() override { events_.clear(); }
void WaitAndSignalOnThread(size_t event) {
size_t next_event = (event + 1) % events_.size();
int my_hops = 0;
do {
events_[event]->Wait();
my_hops = --remaining_hops_;
events_[next_event]->Signal();
} while (my_hops > 0);
if (!my_hops) {
FinishMeasurement();
}
}
void PingPong(int hops) override {
remaining_hops_ = hops;
for (size_t i = 0; i < threads_.size(); i++) {
threads_[i]->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&EventPerfTest::WaitAndSignalOnThread,
base::Unretained(this), i));
}
events_.front()->Signal();
}
int remaining_hops_;
std::vector<std::unique_ptr<WaitableEventType>> events_;
};
typedef EventPerfTest<base::WaitableEvent> WaitableEventThreadPerfTest;
TEST_F(WaitableEventThreadPerfTest, EventPingPong) {
RunPingPongTest(
std::string(kStoryBaseWaitableEvent) + kStorySuffixFourThreads, 4);
}
class ConditionVariableEvent {
public:
ConditionVariableEvent(WaitableEvent::ResetPolicy reset_policy,
WaitableEvent::InitialState initial_state)
: cond_(&lock_) {
DCHECK_EQ(WaitableEvent::ResetPolicy::AUTOMATIC, reset_policy);
DCHECK_EQ(WaitableEvent::InitialState::NOT_SIGNALED, initial_state);
}
void Signal() {
{
base::AutoLock scoped_lock(lock_);
signaled_ = true;
}
cond_.Signal();
}
void Wait() {
base::AutoLock scoped_lock(lock_);
while (!signaled_) {
cond_.Wait();
}
signaled_ = false;
}
private:
base::Lock lock_;
base::ConditionVariable cond_;
bool signaled_ = false;
};
typedef EventPerfTest<ConditionVariableEvent> ConditionVariablePerfTest;
TEST_F(ConditionVariablePerfTest, EventPingPong) {
RunPingPongTest(std::string(kStoryBaseCondVar) + kStorySuffixFourThreads, 4);
}
#if BUILDFLAG(IS_POSIX)
class PthreadEvent {
public:
PthreadEvent(WaitableEvent::ResetPolicy reset_policy,
WaitableEvent::InitialState initial_state) {
DCHECK_EQ(WaitableEvent::ResetPolicy::AUTOMATIC, reset_policy);
DCHECK_EQ(WaitableEvent::InitialState::NOT_SIGNALED, initial_state);
pthread_mutex_init(&mutex_, nullptr);
pthread_cond_init(&cond_, nullptr);
signaled_ = false;
}
~PthreadEvent() {
pthread_cond_destroy(&cond_);
pthread_mutex_destroy(&mutex_);
}
void Signal() {
pthread_mutex_lock(&mutex_);
signaled_ = true;
pthread_mutex_unlock(&mutex_);
pthread_cond_signal(&cond_);
}
void Wait() {
pthread_mutex_lock(&mutex_);
while (!signaled_) {
pthread_cond_wait(&cond_, &mutex_);
}
signaled_ = false;
pthread_mutex_unlock(&mutex_);
}
private:
bool signaled_;
pthread_mutex_t mutex_;
pthread_cond_t cond_;
};
typedef EventPerfTest<PthreadEvent> PthreadEventPerfTest;
TEST_F(PthreadEventPerfTest, EventPingPong) {
RunPingPongTest(
std::string(kStoryBasePthreadCondVar) + kStorySuffixFourThreads, 4);
}
#endif
}
}