Skip to main content

rustc_data_structures/
sharded.rs

1use std::borrow::Borrow;
2use std::hash::{Hash, Hasher};
3use std::iter;
4
5use either::Either;
6use hashbrown::hash_table::{self, Entry, HashTable};
7
8use crate::fx::FxHasher;
9use crate::sync::{CacheAligned, Lock, LockGuard, Mode, is_dyn_thread_safe};
10
11// 32 shards is sufficient to reduce contention on an 8-core Ryzen 7 1700,
12// but this should be tested on higher core count CPUs. How the `Sharded` type gets used
13// may also affect the ideal number of shards.
14const SHARD_BITS: usize = 5;
15
16const SHARDS: usize = 1 << SHARD_BITS;
17
18/// An array of cache-line aligned inner locked structures with convenience methods.
19/// A single field is used when the compiler uses only one thread.
20pub enum Sharded<T> {
21    Single(Lock<T>),
22    Shards(Box<[CacheAligned<Lock<T>>; SHARDS]>),
23}
24
25impl<T: Default> Default for Sharded<T> {
26    #[inline]
27    fn default() -> Self {
28        Self::new(T::default)
29    }
30}
31
32impl<T> Sharded<T> {
33    #[inline]
34    pub fn new(mut value: impl FnMut() -> T) -> Self {
35        if is_dyn_thread_safe() {
36            return Sharded::Shards(Box::new(
37                [(); SHARDS].map(|()| CacheAligned(Lock::new(value()))),
38            ));
39        }
40
41        Sharded::Single(Lock::new(value()))
42    }
43
44    /// The shard is selected by hashing `val` with `FxHasher`.
45    #[inline]
46    pub fn get_shard_by_value<K: Hash + ?Sized>(&self, val: &K) -> &Lock<T> {
47        match self {
48            Self::Single(single) => single,
49            Self::Shards(..) => self.get_shard_by_hash(make_hash(val)),
50        }
51    }
52
53    #[inline]
54    pub fn get_shard_by_hash(&self, hash: u64) -> &Lock<T> {
55        self.get_shard_by_index(get_shard_hash(hash))
56    }
57
58    #[inline]
59    pub fn get_shard_by_index(&self, i: usize) -> &Lock<T> {
60        match self {
61            Self::Single(single) => single,
62            Self::Shards(shards) => {
63                // SAFETY: The index gets ANDed with the shard mask, ensuring it is always inbounds.
64                unsafe { &shards.get_unchecked(i & (SHARDS - 1)).0 }
65            }
66        }
67    }
68
69    /// The shard is selected by hashing `val` with `FxHasher`.
70    #[inline]
71    #[track_caller]
72    pub fn lock_shard_by_value<K: Hash + ?Sized>(&self, val: &K) -> LockGuard<'_, T> {
73        match self {
74            Self::Single(single) => {
75                // Synchronization is disabled so use the `lock_assume_no_sync` method optimized
76                // for that case.
77
78                // SAFETY: We know `is_dyn_thread_safe` was false when creating the lock thus
79                // `might_be_dyn_thread_safe` was also false.
80                unsafe { single.lock_assume(Mode::NoSync) }
81            }
82            Self::Shards(..) => self.lock_shard_by_hash(make_hash(val)),
83        }
84    }
85
86    #[inline]
87    #[track_caller]
88    pub fn lock_shard_by_hash(&self, hash: u64) -> LockGuard<'_, T> {
89        self.lock_shard_by_index(get_shard_hash(hash))
90    }
91
92    #[inline]
93    #[track_caller]
94    pub fn lock_shard_by_index(&self, i: usize) -> LockGuard<'_, T> {
95        match self {
96            Self::Single(single) => {
97                // Synchronization is disabled so use the `lock_assume_no_sync` method optimized
98                // for that case.
99
100                // SAFETY: We know `is_dyn_thread_safe` was false when creating the lock thus
101                // `might_be_dyn_thread_safe` was also false.
102                unsafe { single.lock_assume(Mode::NoSync) }
103            }
104            Self::Shards(shards) => {
105                // Synchronization is enabled so use the `lock_assume_sync` method optimized
106                // for that case.
107
108                // SAFETY (get_unchecked): The index gets ANDed with the shard mask, ensuring it is
109                // always inbounds.
110                // SAFETY (lock_assume_sync): We know `is_dyn_thread_safe` was true when creating
111                // the lock thus `might_be_dyn_thread_safe` was also true.
112                unsafe { shards.get_unchecked(i & (SHARDS - 1)).0.lock_assume(Mode::Sync) }
113            }
114        }
115    }
116
117    #[inline]
118    pub fn lock_shards(&self) -> impl Iterator<Item = LockGuard<'_, T>> {
119        match self {
120            Self::Single(single) => Either::Left(iter::once(single.lock())),
121            Self::Shards(shards) => Either::Right(shards.iter().map(|shard| shard.0.lock())),
122        }
123    }
124
125    #[inline]
126    pub fn try_lock_shards(&self) -> impl Iterator<Item = Option<LockGuard<'_, T>>> {
127        match self {
128            Self::Single(single) => Either::Left(iter::once(single.try_lock())),
129            Self::Shards(shards) => Either::Right(shards.iter().map(|shard| shard.0.try_lock())),
130        }
131    }
132}
133
134#[inline]
135pub fn shards() -> usize {
136    if is_dyn_thread_safe() {
137        return SHARDS;
138    }
139
140    1
141}
142
143pub type ShardedHashMap<K, V> = Sharded<hash_table::HashTable<(K, V)>>;
144
145impl<K: Eq, V> ShardedHashMap<K, V> {
146    pub fn with_capacity(cap: usize) -> Self {
147        let per_shard_cap = cap.div_ceil(shards());
148        Self::new(|| HashTable::with_capacity(per_shard_cap))
149    }
150    pub fn len(&self) -> usize {
151        self.lock_shards().map(|shard| shard.len()).sum()
152    }
153}
154
155impl<K: Eq + Hash, V> ShardedHashMap<K, V> {
156    #[inline]
157    pub fn get<Q>(&self, key: &Q) -> Option<V>
158    where
159        K: Borrow<Q>,
160        Q: Hash + Eq,
161        V: Clone,
162    {
163        let hash = make_hash(key);
164        let shard = self.lock_shard_by_hash(hash);
165        let (_, value) = shard.find(hash, |(k, _)| k.borrow() == key)?;
166        Some(value.clone())
167    }
168
169    #[inline]
170    pub fn get_or_insert_with(&self, key: K, default: impl FnOnce() -> V) -> V
171    where
172        V: Copy,
173    {
174        let hash = make_hash(&key);
175        let mut shard = self.lock_shard_by_hash(hash);
176
177        match table_entry(&mut shard, hash, &key) {
178            Entry::Occupied(e) => e.get().1,
179            Entry::Vacant(e) => {
180                let value = default();
181                e.insert((key, value));
182                value
183            }
184        }
185    }
186
187    /// Insert value into the [`ShardedHashMap`] with unique key.
188    ///
189    /// This function panics if debug_assertions are enabled and uniqueness is violated.
190    /// If uniqueness is violated but debug_assertions are disabled then lookups will arbitrarily
191    /// return one of the inserted elements.
192    #[inline]
193    pub fn insert_unique(&self, key: K, value: V) {
194        let hash = make_hash(&key);
195        let mut shard = self.lock_shard_by_hash(hash);
196
197        cfg_select! {
198            debug_assertions => match table_entry(&mut shard, hash, &key) {
199                Entry::Occupied(_) => {
200                    {
    ::core::panicking::panic_fmt(format_args!("tried to insert key that\'s already present"));
};panic!("tried to insert key that's already present");
201                }
202                Entry::Vacant(e) => {
203                    e.insert((key, value));
204                }
205            },
206            _ => {
207                shard.insert_unique(hash, (key, value), |(k, _)| make_hash(k));
208            }
209        }
210    }
211}
212
213impl<K: Eq + Hash + Copy> ShardedHashMap<K, ()> {
214    #[inline]
215    pub fn intern_ref<Q: ?Sized>(&self, value: &Q, make: impl FnOnce() -> K) -> K
216    where
217        K: Borrow<Q>,
218        Q: Hash + Eq,
219    {
220        let hash = make_hash(value);
221        let mut shard = self.lock_shard_by_hash(hash);
222
223        match table_entry(&mut shard, hash, value) {
224            Entry::Occupied(e) => e.get().0,
225            Entry::Vacant(e) => {
226                let v = make();
227                e.insert((v, ()));
228                v
229            }
230        }
231    }
232
233    #[inline]
234    pub fn intern<Q>(&self, value: Q, make: impl FnOnce(Q) -> K) -> K
235    where
236        K: Borrow<Q>,
237        Q: Hash + Eq,
238    {
239        let hash = make_hash(&value);
240        let mut shard = self.lock_shard_by_hash(hash);
241
242        match table_entry(&mut shard, hash, &value) {
243            Entry::Occupied(e) => e.get().0,
244            Entry::Vacant(e) => {
245                let v = make(value);
246                e.insert((v, ()));
247                v
248            }
249        }
250    }
251}
252
253pub trait IntoPointer {
254    /// Returns a pointer which outlives `self`.
255    fn into_pointer(&self) -> *const ();
256}
257
258impl<K: Eq + Hash + Copy + IntoPointer> ShardedHashMap<K, ()> {
259    pub fn contains_pointer_to<T: Hash + IntoPointer>(&self, value: &T) -> bool {
260        let hash = make_hash(&value);
261        let shard = self.lock_shard_by_hash(hash);
262        let value = value.into_pointer();
263        shard.find(hash, |(k, ())| k.into_pointer() == value).is_some()
264    }
265}
266
267#[inline]
268pub fn make_hash<K: Hash + ?Sized>(val: &K) -> u64 {
269    let mut state = FxHasher::default();
270    val.hash(&mut state);
271    state.finish()
272}
273
274#[inline]
275fn table_entry<'a, K, V, Q>(
276    table: &'a mut HashTable<(K, V)>,
277    hash: u64,
278    key: &Q,
279) -> Entry<'a, (K, V)>
280where
281    K: Hash + Borrow<Q>,
282    Q: ?Sized + Eq,
283{
284    table.entry(hash, move |(k, _)| k.borrow() == key, |(k, _)| make_hash(k))
285}
286
287/// Get a shard with a pre-computed hash value. If `get_shard_by_value` is
288/// ever used in combination with `get_shard_by_hash` on a single `Sharded`
289/// instance, then `hash` must be computed with `FxHasher`. Otherwise,
290/// `hash` can be computed with any hasher, so long as that hasher is used
291/// consistently for each `Sharded` instance.
292#[inline]
293fn get_shard_hash(hash: u64) -> usize {
294    let hash_len = size_of::<usize>();
295    // Ignore the top 7 bits as hashbrown uses these and get the next SHARD_BITS highest bits.
296    // hashbrown also uses the lowest bits, so we can't use those
297    (hash >> (hash_len * 8 - 7 - SHARD_BITS)) as usize
298}