1 // Copyright 2019 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 <stddef.h>
6 #include <memory>
7 #include <vector>
8
9 #include "base/barrier_closure.h"
10 #include "base/functional/bind.h"
11 #include "base/functional/callback.h"
12 #include "base/functional/callback_helpers.h"
13 #include "base/memory/raw_ptr.h"
14 #include "base/synchronization/waitable_event.h"
15 #include "base/test/bind.h"
16 #include "base/threading/simple_thread.h"
17 #include "base/threading/thread_local_storage.h"
18 #include "base/time/time.h"
19 #include "build/build_config.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21 #include "testing/perf/perf_result_reporter.h"
22
23 #if BUILDFLAG(IS_WIN)
24 #include <windows.h>
25
26 #include "base/win/windows_types.h"
27 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
28 #include <pthread.h>
29 #endif
30
31 namespace base {
32 namespace internal {
33
34 namespace {
35
36 constexpr size_t kCount = 5000000;
37
38 constexpr char kMetricPrefixThreadLocalStorage[] = "ThreadLocalStorage.";
39 constexpr char kMetricBaseRead[] = "read";
40 constexpr char kMetricBaseWrite[] = "write";
41 constexpr char kMetricBaseReadWrite[] = "read_write";
42 constexpr char kMetricSuffixThroughput[] = "_throughput";
43 constexpr char kMetricSuffixOperationTime[] = "_operation_time";
44 constexpr char kStoryBaseTLS[] = "thread_local_storage";
45 #if BUILDFLAG(IS_WIN)
46 constexpr char kStoryBasePlatformFLS[] = "platform_fiber_local_storage";
47 #endif // BUILDFLAG(IS_WIN)
48 constexpr char kStoryBasePlatformTLS[] = "platform_thread_local_storage";
49 constexpr char kStoryBaseCPPTLS[] = "c++_platform_thread_local_storage";
50 constexpr char kStorySuffixFourThreads[] = "_4_threads";
51
SetUpReporter(const std::string & story_name)52 perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
53 perf_test::PerfResultReporter reporter(kMetricPrefixThreadLocalStorage,
54 story_name);
55 reporter.RegisterImportantMetric(
56 std::string(kMetricBaseRead) + kMetricSuffixThroughput, "runs/s");
57 reporter.RegisterImportantMetric(
58 std::string(kMetricBaseRead) + kMetricSuffixOperationTime, "ns");
59 reporter.RegisterImportantMetric(
60 std::string(kMetricBaseWrite) + kMetricSuffixThroughput, "runs/s");
61 reporter.RegisterImportantMetric(
62 std::string(kMetricBaseWrite) + kMetricSuffixOperationTime, "ns");
63 reporter.RegisterImportantMetric(
64 std::string(kMetricBaseReadWrite) + kMetricSuffixThroughput, "runs/s");
65 reporter.RegisterImportantMetric(
66 std::string(kMetricBaseReadWrite) + kMetricSuffixOperationTime, "ns");
67 return reporter;
68 }
69
70 // A thread that waits for the caller to signal an event before proceeding to
71 // call action.Run().
72 class TLSThread : public SimpleThread {
73 public:
74 // Creates a PostingThread that waits on |start_event| before calling
75 // action.Run().
TLSThread(WaitableEvent * start_event,base::OnceClosure action,base::OnceClosure completion)76 TLSThread(WaitableEvent* start_event,
77 base::OnceClosure action,
78 base::OnceClosure completion)
79 : SimpleThread("TLSThread"),
80 start_event_(start_event),
81 action_(std::move(action)),
82 completion_(std::move(completion)) {
83 Start();
84 }
85
86 TLSThread(const TLSThread&) = delete;
87 TLSThread& operator=(const TLSThread&) = delete;
88
Run()89 void Run() override {
90 start_event_->Wait();
91 std::move(action_).Run();
92 std::move(completion_).Run();
93 }
94
95 private:
96 const raw_ptr<WaitableEvent> start_event_;
97 base::OnceClosure action_;
98 base::OnceClosure completion_;
99 };
100
101 class ThreadLocalStoragePerfTest : public testing::Test {
102 public:
103 ThreadLocalStoragePerfTest(const ThreadLocalStoragePerfTest&) = delete;
104 ThreadLocalStoragePerfTest& operator=(const ThreadLocalStoragePerfTest&) =
105 delete;
106
107 protected:
108 ThreadLocalStoragePerfTest() = default;
109 ~ThreadLocalStoragePerfTest() override = default;
110
111 template <class Read, class Write>
Benchmark(const std::string & story_name,Read read,Write write,size_t num_operation,size_t num_threads)112 void Benchmark(const std::string& story_name,
113 Read read,
114 Write write,
115 size_t num_operation,
116 size_t num_threads) {
117 write(2);
118
119 BenchmarkImpl(kMetricBaseRead, story_name,
120 base::BindLambdaForTesting([&]() {
121 volatile intptr_t total = 0;
122 for (size_t i = 0; i < num_operation; ++i)
123 total = total + read();
124 }),
125 num_operation, num_threads);
126
127 BenchmarkImpl(kMetricBaseWrite, story_name,
128 base::BindLambdaForTesting([&]() {
129 for (size_t i = 0; i < num_operation; ++i)
130 write(i);
131 }),
132 num_operation, num_threads);
133
134 BenchmarkImpl(kMetricBaseReadWrite, story_name,
135 base::BindLambdaForTesting([&]() {
136 for (size_t i = 0; i < num_operation; ++i)
137 write(read() + 1);
138 }),
139 num_operation, num_threads);
140 }
141
BenchmarkImpl(const std::string & metric_base,const std::string & story_name,base::RepeatingClosure action,size_t num_operation,size_t num_threads)142 void BenchmarkImpl(const std::string& metric_base,
143 const std::string& story_name,
144 base::RepeatingClosure action,
145 size_t num_operation,
146 size_t num_threads) {
147 WaitableEvent start_thread;
148 WaitableEvent complete_thread;
149
150 base::RepeatingClosure done = BarrierClosure(
151 num_threads,
152 base::BindLambdaForTesting([&]() { complete_thread.Signal(); }));
153
154 std::vector<std::unique_ptr<TLSThread>> threads;
155 for (size_t i = 0; i < num_threads; ++i) {
156 threads.emplace_back(
157 std::make_unique<TLSThread>(&start_thread, action, done));
158 }
159
160 TimeTicks operation_start = TimeTicks::Now();
161 start_thread.Signal();
162 complete_thread.Wait();
163 TimeDelta operation_duration = TimeTicks::Now() - operation_start;
164
165 for (auto& thread : threads)
166 thread->Join();
167
168 auto reporter = SetUpReporter(story_name);
169 reporter.AddResult(metric_base + kMetricSuffixThroughput,
170 num_operation / operation_duration.InSecondsF());
171 size_t nanos_per_operation =
172 operation_duration.InNanoseconds() / num_operation;
173 reporter.AddResult(metric_base + kMetricSuffixOperationTime,
174 nanos_per_operation);
175 }
176 };
177
178 } // namespace
179
TEST_F(ThreadLocalStoragePerfTest,ThreadLocalStorage)180 TEST_F(ThreadLocalStoragePerfTest, ThreadLocalStorage) {
181 ThreadLocalStorage::Slot tls;
182 auto read = [&]() { return reinterpret_cast<intptr_t>(tls.Get()); };
183 auto write = [&](intptr_t value) { tls.Set(reinterpret_cast<void*>(value)); };
184
185 Benchmark(kStoryBaseTLS, read, write, 10000000, 1);
186 Benchmark(std::string(kStoryBaseTLS) + kStorySuffixFourThreads, read, write,
187 kCount, 4);
188 }
189
190 #if BUILDFLAG(IS_WIN)
191
destroy(void *)192 void WINAPI destroy(void*) {}
193
TEST_F(ThreadLocalStoragePerfTest,PlatformFls)194 TEST_F(ThreadLocalStoragePerfTest, PlatformFls) {
195 DWORD key = FlsAlloc(destroy);
196 ASSERT_NE(PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES, key);
197
198 auto read = [&]() { return reinterpret_cast<intptr_t>(FlsGetValue(key)); };
199 auto write = [&](intptr_t value) {
200 FlsSetValue(key, reinterpret_cast<void*>(value));
201 };
202
203 Benchmark(kStoryBasePlatformFLS, read, write, 10000000, 1);
204 Benchmark(std::string(kStoryBasePlatformFLS) + kStorySuffixFourThreads, read,
205 write, kCount, 4);
206 }
207
TEST_F(ThreadLocalStoragePerfTest,PlatformTls)208 TEST_F(ThreadLocalStoragePerfTest, PlatformTls) {
209 DWORD key = TlsAlloc();
210 ASSERT_NE(PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES, key);
211
212 auto read = [&]() { return reinterpret_cast<intptr_t>(TlsGetValue(key)); };
213 auto write = [&](intptr_t value) {
214 TlsSetValue(key, reinterpret_cast<void*>(value));
215 };
216
217 Benchmark(kStoryBasePlatformTLS, read, write, 10000000, 1);
218 Benchmark(std::string(kStoryBasePlatformTLS) + kStorySuffixFourThreads, read,
219 write, kCount, 4);
220 }
221
222 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
223
TEST_F(ThreadLocalStoragePerfTest,PlatformTls)224 TEST_F(ThreadLocalStoragePerfTest, PlatformTls) {
225 pthread_key_t key;
226 ASSERT_FALSE(pthread_key_create(&key, [](void*) {}));
227 ASSERT_NE(PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES, key);
228
229 auto read = [&]() {
230 return reinterpret_cast<intptr_t>(pthread_getspecific(key));
231 };
232 auto write = [&](intptr_t value) {
233 pthread_setspecific(key, reinterpret_cast<void*>(value));
234 };
235
236 Benchmark(kStoryBasePlatformTLS, read, write, 10000000, 1);
237 Benchmark(std::string(kStoryBasePlatformTLS) + kStorySuffixFourThreads, read,
238 write, kCount, 4);
239 }
240
241 #endif
242
TEST_F(ThreadLocalStoragePerfTest,Cpp11Tls)243 TEST_F(ThreadLocalStoragePerfTest, Cpp11Tls) {
244 thread_local intptr_t thread_local_variable;
245
246 auto read = [&]() { return thread_local_variable; };
247 auto write = [&](intptr_t value) {
248 reinterpret_cast<volatile intptr_t*>(&thread_local_variable)[0] = value;
249 };
250
251 Benchmark(kStoryBaseCPPTLS, read, write, 10000000, 1);
252 Benchmark(std::string(kStoryBaseCPPTLS) + kStorySuffixFourThreads, read,
253 write, kCount, 4);
254 }
255
256 } // namespace internal
257 } // namespace base
258