use crate::store::hash_key;
use crate::sync::HybridMutex;
use std::collections::HashMap;
use std::hash::{BuildHasher, Hash};
use std::sync::atomic::{AtomicUsize, Ordering};
const BATCH_STRIPES: usize = 16;
pub(crate) struct AccessBatcher<K> {
active_idx: AtomicUsize,
instances: [Box<[HybridMutex<HashMap<K, u64>>]>; 2],
}
impl<K: Hash + Eq> AccessBatcher<K> {
pub(crate) fn new() -> Self {
let create_instance = || -> Box<[HybridMutex<HashMap<K, u64>>]> {
(0..BATCH_STRIPES)
.map(|_| HybridMutex::new(HashMap::new()))
.collect()
};
Self {
active_idx: AtomicUsize::new(0),
instances: [create_instance(), create_instance()],
}
}
#[inline]
pub(crate) fn record_access<H: BuildHasher>(&self, key: K, cost: u64, hasher: &H)
where
K: Clone,
{
let idx = self.active_idx.load(Ordering::Relaxed);
let stripes = &self.instances[idx];
let hash = hash_key(hasher, &key);
let stripe_idx = hash as usize & (BATCH_STRIPES - 1);
let mut guard = stripes[stripe_idx].lock();
guard.insert(key, cost);
}
pub(crate) fn drain(&self) -> HashMap<K, u64> {
let inactive_idx = self.active_idx.swap(1, Ordering::AcqRel);
let active_idx = 1 - inactive_idx;
self.active_idx.store(active_idx, Ordering::Release);
let inactive_stripes = &self.instances[inactive_idx];
let mut final_batch = HashMap::new();
for stripe_mutex in inactive_stripes.iter() {
let mut guard = stripe_mutex.lock();
if !guard.is_empty() {
final_batch.extend(std::mem::take(&mut *guard));
}
}
final_batch
}
}