counter_perftest.cc 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. // Copyright 2021 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 <atomic>
  5. #include <string>
  6. #include "base/barrier_closure.h"
  7. #include "base/callback.h"
  8. #include "base/memory/raw_ptr.h"
  9. #include "base/synchronization/lock.h"
  10. #include "base/synchronization/waitable_event.h"
  11. #include "base/threading/simple_thread.h"
  12. #include "testing/gtest/include/gtest/gtest.h"
  13. #include "testing/perf/perf_result_reporter.h"
  14. // This file contains tests to measure the cost of incrementing:
  15. // - A non-atomic variable, no lock.
  16. // - A non-atomic variable, with lock.
  17. // - An atomic variable, no memory barriers.
  18. // - An atomic variable, acquire-release barriers.
  19. // The goal is to provide data to guide counter implementation choices.
  20. namespace base {
  21. namespace {
  22. constexpr char kMetricPrefixCounter[] = "Counter.";
  23. constexpr char kMetricOperationThroughput[] = "operation_throughput";
  24. constexpr uint64_t kNumIterations = 100000000;
  25. perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
  26. perf_test::PerfResultReporter reporter(kMetricPrefixCounter, story_name);
  27. reporter.RegisterImportantMetric(kMetricOperationThroughput, "operations/ms");
  28. return reporter;
  29. }
  30. class Uint64_NoLock {
  31. public:
  32. Uint64_NoLock() = default;
  33. void Increment() { counter_ = counter_ + 1; }
  34. uint64_t value() const { return counter_; }
  35. private:
  36. // Volatile to prevent the compiler from over-optimizing the increment.
  37. volatile uint64_t counter_ = 0;
  38. };
  39. class Uint64_Lock {
  40. public:
  41. Uint64_Lock() = default;
  42. void Increment() {
  43. AutoLock auto_lock(lock_);
  44. ++counter_;
  45. }
  46. uint64_t value() const {
  47. AutoLock auto_lock(lock_);
  48. return counter_;
  49. }
  50. private:
  51. mutable Lock lock_;
  52. uint64_t counter_ GUARDED_BY(lock_) = 0;
  53. };
  54. class AtomicUint64_NoBarrier {
  55. public:
  56. AtomicUint64_NoBarrier() = default;
  57. void Increment() { counter_.fetch_add(1, std::memory_order_relaxed); }
  58. uint64_t value() const { return counter_; }
  59. private:
  60. std::atomic<uint64_t> counter_{0};
  61. };
  62. class AtomicUint64_Barrier {
  63. public:
  64. AtomicUint64_Barrier() = default;
  65. void Increment() { counter_.fetch_add(1, std::memory_order_acq_rel); }
  66. uint64_t value() const { return counter_; }
  67. private:
  68. std::atomic<uint64_t> counter_{0};
  69. };
  70. template <typename CounterType>
  71. class IncrementThread : public SimpleThread {
  72. public:
  73. // Upon entering its main function, the thread waits for |start_event| to be
  74. // signaled. Then, it increments |counter| |kNumIterations| times.
  75. // Finally, it invokes |done_closure|.
  76. explicit IncrementThread(WaitableEvent* start_event,
  77. CounterType* counter,
  78. OnceClosure done_closure)
  79. : SimpleThread("IncrementThread"),
  80. start_event_(start_event),
  81. counter_(counter),
  82. done_closure_(std::move(done_closure)) {}
  83. // SimpleThread:
  84. void Run() override {
  85. start_event_->Wait();
  86. for (uint64_t i = 0; i < kNumIterations; ++i)
  87. counter_->Increment();
  88. std::move(done_closure_).Run();
  89. }
  90. private:
  91. const raw_ptr<WaitableEvent> start_event_;
  92. const raw_ptr<CounterType> counter_;
  93. OnceClosure done_closure_;
  94. };
  95. template <typename CounterType>
  96. void RunIncrementPerfTest(const std::string& story_name, int num_threads) {
  97. WaitableEvent start_event;
  98. WaitableEvent end_event;
  99. CounterType counter;
  100. RepeatingClosure done_closure = BarrierClosure(
  101. num_threads, BindOnce(&WaitableEvent::Signal, Unretained(&end_event)));
  102. std::vector<std::unique_ptr<IncrementThread<CounterType>>> threads;
  103. for (int i = 0; i < num_threads; ++i) {
  104. threads.push_back(std::make_unique<IncrementThread<CounterType>>(
  105. &start_event, &counter, done_closure));
  106. threads.back()->Start();
  107. }
  108. TimeTicks start_time = TimeTicks::Now();
  109. start_event.Signal();
  110. end_event.Wait();
  111. TimeTicks end_time = TimeTicks::Now();
  112. EXPECT_EQ(num_threads * kNumIterations, counter.value());
  113. auto reporter = SetUpReporter(story_name);
  114. reporter.AddResult(
  115. kMetricOperationThroughput,
  116. kNumIterations / (end_time - start_time).InMillisecondsF());
  117. for (auto& thread : threads)
  118. thread->Join();
  119. }
  120. } // namespace
  121. TEST(CounterPerfTest, Uint64_NoLock_1Thread) {
  122. RunIncrementPerfTest<Uint64_NoLock>("Uint64_NoLock_1Thread", 1);
  123. }
  124. // No Uint64_NoLock_4Threads test because it would cause data races.
  125. TEST(CounterPerfTest, Uint64_Lock_1Thread) {
  126. RunIncrementPerfTest<Uint64_Lock>("Uint64_Lock_1Thread", 1);
  127. }
  128. TEST(CounterPerfTest, Uint64_Lock_4Threads) {
  129. RunIncrementPerfTest<Uint64_Lock>("Uint64_Lock_4Threads", 4);
  130. }
  131. TEST(CounterPerfTest, AtomicUint64_NoBarrier_1Thread) {
  132. RunIncrementPerfTest<AtomicUint64_NoBarrier>("AtomicUint64_NoBarrier_1Thread",
  133. 1);
  134. }
  135. TEST(CounterPerfTest, AtomicUint64_NoBarrier_4Threads) {
  136. RunIncrementPerfTest<AtomicUint64_NoBarrier>(
  137. "AtomicUint64_NoBarrier_4Threads", 4);
  138. }
  139. TEST(CounterPerfTest, AtomicUint64_Barrier_1Thread) {
  140. RunIncrementPerfTest<AtomicUint64_Barrier>("AtomicUint64_Barrier_1Thread", 1);
  141. }
  142. TEST(CounterPerfTest, AtomicUint64_Barrier_4Threads) {
  143. RunIncrementPerfTest<AtomicUint64_Barrier>("AtomicUint64_Barrier_4Threads",
  144. 4);
  145. }
  146. } // namespace base