use std::hash::{Hasher, Hash}; use std::mem; use std::borrow::Borrow; use std::collections::hash_map::RawEntryMut; use crate::fx::{FxHasher, FxHashMap}; use crate::sync::{Lock, LockGuard}; #[derive(Clone, Default)] #[cfg_attr(parallel_compiler, repr(align(64)))] struct CacheAligned(T); #[cfg(parallel_compiler)] // 32 shards is sufficient to reduce contention on an 8-core Ryzen 7 1700, // but this should be tested on higher core count CPUs. How the `Sharded` type gets used // may also affect the ideal nunber of shards. const SHARD_BITS: usize = 5; #[cfg(not(parallel_compiler))] const SHARD_BITS: usize = 0; const SHARDS: usize = 1 << SHARD_BITS; /// An array of cache-line aligned inner locked structures with convenience methods. #[derive(Clone)] pub struct Sharded { shards: [CacheAligned>; SHARDS], } impl Default for Sharded { #[inline] fn default() -> Self { let mut shards: mem::MaybeUninit<[CacheAligned>; SHARDS]> = mem::MaybeUninit::uninit(); let first = shards.as_mut_ptr() as *mut CacheAligned>; unsafe { for i in 0..SHARDS { first.add(i).write(CacheAligned(Lock::new(T::default()))); } Sharded { shards: shards.assume_init(), } } } } impl Sharded { #[inline] pub fn get_shard_by_value(&self, val: &K) -> &Lock { if SHARDS == 1 { &self.shards[0].0 } else { self.get_shard_by_hash(make_hash(val)) } } #[inline] pub fn get_shard_by_hash(&self, hash: u64) -> &Lock { let hash_len = mem::size_of::(); // Ignore the top 7 bits as hashbrown uses these and get the next SHARD_BITS highest bits. // hashbrown also uses the lowest bits, so we can't use those let bits = (hash >> (hash_len * 8 - 7 - SHARD_BITS)) as usize; let i = bits % SHARDS; &self.shards[i].0 } pub fn lock_shards(&self) -> Vec> { (0..SHARDS).map(|i| self.shards[i].0.lock()).collect() } pub fn try_lock_shards(&self) -> Option>> { (0..SHARDS).map(|i| self.shards[i].0.try_lock()).collect() } } pub type ShardedHashMap = Sharded>; impl ShardedHashMap { pub fn len(&self) -> usize { self.lock_shards().iter().map(|shard| shard.len()).sum() } } impl ShardedHashMap { #[inline] pub fn intern_ref(&self, value: &Q, make: impl FnOnce() -> K) -> K where K: Borrow, Q: Hash + Eq { let hash = make_hash(value); let mut shard = self.get_shard_by_hash(hash).lock(); let entry = shard.raw_entry_mut().from_key_hashed_nocheck(hash, value); match entry { RawEntryMut::Occupied(e) => *e.key(), RawEntryMut::Vacant(e) => { let v = make(); e.insert_hashed_nocheck(hash, v, ()); v } } } #[inline] pub fn intern(&self, value: Q, make: impl FnOnce(Q) -> K) -> K where K: Borrow, Q: Hash + Eq { let hash = make_hash(&value); let mut shard = self.get_shard_by_hash(hash).lock(); let entry = shard.raw_entry_mut().from_key_hashed_nocheck(hash, &value); match entry { RawEntryMut::Occupied(e) => *e.key(), RawEntryMut::Vacant(e) => { let v = make(value); e.insert_hashed_nocheck(hash, v, ()); v } } } } #[inline] fn make_hash(val: &K) -> u64 { let mut state = FxHasher::default(); val.hash(&mut state); state.finish() }