crate::ix!();
pub trait EightWayHasher<E> {
fn hashes(&self, e: &E) -> [u32; 8];
}
#[derive(Getters, Debug)]
#[get = "pub(crate)"]
pub struct Cache<Element, Hash>
where
Element: PartialEq + Clone,
Hash: EightWayHasher<Element>,
{
table: Vec<Option<Element>>,
size: u32,
collection_flags: BitPackedAtomicFlags,
epoch_flags: Vec<bool>,
epoch_heuristic_counter: u32,
epoch_size: u32,
depth_limit: u8,
hash_function: Hash,
}
impl<E, H> Default for Cache<E, H>
where
E: PartialEq + Clone,
H: EightWayHasher<E> + Default,
{
fn default() -> Self {
Self {
table: Vec::new(),
size: 0,
collection_flags: BitPackedAtomicFlags::new(0),
epoch_flags: Vec::new(),
epoch_heuristic_counter: 0,
epoch_size: 0,
depth_limit: 0,
hash_function: H::default(),
}
}
}
impl<E, H> Cache<E, H>
where
E: PartialEq + Clone,
H: EightWayHasher<E>,
{
#[inline]
pub fn compute_hashes(&self, e: &E) -> [u32; 8] {
let size = self.size as u64;
self.hash_function
.hashes(e)
.map(|h| ((h as u64 * size) >> 32) as u32)
}
#[inline]
pub const fn invalid() -> u32 {
u32::MAX
}
#[inline]
pub fn allow_erase(&self, n: u32) {
self.collection_flags.bit_set(n);
trace!(bucket = n, "allow_erase()");
}
#[inline]
pub fn please_keep(&self, n: u32) {
self.collection_flags.bit_unset(n);
trace!(bucket = n, "please_keep()");
}
#[inline]
fn epoch_check(&mut self) {
if self.epoch_heuristic_counter > 0 {
self.epoch_heuristic_counter -= 1;
return;
}
let mut epoch_unused = 0_u32;
for i in 0..self.size {
if self.epoch_flags[i as usize] && !self.collection_flags.bit_is_set(i) {
epoch_unused += 1;
}
}
if epoch_unused >= self.epoch_size {
for i in 0..self.size {
if self.epoch_flags[i as usize] {
self.epoch_flags[i as usize] = false;
} else {
self.allow_erase(i);
}
}
self.epoch_heuristic_counter = self.epoch_size;
} else {
let min_scan = self.epoch_size / 16;
let diff = self.epoch_size - epoch_unused;
self.epoch_heuristic_counter = cmp::max(1, cmp::max(min_scan, diff));
}
debug!(counter = self.epoch_heuristic_counter, "epoch_check() reset");
}
pub fn setup(&mut self, new_size: u32) -> u32 {
self.depth_limit = ((cmp::max(2, new_size) as f32).log2()) as u8;
self.size = cmp::max(2, new_size);
self.table = vec![None; self.size as usize];
self.collection_flags .setup(self.size);
self.epoch_flags = vec![false; self.size as usize];
self.epoch_size = cmp::max(1, (45 * self.size) / 100);
self.epoch_heuristic_counter = self.epoch_size;
debug!(
size = self.size,
depth_limit = self.depth_limit,
epoch_size = self.epoch_size,
"cache setup() completed"
);
self.size
}
#[inline]
pub fn setup_bytes(&mut self, bytes: usize) -> u32 {
let elem_sz = cmp::max(mem::size_of::<E>(), 1);
self.setup((bytes / elem_sz) as u32)
}
#[inline]
pub fn insert(&mut self, mut e: E) {
self.epoch_check();
let mut last_loc = Self::invalid();
let mut last_epoch = true;
let mut locs = self.compute_hashes(&e);
for &loc in &locs {
if let Some(ref existing) = self.table[loc as usize] {
if existing == &e {
self.please_keep(loc);
self.epoch_flags[loc as usize] = last_epoch;
return;
}
}
}
for _depth in 0..self.depth_limit {
for &loc in &locs {
if self.collection_flags.bit_is_set(loc) {
self.table[loc as usize] = Some(e);
self.please_keep(loc);
self.epoch_flags[loc as usize] = last_epoch;
return;
}
}
let idx = locs
.iter()
.position(|&x| x == last_loc)
.map(|p| (p + 1) & 7)
.unwrap_or(0);
last_loc = locs[idx];
let slot = &mut self.table[last_loc as usize];
let mut victim = slot.take().expect("victim must exist");
mem::swap(&mut victim, &mut e); *slot = Some(victim);
let epoch_current = last_epoch;
last_epoch = self.epoch_flags[last_loc as usize];
self.epoch_flags[last_loc as usize] = epoch_current;
locs = self.compute_hashes(&e);
}
trace!("insert() evicted element after depth_limit");
}
#[inline]
pub fn contains(&self, e: &E, erase: bool) -> bool {
for &loc in &self.compute_hashes(e) {
if let Some(ref existing) = self.table[loc as usize] {
if existing == e {
if erase {
self.allow_erase(loc);
}
return true;
}
}
}
false
}
}
#[cfg(test)]
mod cuckoo_cache_suite {
use super::*;
#[derive(Default)]
struct TrivialHasher;
impl EightWayHasher<u32> for TrivialHasher {
fn hashes(&self, e: &u32) -> [u32; 8] {
let mut h = [0u32; 8];
for i in 0..8 {
h[i] = e.wrapping_add(i as u32).wrapping_mul(0x9E37_79B9);
}
h
}
}
fn fresh_cache(cap: u32) -> Cache<u32, TrivialHasher> {
let mut c: Cache<u32, TrivialHasher> = Cache::default();
c.setup(cap);
c
}
#[traced_test]
fn setup_establishes_parameters() {
let mut c = Cache::<u32, TrivialHasher>::default();
let sz = c.setup(100);
assert_eq!(sz, *c.size());
assert!(*c.depth_limit() > 0);
assert_eq!(c.collection_flags().mem().len(), ((sz + 7) / 8) as usize);
}
#[traced_test]
fn insert_and_contains_roundtrip() {
let mut c = fresh_cache(128);
for i in 0..50u32 {
c.insert(i);
}
for i in 0..50u32 {
assert!(c.contains(&i, false), "cache should contain {i}");
}
}
#[traced_test]
fn erase_mechanism_works() {
let mut c = fresh_cache(32);
c.insert(42);
assert!(c.contains(&42, true), "first lookup marks for erase");
assert!(c.contains(&42, false), "element must remain until reclaimed");
for i in 0..64u32 {
if i != 42 {
c.insert(i);
}
}
}
}