Skip to content

Commit 11f22f1

Browse files
authored
[tzdb] Replace shared_mutex with mutex. (#87929)
The overhead of taking a std::mutex is much lower than taking a reader lock on a shared mutex, even under heavy contention. The benefit of shared_mutex only occurs as the amount of time spent in the critical sections grows large enough. In our case all we do is read a pointer and return the lock. As a result, using a shared lock can be ~50%-100% slower Here are the results for the provided benchmark on my machine: ``` 2024-04-07T12:48:51-04:00 Running ./libcxx/benchmarks/shared_mutex_vs_mutex.libcxx.out Run on (12 X 400 MHz CPU s) CPU Caches: L1 Data 32 KiB (x6) L1 Instruction 32 KiB (x6) L2 Unified 1024 KiB (x6) L3 Unified 32768 KiB (x1) Load Average: 2.70, 2.70, 1.63 --------------------------------------------------------------------- Benchmark Time CPU Iterations --------------------------------------------------------------------- BM_shared_mutex/threads:1 13.9 ns 13.9 ns 50533700 BM_shared_mutex/threads:2 34.5 ns 68.9 ns 9957784 BM_shared_mutex/threads:4 38.4 ns 137 ns 4987772 BM_shared_mutex/threads:8 51.1 ns 358 ns 1974160 BM_shared_mutex/threads:32 57.1 ns 682 ns 1043648 BM_mutex/threads:1 5.54 ns 5.53 ns 125867422 BM_mutex/threads:2 15.5 ns 30.9 ns 21830116 BM_mutex/threads:4 15.4 ns 57.2 ns 12136920 BM_mutex/threads:8 19.3 ns 140 ns 4997080 BM_mutex/threads:32 20.8 ns 252 ns 2859808 ```
1 parent 6d66db3 commit 11f22f1

File tree

3 files changed

+49
-4
lines changed

3 files changed

+49
-4
lines changed

libcxx/benchmarks/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ set(BENCHMARK_TESTS
221221
map.bench.cpp
222222
monotonic_buffer.bench.cpp
223223
ordered_set.bench.cpp
224+
shared_mutex_vs_mutex.bench.cpp
224225
stop_token.bench.cpp
225226
std_format_spec_string_unicode.bench.cpp
226227
string.bench.cpp
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===----------------------------------------------------------------------===//
2+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
3+
// See https://llvm.org/LICENSE.txt for license information.
4+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
//
6+
//===----------------------------------------------------------------------===//
7+
8+
// This benchmark compares the performance of std::mutex and std::shared_mutex in contended scenarios.
9+
// it's meant to establish a baseline overhead for std::shared_mutex and std::mutex, and to help inform decisions about
10+
// which mutex to use when selecting a mutex type for a given use case.
11+
12+
#include <atomic>
13+
#include <mutex>
14+
#include <numeric>
15+
#include <shared_mutex>
16+
#include <thread>
17+
18+
#include "benchmark/benchmark.h"
19+
20+
int global_value = 42;
21+
std::mutex m;
22+
std::shared_mutex sm;
23+
24+
static void BM_shared_mutex(benchmark::State& state) {
25+
for (auto _ : state) {
26+
std::shared_lock<std::shared_mutex> lock(sm);
27+
benchmark::DoNotOptimize(global_value);
28+
}
29+
}
30+
31+
static void BM_mutex(benchmark::State& state) {
32+
for (auto _ : state) {
33+
std::lock_guard<std::mutex> lock(m);
34+
benchmark::DoNotOptimize(global_value);
35+
}
36+
}
37+
38+
BENCHMARK(BM_shared_mutex)->Threads(1)->Threads(2)->Threads(4)->Threads(8)->Threads(32);
39+
BENCHMARK(BM_mutex)->Threads(1)->Threads(2)->Threads(4)->Threads(8)->Threads(32);
40+
41+
BENCHMARK_MAIN();

libcxx/src/include/tzdb/tzdb_list_private.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
#include <forward_list>
1616

1717
// When threads are not available the locking is not required.
18+
// When threads are available, we use std::mutex over std::shared_mutex
19+
// due to the increased overhead of std::shared_mutex.
20+
// See shared_mutex_vs_mutex.bench.cpp
1821
#ifndef _LIBCPP_HAS_NO_THREADS
19-
# include <shared_mutex>
22+
# include <mutex>
2023
#endif
2124

2225
#include "types_private.h"
@@ -56,7 +59,7 @@ class tzdb_list::__impl {
5659

5760
const tzdb& __front() const noexcept {
5861
#ifndef _LIBCPP_HAS_NO_THREADS
59-
shared_lock __lock{__mutex_};
62+
unique_lock __lock{__mutex_};
6063
#endif
6164
return __tzdb_.front();
6265
}
@@ -72,7 +75,7 @@ class tzdb_list::__impl {
7275

7376
const_iterator __begin() const noexcept {
7477
#ifndef _LIBCPP_HAS_NO_THREADS
75-
shared_lock __lock{__mutex_};
78+
unique_lock __lock{__mutex_};
7679
#endif
7780
return __tzdb_.begin();
7881
}
@@ -87,7 +90,7 @@ class tzdb_list::__impl {
8790
void __load_no_lock() { chrono::__init_tzdb(__tzdb_.emplace_front(), __rules_.emplace_front()); }
8891

8992
#ifndef _LIBCPP_HAS_NO_THREADS
90-
mutable shared_mutex __mutex_;
93+
mutable mutex __mutex_;
9194
#endif
9295
forward_list<tzdb> __tzdb_;
9396

0 commit comments

Comments
 (0)