| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| #include <memory> |
| #include <vector> |
| |
| #include "base/barrier_closure.h" |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/callback.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/threading/simple_thread.h" |
| #include "base/threading/thread_local_storage.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "testing/perf/perf_test.h" |
| |
| #if defined(OS_WIN) |
| #include <windows.h> |
| #include "base/win/windows_types.h" |
| #elif defined(OS_POSIX) || defined(OS_FUCHSIA) |
| #include <pthread.h> |
| #endif |
| |
| namespace base { |
| namespace internal { |
| |
| namespace { |
| |
| // A thread that waits for the caller to signal an event before proceeding to |
| // call action.Run(). |
| class TLSThread : public SimpleThread { |
| public: |
| // Creates a PostingThread that waits on |start_event| before calling |
| // action.Run(). |
| TLSThread(WaitableEvent* start_event, |
| base::OnceClosure action, |
| base::OnceClosure completion) |
| : SimpleThread("TLSThread"), |
| start_event_(start_event), |
| action_(std::move(action)), |
| completion_(std::move(completion)) { |
| Start(); |
| } |
| |
| void Run() override { |
| start_event_->Wait(); |
| std::move(action_).Run(); |
| std::move(completion_).Run(); |
| } |
| |
| private: |
| WaitableEvent* const start_event_; |
| base::OnceClosure action_; |
| base::OnceClosure completion_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TLSThread); |
| }; |
| |
| class ThreadLocalStoragePerfTest : public testing::Test { |
| public: |
| protected: |
| ThreadLocalStoragePerfTest() = default; |
| ~ThreadLocalStoragePerfTest() override = default; |
| |
| template <class Read, class Write> |
| void Benchmark(const std::string& trace, |
| Read read, |
| Write write, |
| size_t num_operation, |
| size_t num_threads) { |
| write(2); |
| |
| BenchmarkImpl("TLS read throughput", trace, |
| base::BindLambdaForTesting([&]() { |
| volatile intptr_t total = 0; |
| for (size_t i = 0; i < num_operation; ++i) |
| total += read(); |
| }), |
| num_operation, num_threads); |
| |
| BenchmarkImpl("TLS write throughput", trace, |
| base::BindLambdaForTesting([&]() { |
| for (size_t i = 0; i < num_operation; ++i) |
| write(i); |
| }), |
| num_operation, num_threads); |
| |
| BenchmarkImpl("TLS read-write throughput", trace, |
| base::BindLambdaForTesting([&]() { |
| for (size_t i = 0; i < num_operation; ++i) |
| write(read() + 1); |
| }), |
| num_operation, num_threads); |
| } |
| |
| void BenchmarkImpl(const std::string& measurment, |
| const std::string& trace, |
| base::RepeatingClosure action, |
| size_t num_operation, |
| size_t num_threads) { |
| WaitableEvent start_thread; |
| WaitableEvent complete_thread; |
| |
| base::RepeatingClosure done = BarrierClosure( |
| num_threads, |
| base::BindLambdaForTesting([&]() { complete_thread.Signal(); })); |
| |
| std::vector<std::unique_ptr<TLSThread>> threads; |
| for (size_t i = 0; i < num_threads; ++i) { |
| threads.emplace_back( |
| std::make_unique<TLSThread>(&start_thread, action, done)); |
| } |
| |
| TimeTicks operation_start = TimeTicks::Now(); |
| start_thread.Signal(); |
| complete_thread.Wait(); |
| TimeDelta operation_duration = TimeTicks::Now() - operation_start; |
| |
| for (auto& thread : threads) |
| thread->Join(); |
| |
| perf_test::PrintResult( |
| measurment, "", trace, |
| num_operation / |
| static_cast<double>(operation_duration.InMilliseconds()), |
| "operations/ms", true); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ThreadLocalStoragePerfTest); |
| }; |
| |
| } // namespace |
| |
| TEST_F(ThreadLocalStoragePerfTest, ThreadLocalStorage) { |
| ThreadLocalStorage::Slot tls; |
| auto read = [&]() { return reinterpret_cast<intptr_t>(tls.Get()); }; |
| auto write = [&](intptr_t value) { tls.Set(reinterpret_cast<void*>(value)); }; |
| |
| Benchmark("ThreadLocalStorage", read, write, 10000000, 1); |
| Benchmark("ThreadLocalStorage 4 threads", read, write, 10000000, 4); |
| } |
| |
| #if defined(OS_WIN) |
| |
| void WINAPI destroy(void*) {} |
| |
| TEST_F(ThreadLocalStoragePerfTest, PlatformFls) { |
| DWORD key = FlsAlloc(destroy); |
| ASSERT_NE(PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES, key); |
| |
| auto read = [&]() { return reinterpret_cast<intptr_t>(FlsGetValue(key)); }; |
| auto write = [&](intptr_t value) { |
| FlsSetValue(key, reinterpret_cast<void*>(value)); |
| }; |
| |
| Benchmark("PlatformFls", read, write, 10000000, 1); |
| Benchmark("PlatformFls 4 threads", read, write, 10000000, 4); |
| } |
| |
| TEST_F(ThreadLocalStoragePerfTest, PlatformTls) { |
| DWORD key = TlsAlloc(); |
| ASSERT_NE(PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES, key); |
| |
| auto read = [&]() { return reinterpret_cast<intptr_t>(TlsGetValue(key)); }; |
| auto write = [&](intptr_t value) { |
| TlsSetValue(key, reinterpret_cast<void*>(value)); |
| }; |
| |
| Benchmark("PlatformTls", read, write, 10000000, 1); |
| Benchmark("PlatformTls 4 threads", read, write, 10000000, 4); |
| } |
| |
| #elif defined(OS_POSIX) || defined(OS_FUCHSIA) |
| |
| TEST_F(ThreadLocalStoragePerfTest, PlatformTls) { |
| pthread_key_t key; |
| ASSERT_FALSE(pthread_key_create(&key, [](void*) {})); |
| ASSERT_NE(PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES, key); |
| |
| auto read = [&]() { |
| return reinterpret_cast<intptr_t>(pthread_getspecific(key)); |
| }; |
| auto write = [&](intptr_t value) { |
| pthread_setspecific(key, reinterpret_cast<void*>(value)); |
| }; |
| |
| Benchmark("PlatformTls", read, write, 10000000, 1); |
| Benchmark("PlatformTls 4 threads", read, write, 10000000, 4); |
| } |
| |
| #endif |
| |
| } // namespace internal |
| } // namespace base |