thread_perftest.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include <stddef.h>
  5. #include <memory>
  6. #include <vector>
  7. #include "base/base_switches.h"
  8. #include "base/bind.h"
  9. #include "base/command_line.h"
  10. #include "base/location.h"
  11. #include "base/memory/ptr_util.h"
  12. #include "base/synchronization/condition_variable.h"
  13. #include "base/synchronization/lock.h"
  14. #include "base/synchronization/waitable_event.h"
  15. #include "base/task/current_thread.h"
  16. #include "base/task/single_thread_task_runner.h"
  17. #include "base/task/task_observer.h"
  18. #include "base/threading/thread.h"
  19. #include "base/time/time.h"
  20. #include "build/build_config.h"
  21. #include "testing/gtest/include/gtest/gtest.h"
  22. #include "testing/perf/perf_result_reporter.h"
  23. #if BUILDFLAG(IS_POSIX)
  24. #include <pthread.h>
  25. #endif
  26. namespace base {
  27. namespace {
  28. const int kNumRuns = 100000;
  29. constexpr char kMetricPrefixThread[] = "Thread.";
  30. constexpr char kMetricClockTimePerHop[] = "wall_time_per_hop";
  31. constexpr char kMetricCpuTimePerHop[] = "cpu_time_per_hop";
  32. constexpr char kStoryBaseTask[] = "task";
  33. constexpr char kStoryBaseTaskWithObserver[] = "task_with_observer";
  34. constexpr char kStoryBaseWaitableEvent[] = "waitable_event";
  35. constexpr char kStoryBaseCondVar[] = "condition_variable";
  36. constexpr char kStorySuffixOneThread[] = "_1_thread";
  37. constexpr char kStorySuffixFourThreads[] = "_4_threads";
  38. #if BUILDFLAG(IS_POSIX)
  39. constexpr char kStoryBasePthreadCondVar[] = "pthread_condition_variable";
  40. #endif // BUILDFLAG(IS_POSIX)
  41. perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
  42. perf_test::PerfResultReporter reporter(kMetricPrefixThread, story_name);
  43. reporter.RegisterImportantMetric(kMetricClockTimePerHop, "us");
  44. reporter.RegisterImportantMetric(kMetricCpuTimePerHop, "us");
  45. return reporter;
  46. }
  47. // Base class for a threading perf-test. This sets up some threads for the
  48. // test and measures the clock-time in addition to time spent on each thread.
  49. class ThreadPerfTest : public testing::Test {
  50. public:
  51. ThreadPerfTest()
  52. : done_(WaitableEvent::ResetPolicy::AUTOMATIC,
  53. WaitableEvent::InitialState::NOT_SIGNALED) {}
  54. // To be implemented by each test. Subclass must uses threads_ such that
  55. // their cpu-time can be measured. Test must return from PingPong() _and_
  56. // call FinishMeasurement from any thread to complete the test.
  57. virtual void Init() {
  58. if (ThreadTicks::IsSupported())
  59. ThreadTicks::WaitUntilInitialized();
  60. }
  61. virtual void PingPong(int hops) = 0;
  62. virtual void Reset() {}
  63. void TimeOnThread(base::ThreadTicks* ticks, base::WaitableEvent* done) {
  64. *ticks = base::ThreadTicks::Now();
  65. done->Signal();
  66. }
  67. base::ThreadTicks ThreadNow(const base::Thread& thread) {
  68. base::WaitableEvent done(WaitableEvent::ResetPolicy::AUTOMATIC,
  69. WaitableEvent::InitialState::NOT_SIGNALED);
  70. base::ThreadTicks ticks;
  71. thread.task_runner()->PostTask(
  72. FROM_HERE, base::BindOnce(&ThreadPerfTest::TimeOnThread,
  73. base::Unretained(this), &ticks, &done));
  74. done.Wait();
  75. return ticks;
  76. }
  77. void RunPingPongTest(const std::string& story_name, unsigned num_threads) {
  78. // Create threads and collect starting cpu-time for each thread.
  79. std::vector<base::ThreadTicks> thread_starts;
  80. while (threads_.size() < num_threads) {
  81. threads_.push_back(std::make_unique<base::Thread>("PingPonger"));
  82. threads_.back()->Start();
  83. if (base::ThreadTicks::IsSupported())
  84. thread_starts.push_back(ThreadNow(*threads_.back()));
  85. }
  86. Init();
  87. base::TimeTicks start = base::TimeTicks::Now();
  88. PingPong(kNumRuns);
  89. done_.Wait();
  90. base::TimeTicks end = base::TimeTicks::Now();
  91. // Gather the cpu-time spent on each thread. This does one extra tasks,
  92. // but that should be in the noise given enough runs.
  93. base::TimeDelta thread_time;
  94. while (threads_.size()) {
  95. if (base::ThreadTicks::IsSupported()) {
  96. thread_time += ThreadNow(*threads_.back()) - thread_starts.back();
  97. thread_starts.pop_back();
  98. }
  99. threads_.pop_back();
  100. }
  101. Reset();
  102. double us_per_task_clock = (end - start).InMicrosecondsF() / kNumRuns;
  103. double us_per_task_cpu = thread_time.InMicrosecondsF() / kNumRuns;
  104. auto reporter = SetUpReporter(story_name);
  105. // Clock time per task.
  106. reporter.AddResult(kMetricClockTimePerHop, us_per_task_clock);
  107. // Total utilization across threads if available (likely higher).
  108. if (base::ThreadTicks::IsSupported()) {
  109. reporter.AddResult(kMetricCpuTimePerHop, us_per_task_cpu);
  110. }
  111. }
  112. protected:
  113. void FinishMeasurement() { done_.Signal(); }
  114. std::vector<std::unique_ptr<base::Thread>> threads_;
  115. private:
  116. base::WaitableEvent done_;
  117. };
  118. // Class to test task performance by posting empty tasks back and forth.
  119. class TaskPerfTest : public ThreadPerfTest {
  120. base::Thread* NextThread(int count) {
  121. return threads_[count % threads_.size()].get();
  122. }
  123. void PingPong(int hops) override {
  124. if (!hops) {
  125. FinishMeasurement();
  126. return;
  127. }
  128. NextThread(hops)->task_runner()->PostTask(
  129. FROM_HERE, base::BindOnce(&ThreadPerfTest::PingPong,
  130. base::Unretained(this), hops - 1));
  131. }
  132. };
  133. // This tries to test the 'best-case' as well as the 'worst-case' task posting
  134. // performance. The best-case keeps one thread alive such that it never yeilds,
  135. // while the worse-case forces a context switch for every task. Four threads are
  136. // used to ensure the threads do yeild (with just two it might be possible for
  137. // both threads to stay awake if they can signal each other fast enough).
  138. TEST_F(TaskPerfTest, TaskPingPong) {
  139. RunPingPongTest(std::string(kStoryBaseTask) + kStorySuffixOneThread, 1);
  140. RunPingPongTest(std::string(kStoryBaseTask) + kStorySuffixFourThreads, 4);
  141. }
  142. // Same as above, but add observers to test their perf impact.
  143. class MessageLoopObserver : public base::TaskObserver {
  144. public:
  145. void WillProcessTask(const base::PendingTask& pending_task,
  146. bool was_blocked_or_low_priority) override {}
  147. void DidProcessTask(const base::PendingTask& pending_task) override {}
  148. };
  149. MessageLoopObserver message_loop_observer;
  150. class TaskObserverPerfTest : public TaskPerfTest {
  151. public:
  152. void Init() override {
  153. TaskPerfTest::Init();
  154. for (auto& i : threads_) {
  155. i->task_runner()->PostTask(
  156. FROM_HERE, BindOnce(
  157. [](MessageLoopObserver* observer) {
  158. CurrentThread::Get()->AddTaskObserver(observer);
  159. },
  160. Unretained(&message_loop_observer)));
  161. }
  162. }
  163. };
  164. TEST_F(TaskObserverPerfTest, TaskPingPong) {
  165. RunPingPongTest(
  166. std::string(kStoryBaseTaskWithObserver) + kStorySuffixOneThread, 1);
  167. RunPingPongTest(
  168. std::string(kStoryBaseTaskWithObserver) + kStorySuffixFourThreads, 4);
  169. }
  170. // Class to test our WaitableEvent performance by signaling back and fort.
  171. // WaitableEvent is templated so we can also compare with other versions.
  172. template <typename WaitableEventType>
  173. class EventPerfTest : public ThreadPerfTest {
  174. public:
  175. void Init() override {
  176. for (size_t i = 0; i < threads_.size(); i++) {
  177. events_.push_back(std::make_unique<WaitableEventType>(
  178. WaitableEvent::ResetPolicy::AUTOMATIC,
  179. WaitableEvent::InitialState::NOT_SIGNALED));
  180. }
  181. }
  182. void Reset() override { events_.clear(); }
  183. void WaitAndSignalOnThread(size_t event) {
  184. size_t next_event = (event + 1) % events_.size();
  185. int my_hops = 0;
  186. do {
  187. events_[event]->Wait();
  188. my_hops = --remaining_hops_; // We own 'hops' between Wait and Signal.
  189. events_[next_event]->Signal();
  190. } while (my_hops > 0);
  191. // Once we are done, all threads will signal as hops passes zero.
  192. // We only signal completion once, on the thread that reaches zero.
  193. if (!my_hops)
  194. FinishMeasurement();
  195. }
  196. void PingPong(int hops) override {
  197. remaining_hops_ = hops;
  198. for (size_t i = 0; i < threads_.size(); i++) {
  199. threads_[i]->task_runner()->PostTask(
  200. FROM_HERE, base::BindOnce(&EventPerfTest::WaitAndSignalOnThread,
  201. base::Unretained(this), i));
  202. }
  203. // Kick off the Signal ping-ponging.
  204. events_.front()->Signal();
  205. }
  206. int remaining_hops_;
  207. std::vector<std::unique_ptr<WaitableEventType>> events_;
  208. };
  209. // Similar to the task posting test, this just tests similar functionality
  210. // using WaitableEvents. We only test four threads (worst-case), but we
  211. // might want to craft a way to test the best-case (where the thread doesn't
  212. // end up blocking because the event is already signalled).
  213. typedef EventPerfTest<base::WaitableEvent> WaitableEventThreadPerfTest;
  214. TEST_F(WaitableEventThreadPerfTest, EventPingPong) {
  215. RunPingPongTest(
  216. std::string(kStoryBaseWaitableEvent) + kStorySuffixFourThreads, 4);
  217. }
  218. // Build a minimal event using ConditionVariable.
  219. class ConditionVariableEvent {
  220. public:
  221. ConditionVariableEvent(WaitableEvent::ResetPolicy reset_policy,
  222. WaitableEvent::InitialState initial_state)
  223. : cond_(&lock_), signaled_(false) {
  224. DCHECK_EQ(WaitableEvent::ResetPolicy::AUTOMATIC, reset_policy);
  225. DCHECK_EQ(WaitableEvent::InitialState::NOT_SIGNALED, initial_state);
  226. }
  227. void Signal() {
  228. {
  229. base::AutoLock scoped_lock(lock_);
  230. signaled_ = true;
  231. }
  232. cond_.Signal();
  233. }
  234. void Wait() {
  235. base::AutoLock scoped_lock(lock_);
  236. while (!signaled_)
  237. cond_.Wait();
  238. signaled_ = false;
  239. }
  240. private:
  241. base::Lock lock_;
  242. base::ConditionVariable cond_;
  243. bool signaled_;
  244. };
  245. // This is meant to test the absolute minimal context switching time
  246. // using our own base synchronization code.
  247. typedef EventPerfTest<ConditionVariableEvent> ConditionVariablePerfTest;
  248. TEST_F(ConditionVariablePerfTest, EventPingPong) {
  249. RunPingPongTest(std::string(kStoryBaseCondVar) + kStorySuffixFourThreads, 4);
  250. }
  251. #if BUILDFLAG(IS_POSIX)
  252. // Absolutely 100% minimal posix waitable event. If there is a better/faster
  253. // way to force a context switch, we should use that instead.
  254. class PthreadEvent {
  255. public:
  256. PthreadEvent(WaitableEvent::ResetPolicy reset_policy,
  257. WaitableEvent::InitialState initial_state) {
  258. DCHECK_EQ(WaitableEvent::ResetPolicy::AUTOMATIC, reset_policy);
  259. DCHECK_EQ(WaitableEvent::InitialState::NOT_SIGNALED, initial_state);
  260. pthread_mutex_init(&mutex_, nullptr);
  261. pthread_cond_init(&cond_, nullptr);
  262. signaled_ = false;
  263. }
  264. ~PthreadEvent() {
  265. pthread_cond_destroy(&cond_);
  266. pthread_mutex_destroy(&mutex_);
  267. }
  268. void Signal() {
  269. pthread_mutex_lock(&mutex_);
  270. signaled_ = true;
  271. pthread_mutex_unlock(&mutex_);
  272. pthread_cond_signal(&cond_);
  273. }
  274. void Wait() {
  275. pthread_mutex_lock(&mutex_);
  276. while (!signaled_)
  277. pthread_cond_wait(&cond_, &mutex_);
  278. signaled_ = false;
  279. pthread_mutex_unlock(&mutex_);
  280. }
  281. private:
  282. bool signaled_;
  283. pthread_mutex_t mutex_;
  284. pthread_cond_t cond_;
  285. };
  286. // This is meant to test the absolute minimal context switching time.
  287. // If there is any faster way to do this we should substitute it in.
  288. typedef EventPerfTest<PthreadEvent> PthreadEventPerfTest;
  289. TEST_F(PthreadEventPerfTest, EventPingPong) {
  290. RunPingPongTest(
  291. std::string(kStoryBasePthreadCondVar) + kStorySuffixFourThreads, 4);
  292. }
  293. #endif
  294. } // namespace
  295. } // namespace base