1 // Copyright The OpenTelemetry Authors
2 // SPDX-License-Identifier: Apache-2.0
4 #include "opentelemetry/common/spin_lock_mutex.h"
6 #include <benchmark/benchmark.h>
11 using opentelemetry::common::SpinLockMutex
;
13 constexpr int TightLoopLocks
= 10000;
15 // Runs a thrash-test where we spin up N threads, each of which will
16 // attempt to lock-mutate-unlock a total of `TightLoopLocks` times.
18 // lock: A lambda denoting how to lock. Accepts a reference to `SpinLockType`.
19 // unlock: A lambda denoting how to unlock. Accepts a reference to `SpinLockType`.
20 template <typename SpinLockType
, typename LockF
, typename UnlockF
>
21 inline void SpinThrash(benchmark::State
&s
, SpinLockType
&spinlock
, LockF lock
, UnlockF unlock
)
23 auto num_threads
= s
.range(0);
24 // Value we will increment, fighting over a spinlock.
25 // The contention is meant to be brief, as close to our expected
26 // use cases of "updating pointers" or "pushing an event onto a buffer".
27 std::int64_t value
= 0;
29 std::vector
<std::thread
> threads
;
30 threads
.reserve(num_threads
);
35 for (auto i
= 0; i
< num_threads
; i
++)
37 threads
.emplace_back([&] {
38 // Increment value once each time the lock is acquired. Spin a few times
39 // to ensure maximum thread contention.
40 for (int i
= 0; i
< TightLoopLocks
; i
++)
49 for (auto &thread
: threads
)
55 // Benchmark of full spin-lock implementation.
56 static void BM_SpinLockThrashing(benchmark::State
&s
)
58 SpinLockMutex spinlock
;
60 s
, spinlock
, [](SpinLockMutex
&m
) { m
.lock(); }, [](SpinLockMutex
&m
) { m
.unlock(); });
63 // Naive `while(try_lock()) {}` implementation of lock.
64 static void BM_NaiveSpinLockThrashing(benchmark::State
&s
)
66 SpinLockMutex spinlock
;
69 [](SpinLockMutex
&m
) {
72 // Left this comment to keep the same format on old and new versions of clang-format
75 [](SpinLockMutex
&m
) { m
.unlock(); });
78 // Simple `while(try_lock()) { yield-processor }`
79 static void BM_ProcYieldSpinLockThrashing(benchmark::State
&s
)
81 SpinLockMutex spinlock
;
82 SpinThrash
<SpinLockMutex
>(
84 [](SpinLockMutex
&m
) {
89 #elif defined(__i386__) || defined(__x86_64__)
90 # if defined(__clang__)
93 __builtin_ia32_pause();
95 #elif defined(__arm__)
96 __asm__
volatile("yield" ::: "memory");
100 [](SpinLockMutex
&m
) { m
.unlock(); });
103 // SpinLock thrashing with thread::yield().
104 static void BM_ThreadYieldSpinLockThrashing(benchmark::State
&s
)
106 std::atomic_flag mutex
= ATOMIC_FLAG_INIT
;
107 SpinThrash
<std::atomic_flag
>(
109 [](std::atomic_flag
&l
) {
110 uint32_t try_count
= 0;
111 while (l
.test_and_set(std::memory_order_acq_rel
))
116 std::this_thread::yield();
119 std::this_thread::yield();
121 [](std::atomic_flag
&l
) { l
.clear(std::memory_order_release
); });
124 // Run the benchmarks at 2x thread/core and measure the amount of time to thrash around.
125 BENCHMARK(BM_SpinLockThrashing
)
127 ->Range(1, std::thread::hardware_concurrency())
128 ->MeasureProcessCPUTime()
130 ->Unit(benchmark::kMillisecond
);
131 BENCHMARK(BM_ProcYieldSpinLockThrashing
)
133 ->Range(1, std::thread::hardware_concurrency())
134 ->MeasureProcessCPUTime()
136 ->Unit(benchmark::kMillisecond
);
137 BENCHMARK(BM_NaiveSpinLockThrashing
)
139 ->Range(1, std::thread::hardware_concurrency())
140 ->MeasureProcessCPUTime()
142 ->Unit(benchmark::kMillisecond
);
143 BENCHMARK(BM_ThreadYieldSpinLockThrashing
)
145 ->Range(1, std::thread::hardware_concurrency())
146 ->MeasureProcessCPUTime()
148 ->Unit(benchmark::kMillisecond
);