| // Copyright 2014 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 <stddef.h> | 
 |  | 
 | #include <memory> | 
 | #include <vector> | 
 |  | 
 | #include "base/base_switches.h" | 
 | #include "base/bind.h" | 
 | #include "base/command_line.h" | 
 | #include "base/location.h" | 
 | #include "base/memory/ptr_util.h" | 
 | #include "base/message_loop/message_loop.h" | 
 | #include "base/single_thread_task_runner.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "base/synchronization/condition_variable.h" | 
 | #include "base/synchronization/lock.h" | 
 | #include "base/synchronization/waitable_event.h" | 
 | #include "base/threading/thread.h" | 
 | #include "base/time/time.h" | 
 | #include "build/build_config.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 | #include "testing/perf/perf_test.h" | 
 |  | 
 | #if defined(OS_POSIX) | 
 | #include <pthread.h> | 
 | #endif | 
 |  | 
 | namespace base { | 
 |  | 
 | namespace { | 
 |  | 
 | const int kNumRuns = 100000; | 
 |  | 
 | // Base class for a threading perf-test. This sets up some threads for the | 
 | // test and measures the clock-time in addition to time spent on each thread. | 
 | class ThreadPerfTest : public testing::Test { | 
 |  public: | 
 |   ThreadPerfTest() | 
 |       : done_(WaitableEvent::ResetPolicy::AUTOMATIC, | 
 |               WaitableEvent::InitialState::NOT_SIGNALED) {} | 
 |  | 
 |   // To be implemented by each test. Subclass must uses threads_ such that | 
 |   // their cpu-time can be measured. Test must return from PingPong() _and_ | 
 |   // call FinishMeasurement from any thread to complete the test. | 
 |   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& name, unsigned num_threads) { | 
 |     // Create threads and collect starting cpu-time for each thread. | 
 |     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(); | 
 |  | 
 |     // Gather the cpu-time spent on each thread. This does one extra tasks, | 
 |     // but that should be in the noise given enough runs. | 
 |     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 num_runs = static_cast<double>(kNumRuns); | 
 |     double us_per_task_clock = (end - start).InMicroseconds() / num_runs; | 
 |     double us_per_task_cpu = thread_time.InMicroseconds() / num_runs; | 
 |  | 
 |     // Clock time per task. | 
 |     perf_test::PrintResult( | 
 |         "task", "", name + "_time ", us_per_task_clock, "us/hop", true); | 
 |  | 
 |     // Total utilization across threads if available (likely higher). | 
 |     if (base::ThreadTicks::IsSupported()) { | 
 |       perf_test::PrintResult( | 
 |           "task", "", name + "_cpu ", us_per_task_cpu, "us/hop", true); | 
 |     } | 
 |   } | 
 |  | 
 |  protected: | 
 |   void FinishMeasurement() { done_.Signal(); } | 
 |   std::vector<std::unique_ptr<base::Thread>> threads_; | 
 |  | 
 |  private: | 
 |   base::WaitableEvent done_; | 
 | }; | 
 |  | 
 | // Class to test task performance by posting empty tasks back and forth. | 
 | 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)); | 
 |   } | 
 | }; | 
 |  | 
 | // This tries to test the 'best-case' as well as the 'worst-case' task posting | 
 | // performance. The best-case keeps one thread alive such that it never yeilds, | 
 | // while the worse-case forces a context switch for every task. Four threads are | 
 | // used to ensure the threads do yeild (with just two it might be possible for | 
 | // both threads to stay awake if they can signal each other fast enough). | 
 | TEST_F(TaskPerfTest, TaskPingPong) { | 
 |   RunPingPongTest("1_Task_Threads", 1); | 
 |   RunPingPongTest("4_Task_Threads", 4); | 
 | } | 
 |  | 
 |  | 
 | // Same as above, but add observers to test their perf impact. | 
 | class MessageLoopObserver : public base::MessageLoop::TaskObserver { | 
 |  public: | 
 |   void WillProcessTask(const base::PendingTask& pending_task) override {} | 
 |   void DidProcessTask(const base::PendingTask& pending_task) override {} | 
 | }; | 
 | MessageLoopObserver message_loop_observer; | 
 |  | 
 | class TaskObserverPerfTest : public TaskPerfTest { | 
 |  public: | 
 |   void Init() override { | 
 |     TaskPerfTest::Init(); | 
 |     for (size_t i = 0; i < threads_.size(); i++) { | 
 |       threads_[i]->message_loop()->AddTaskObserver(&message_loop_observer); | 
 |     } | 
 |   } | 
 | }; | 
 |  | 
 | TEST_F(TaskObserverPerfTest, TaskPingPong) { | 
 |   RunPingPongTest("1_Task_Threads_With_Observer", 1); | 
 |   RunPingPongTest("4_Task_Threads_With_Observer", 4); | 
 | } | 
 |  | 
 | // Class to test our WaitableEvent performance by signaling back and fort. | 
 | // WaitableEvent is templated so we can also compare with other versions. | 
 | 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_;  // We own 'hops' between Wait and Signal. | 
 |       events_[next_event]->Signal(); | 
 |     } while (my_hops > 0); | 
 |     // Once we are done, all threads will signal as hops passes zero. | 
 |     // We only signal completion once, on the thread that reaches zero. | 
 |     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)); | 
 |     } | 
 |  | 
 |     // Kick off the Signal ping-ponging. | 
 |     events_.front()->Signal(); | 
 |   } | 
 |  | 
 |   int remaining_hops_; | 
 |   std::vector<std::unique_ptr<WaitableEventType>> events_; | 
 | }; | 
 |  | 
 | // Similar to the task posting test, this just tests similar functionality | 
 | // using WaitableEvents. We only test four threads (worst-case), but we | 
 | // might want to craft a way to test the best-case (where the thread doesn't | 
 | // end up blocking because the event is already signalled). | 
 | typedef EventPerfTest<base::WaitableEvent> WaitableEventThreadPerfTest; | 
 | TEST_F(WaitableEventThreadPerfTest, EventPingPong) { | 
 |   RunPingPongTest("4_WaitableEvent_Threads", 4); | 
 | } | 
 |  | 
 | // Build a minimal event using ConditionVariable. | 
 | class ConditionVariableEvent { | 
 |  public: | 
 |   ConditionVariableEvent(WaitableEvent::ResetPolicy reset_policy, | 
 |                          WaitableEvent::InitialState initial_state) | 
 |       : cond_(&lock_), signaled_(false) { | 
 |     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_; | 
 | }; | 
 |  | 
 | // This is meant to test the absolute minimal context switching time | 
 | // using our own base synchronization code. | 
 | typedef EventPerfTest<ConditionVariableEvent> ConditionVariablePerfTest; | 
 | TEST_F(ConditionVariablePerfTest, EventPingPong) { | 
 |   RunPingPongTest("4_ConditionVariable_Threads", 4); | 
 | } | 
 | #if defined(OS_POSIX) | 
 |  | 
 | // Absolutely 100% minimal posix waitable event. If there is a better/faster | 
 | // way to force a context switch, we should use that instead. | 
 | 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_; | 
 | }; | 
 |  | 
 | // This is meant to test the absolute minimal context switching time. | 
 | // If there is any faster way to do this we should substitute it in. | 
 | typedef EventPerfTest<PthreadEvent> PthreadEventPerfTest; | 
 | TEST_F(PthreadEventPerfTest, EventPingPong) { | 
 |   RunPingPongTest("4_PthreadCondVar_Threads", 4); | 
 | } | 
 |  | 
 | #endif | 
 |  | 
 | }  // namespace | 
 |  | 
 | }  // namespace base |