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