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