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