use hashbrown::{Equivalent, HashTable, hash_table::Entry};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::hash::{BuildHasher, Hash};
const SHARDS: usize = 16;
#[repr(align(128))]
struct Shard<K, V>(Mutex<HashTable<(K, V)>>);
pub struct ShardedMemo<K, V> {
shards: OnceCell<Box<[Shard<K, V>; SHARDS]>>,
hasher: crate::DefaultBuildHasher,
}
impl<K, V> Default for ShardedMemo<K, V> {
fn default() -> Self {
Self {
shards: OnceCell::new(),
hasher: crate::DefaultBuildHasher::default(),
}
}
}
impl<K: Hash + Eq, V: Clone> ShardedMemo<K, V> {
#[allow(clippy::cast_possible_truncation)]
fn shard(shards: &[Shard<K, V>; SHARDS], hash: u64) -> &Mutex<HashTable<(K, V)>> {
&shards[((hash >> 48) & (SHARDS as u64 - 1)) as usize].0
}
pub fn get<Q: Hash + Equivalent<K> + ?Sized>(&self, key: &Q) -> Option<V> {
let shards = self.shards.get()?;
let hash = self.hasher.hash_one(key);
Self::shard(shards, hash).lock().find(hash, |(k, _)| key.equivalent(k)).map(|(_, v)| v.clone())
}
pub fn insert(&self, key: K, value: V) {
let shards = self
.shards
.get_or_init(|| Box::new(std::array::from_fn(|_| Shard(Mutex::new(HashTable::new())))));
let hash = self.hasher.hash_one(&key);
let mut shard = Self::shard(shards, hash).lock();
match shard.entry(hash, |(k, _)| *k == key, |(k, _)| self.hasher.hash_one(k)) {
Entry::Occupied(mut entry) => entry.get_mut().1 = value,
Entry::Vacant(entry) => {
entry.insert((key, value));
}
}
}
}