|  | // 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 |