use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[derive(Debug)]
pub struct TinyLFU {
width: usize,
depth: usize,
tables: Vec<Vec<u8>>, }
impl TinyLFU {
pub fn new(width: usize, depth: usize) -> Self {
let width = width.next_power_of_two();
let depth = depth.max(1).min(4);
let tables = (0..depth).map(|_| vec![0u8; (width + 1) / 2]).collect();
Self {
width,
depth,
tables,
}
}
#[inline]
fn hash_i(&self, key: u64, i: usize) -> usize {
let mut h = DefaultHasher::new();
(key.wrapping_add((i as u64) << 32)).hash(&mut h);
(h.finish() as usize) & (self.width - 1)
}
#[inline]
fn load_counter(slot: &mut [u8], idx: usize) -> u8 {
let byte = &mut slot[idx / 2];
if idx % 2 == 0 {
*byte & 0x0F
} else {
(*byte >> 4) & 0x0F
}
}
#[inline]
fn store_counter(slot: &mut [u8], idx: usize, val: u8) {
let b = &mut slot[idx / 2];
if idx % 2 == 0 {
*b = (*b & 0xF0) | (val & 0x0F);
} else {
*b = (*b & 0x0F) | ((val & 0x0F) << 4);
}
}
pub fn admit_record(&mut self, key: u64) {
for i in 0..self.depth {
let idx = self.hash_i(key, i);
let slot = &mut self.tables[i];
let mut c = Self::load_counter(slot, idx);
if c < 15 {
c += 1;
}
Self::store_counter(slot, idx, c);
}
}
pub fn estimate(&self, key: u64) -> u8 {
let mut minv = 15u8;
for i in 0..self.depth {
let idx = self.hash_i(key, i);
let slot = &self.tables[i];
let c = if idx / 2 < slot.len() {
if idx % 2 == 0 {
slot[idx / 2] & 0x0F
} else {
(slot[idx / 2] >> 4) & 0x0F
}
} else {
0
};
if c < minv {
minv = c;
}
}
minv
}
pub fn age(&mut self) {
for t in self.tables.iter_mut() {
for b in t.iter_mut() {
let lo = (*b & 0x0F) >> 1;
let hi = ((*b >> 4) & 0x0F) >> 1;
*b = (hi << 4) | lo;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tiny_lfu_basic() {
let mut f = TinyLFU::new(1024, 4);
let k1 = 123u64;
let k2 = 456u64;
for _ in 0..8 {
f.admit_record(k1);
}
for _ in 0..2 {
f.admit_record(k2);
}
assert!(f.estimate(k1) >= f.estimate(k2));
f.age();
assert!(f.estimate(k1) >= f.estimate(k2));
}
}