// 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 "base/synchronization/waitable_event.h"
#include <numeric>
#include "base/threading/simple_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/perf/perf_test.h"
namespace base {
namespace {
class TraceWaitableEvent {
public:
TraceWaitableEvent(size_t samples)
: event_(WaitableEvent::ResetPolicy::AUTOMATIC,
WaitableEvent::InitialState::NOT_SIGNALED),
samples_(samples) {
signal_times_.reserve(samples);
wait_times_.reserve(samples);
}
~TraceWaitableEvent() = default;
void Signal() {
TimeTicks start = TimeTicks::Now();
event_.Signal();
signal_times_.push_back(TimeTicks::Now() - start);
}
void Wait() {
TimeTicks start = TimeTicks::Now();
event_.Wait();
wait_times_.push_back(TimeTicks::Now() - start);
}
bool TimedWaitUntil(const TimeTicks& end_time) {
TimeTicks start = TimeTicks::Now();
bool signaled = event_.TimedWaitUntil(end_time);
wait_times_.push_back(TimeTicks::Now() - start);
return signaled;
}
bool IsSignaled() { return event_.IsSignaled(); }
const std::vector<TimeDelta>& signal_times() const { return signal_times_; }
const std::vector<TimeDelta>& wait_times() const { return wait_times_; }
size_t samples() const { return samples_; }
private:
WaitableEvent event_;
std::vector<TimeDelta> signal_times_;
std::vector<TimeDelta> wait_times_;
const size_t samples_;
DISALLOW_COPY_AND_ASSIGN(TraceWaitableEvent);
};
class SignalerThread : public SimpleThread {
public:
SignalerThread(TraceWaitableEvent* waiter, TraceWaitableEvent* signaler)
: SimpleThread("WaitableEventPerfTest signaler"),
waiter_(waiter),
signaler_(signaler) {}
~SignalerThread() override = default;
void Run() override {
while (!stop_event_.IsSignaled()) {
if (waiter_)
waiter_->Wait();
if (signaler_)
signaler_->Signal();
}
}
// Signals the thread to stop on the next iteration of its loop (which
// will happen immediately if no |waiter_| is present or is signaled.
void RequestStop() { stop_event_.Signal(); }
private:
WaitableEvent stop_event_{WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED};
TraceWaitableEvent* waiter_;
TraceWaitableEvent* signaler_;
DISALLOW_COPY_AND_ASSIGN(SignalerThread);
};
void PrintPerfWaitableEvent(const TraceWaitableEvent* event,
const std::string& modifier,
const std::string& trace) {
TimeDelta signal_time = std::accumulate(
event->signal_times().begin(), event->signal_times().end(), TimeDelta());
TimeDelta wait_time = std::accumulate(event->wait_times().begin(),
event->wait_times().end(), TimeDelta());
perf_test::PrintResult(
"signal_time", modifier, trace,
static_cast<size_t>(signal_time.InNanoseconds()) / event->samples(),
"ns/sample", true);
perf_test::PrintResult(
"wait_time", modifier, trace,
static_cast<size_t>(wait_time.InNanoseconds()) / event->samples(),
"ns/sample", true);
}
} // namespace
TEST(WaitableEventPerfTest, SingleThread) {
const size_t kSamples = 1000;
TraceWaitableEvent event(kSamples);
for (size_t i = 0; i < kSamples; ++i) {
event.Signal();
event.Wait();
}
PrintPerfWaitableEvent(&event, "", "singlethread-1000-samples");
}
TEST(WaitableEventPerfTest, MultipleThreads) {
const size_t kSamples = 1000;
TraceWaitableEvent waiter(kSamples);
TraceWaitableEvent signaler(kSamples);
// The other thread will wait and signal on the respective opposite events.
SignalerThread thread(&signaler, &waiter);
thread.Start();
for (size_t i = 0; i < kSamples; ++i) {
signaler.Signal();
waiter.Wait();
}
// Signal the stop event and then make sure the signaler event it is
// waiting on is also signaled.
thread.RequestStop();
signaler.Signal();
thread.Join();
PrintPerfWaitableEvent(&waiter, "_waiter", "multithread-1000-samples");
PrintPerfWaitableEvent(&signaler, "_signaler", "multithread-1000-samples");
}
TEST(WaitableEventPerfTest, Throughput) {
// Reserve a lot of sample space.
const size_t kCapacity = 500000;
TraceWaitableEvent event(kCapacity);
SignalerThread thread(nullptr, &event);
thread.Start();
TimeTicks end_time = TimeTicks::Now() + TimeDelta::FromSeconds(1);
size_t count = 0;
while (event.TimedWaitUntil(end_time)) {
++count;
}
thread.RequestStop();
thread.Join();
perf_test::PrintResult("counts", "", "throughput", count, "signals", true);
PrintPerfWaitableEvent(&event, "", "throughput");
// Make sure that allocation didn't happen during the test.
EXPECT_LE(event.signal_times().capacity(), kCapacity);
EXPECT_LE(event.wait_times().capacity(), kCapacity);
}
} // namespace base