use super::avl::{ReserveError, grown};
use rustc_hash::FxHashMap;
use std::hash::Hash;
fn capacity_to_buckets(cap: usize) -> usize {
if cap < 8 {
return if cap < 4 { 4 } else { 8 };
}
let adjusted = cap.checked_mul(8).expect("capacity overflow") / 7;
adjusted.next_power_of_two()
}
fn buckets_to_capacity(buckets: usize) -> usize {
if buckets < 8 {
buckets - 1
} else {
buckets / 8 * 7
}
}
pub fn hash_capacity_after(len: usize, capacity: usize, additional: usize) -> usize {
if additional == 0 || len + additional <= capacity {
return capacity;
}
let want = (len + additional).max(capacity + 1);
buckets_to_capacity(capacity_to_buckets(want))
}
pub fn hash_table_bytes<T>(capacity: usize) -> usize {
if capacity == 0 {
return 0;
}
let buckets = capacity_to_buckets(capacity);
let data = (buckets * size_of::<T>()).div_ceil(16) * 16;
data + buckets + 16
}
#[derive(Clone, Debug)]
pub struct Directory<K: Eq + Hash + Clone> {
map: FxHashMap<K, u32>,
keys: Vec<K>,
key_heap: usize,
staged: Vec<(K, K, usize)>,
}
impl<K: Eq + Hash + Clone> Default for Directory<K> {
fn default() -> Self {
Self {
map: FxHashMap::default(),
keys: Vec::new(),
key_heap: 0,
staged: Vec::new(),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct DirPlan {
pub new_keys: usize,
pub new_key_heap: usize,
}
impl<K: Eq + Hash + Clone> Directory<K> {
pub fn len(&self) -> usize {
self.keys.len()
}
pub fn is_empty(&self) -> bool {
self.keys.is_empty()
}
pub fn get(&self, k: &K) -> Option<u32> {
self.map.get(k).copied()
}
pub fn key(&self, id: u32) -> &K {
&self.keys[id as usize]
}
pub fn heap_bytes(&self) -> usize {
hash_table_bytes::<(K, u32)>(self.map.capacity())
+ self.keys.capacity() * size_of::<K>()
+ 2 * self.key_heap
+ self.staged.capacity() * size_of::<(K, K, usize)>()
+ self.staged.iter().map(|x| 2 * x.2).sum::<usize>()
}
pub fn plan_parts(&self, plan: DirPlan) -> [(usize, usize); 3] {
let cap = hash_capacity_after(self.map.len(), self.map.capacity(), plan.new_keys);
let keys_cap = grown(self.keys.capacity(), self.keys.len() + plan.new_keys);
[
(
hash_table_bytes::<(K, u32)>(self.map.capacity()),
hash_table_bytes::<(K, u32)>(cap),
),
(
self.keys.capacity() * size_of::<K>(),
keys_cap * size_of::<K>(),
),
(2 * self.key_heap, 2 * (self.key_heap + plan.new_key_heap)),
]
}
pub fn stage_list_bytes(n: usize) -> usize {
n * size_of::<(K, K, usize)>()
}
pub fn stage(&mut self, staged: Vec<(K, K, usize)>) {
self.staged = staged;
}
pub fn drop_staged(&mut self) {
self.staged = Vec::new();
}
pub fn intern_staged(&mut self, k: &K) -> u32 {
if let Some(id) = self.map.get(k) {
return *id;
}
let i = self
.staged
.iter()
.position(|x| x.0 == *k)
.expect("staged directory key");
let (a, b, heap) = self.staged.swap_remove(i);
if self.staged.is_empty() {
self.staged = Vec::new();
}
let id = self.keys.len() as u32;
debug_assert!(self.keys.len() < self.keys.capacity(), "unreserved key");
self.keys.push(b);
self.map.insert(a, id);
self.key_heap += heap;
id
}
pub fn planned_bytes(&self, plan: DirPlan) -> usize {
let cap = hash_capacity_after(self.map.len(), self.map.capacity(), plan.new_keys);
let keys_cap = grown(self.keys.capacity(), self.keys.len() + plan.new_keys);
hash_table_bytes::<(K, u32)>(cap)
+ keys_cap * size_of::<K>()
+ 2 * (self.key_heap + plan.new_key_heap)
}
pub fn try_reserve(&mut self, plan: DirPlan) -> Result<(), ReserveError> {
if plan.new_keys == 0 {
return Ok(());
}
self.map
.try_reserve(plan.new_keys)
.map_err(|_| ReserveError)?;
let cap = grown(self.keys.capacity(), self.keys.len() + plan.new_keys);
if cap > self.keys.capacity() {
self.keys
.try_reserve_exact(cap - self.keys.len())
.map_err(|_| ReserveError)?;
}
Ok(())
}
pub fn intern(&mut self, k: K, heap: usize) -> u32 {
if let Some(id) = self.map.get(&k) {
return *id;
}
let id = self.keys.len() as u32;
self.keys.push(k.clone());
self.map.insert(k, id);
self.key_heap += heap;
id
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CompactPlan {
pub live: usize,
pub live_heap: usize,
pub map_cap: usize,
}
impl<K: Eq + Hash + Clone> Directory<K> {
pub fn compact_plan(&self, live: usize, live_heap: usize) -> CompactPlan {
CompactPlan {
live,
live_heap,
map_cap: hash_capacity_after(0, 0, live),
}
}
pub fn compact_bytes(plan: &CompactPlan) -> usize {
hash_table_bytes::<(K, u32)>(plan.map_cap) + plan.live * size_of::<K>() + 2 * plan.live_heap
}
pub fn compact_alloc_bytes(plan: &CompactPlan) -> usize {
hash_table_bytes::<(K, u32)>(plan.map_cap) + plan.live * size_of::<K>()
}
pub fn try_stage_compact(
&self,
plan: &CompactPlan,
) -> Result<(FxHashMap<K, u32>, Vec<K>), ReserveError> {
let mut map = FxHashMap::default();
map.try_reserve(plan.live).map_err(|_| ReserveError)?;
let mut keys = Vec::new();
keys.try_reserve_exact(plan.live)
.map_err(|_| ReserveError)?;
Ok((map, keys))
}
pub fn apply_compact(
&mut self,
remap: &[u32],
dead: u32,
staged: (FxHashMap<K, u32>, Vec<K>),
plan: &CompactPlan,
) {
let old_map = std::mem::replace(&mut self.map, staged.0);
let old_keys = std::mem::replace(&mut self.keys, staged.1);
for (id, k) in old_keys.into_iter().enumerate() {
if remap[id] != dead {
debug_assert_eq!(remap[id] as usize, self.keys.len());
self.keys.push(k);
}
}
for (k, id) in old_map {
let n = remap[id as usize];
if n != dead {
self.map.insert(k, n);
}
}
debug_assert_eq!(self.map.capacity(), plan.map_cap, "compaction grew");
self.key_heap = plan.live_heap;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn directory_capacity_is_predicted() {
let mut d: Directory<(u16, u64)> = Directory::default();
let mut x = 1u64;
for batch in 0..400usize {
let n = batch % 7;
let keys: Vec<(u16, u64)> = (0..n)
.map(|_| {
x = x.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
((x >> 60) as u16, x)
})
.collect();
let plan = DirPlan {
new_keys: keys.len(),
new_key_heap: 0,
};
let predicted = d.planned_bytes(plan);
let cap = hash_capacity_after(d.map.len(), d.map.capacity(), n);
d.try_reserve(plan).unwrap();
assert_eq!(d.map.capacity(), cap, "batch {batch}");
for k in keys {
d.intern(k, 0);
}
assert_eq!(d.map.capacity(), cap, "insert grew past the reservation");
assert_eq!(d.heap_bytes(), predicted);
}
}
}