use std::{collections::HashMap, num::NonZeroU32};
use crate::hash::Fingerprint;
const MAX_SHARD_SIZE: u32 = 512;
fn default_shard_size() -> NonZeroU32 {
static ITEM_SHARD_SIZE: std::sync::OnceLock<NonZeroU32> = std::sync::OnceLock::new();
fn determine_default_shard_size() -> NonZeroU32 {
let thread_cnt = {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
};
let size = (thread_cnt.next_power_of_two() * 2) as u32;
NonZeroU32::new(size.min(MAX_SHARD_SIZE)).unwrap()
}
*ITEM_SHARD_SIZE.get_or_init(determine_default_shard_size)
}
type FMapBase<V> = parking_lot::RwLock<HashMap<Fingerprint, V>>;
pub struct FingerprintMap<V> {
mask: u32,
shards: Vec<parking_lot::RwLock<HashMap<Fingerprint, V>>>,
}
impl<V> Default for FingerprintMap<V> {
fn default() -> Self {
Self::new(default_shard_size())
}
}
impl<V> FingerprintMap<V> {
pub fn new(shard_size: NonZeroU32) -> Self {
let shard_size = shard_size.get().next_power_of_two();
let shard_size = shard_size.min(MAX_SHARD_SIZE);
assert!(
shard_size.is_power_of_two(),
"shard size must be a power of two"
);
assert!(shard_size > 0, "shard size must be greater than zero");
Self {
mask: shard_size - 1,
shards: (0..shard_size)
.map(|_| parking_lot::RwLock::new(HashMap::new()))
.collect(),
}
}
pub fn into_items(self) -> impl Iterator<Item = (Fingerprint, V)> {
self.shards
.into_iter()
.flat_map(|shard| shard.into_inner().into_iter())
}
pub fn shard(&self, fg: Fingerprint) -> &FMapBase<V> {
let shards = &self.shards;
let route_idx = (fg.lower32() & self.mask) as usize;
debug_assert!(route_idx < shards.len());
unsafe { shards.get_unchecked(route_idx) }
}
pub fn as_mut_slice(&mut self) -> &mut [FMapBase<V>] {
&mut self.shards
}
pub fn contains_key(&self, fg: &Fingerprint) -> bool {
self.shard(*fg).read().contains_key(fg)
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_default_shard_size() {
let size = super::default_shard_size().get();
eprintln!("size = {size}");
assert!(size > 0);
assert_eq!(size & (size - 1), 0);
}
}