use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use hashlink::LinkedHashMap;
use crate::sketch::{CountMinSketch, Doorkeeper};
use crate::{Cache, CacheEntry};
struct KeyHasher {
state: u64,
seed: u64,
}
impl KeyHasher {
fn new(seed: u64) -> Self {
KeyHasher {
state: 0xcbf29ce484222325u64 ^ seed,
seed,
}
}
}
impl Hasher for KeyHasher {
fn write(&mut self, bytes: &[u8]) {
for &b in bytes {
self.state ^= b as u64;
self.state = self.state.wrapping_mul(0x100000001b3);
}
}
fn finish(&self) -> u64 {
self.state ^ self.seed
}
}
fn key_bytes<K: Hash>(key: &K) -> [u8; 16] {
let mut h1 = KeyHasher::new(0xcbf29ce484222325);
key.hash(&mut h1);
let a = h1.finish();
let mut h2 = KeyHasher::new(0x6c62272e07bb0142);
key.hash(&mut h2);
let b = h2.finish();
let mut buf = [0u8; 16];
buf[..8].copy_from_slice(&a.to_le_bytes());
buf[8..].copy_from_slice(&b.to_le_bytes());
buf
}
fn xorshift64(x: u64) -> u64 {
let x = x ^ (x << 13);
let x = x ^ (x >> 7);
x ^ (x << 17)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CacheSegment {
Window,
Probation,
Protected,
}
pub struct WTinyLfuCache<K, V> {
total_cap: usize,
window_cap: usize,
main_cap: usize,
protected_cap: usize,
window: LinkedHashMap<K, CacheEntry<V>, ahash::RandomState>,
protected: LinkedHashMap<K, CacheEntry<V>, ahash::RandomState>,
probation: LinkedHashMap<K, CacheEntry<V>, ahash::RandomState>,
segment_index: HashMap<K, CacheSegment, ahash::RandomState>,
sketch: CountMinSketch,
doorkeeper: Doorkeeper,
rng: u64,
}
impl<K, V> WTinyLfuCache<K, V>
where
K: Eq + Hash + Clone,
{
#[must_use]
pub fn new(total_cap: usize) -> Self {
let total_cap = total_cap.max(2);
let window_cap = (total_cap / 100).max(1);
let main_cap = total_cap - window_cap;
let protected_cap = main_cap * 4 / 5;
let rng = total_cap as u64 ^ 0xdeadbeef_cafebabe;
WTinyLfuCache {
total_cap,
window_cap,
main_cap,
protected_cap,
window: LinkedHashMap::with_hasher(ahash::RandomState::default()),
protected: LinkedHashMap::with_hasher(ahash::RandomState::default()),
probation: LinkedHashMap::with_hasher(ahash::RandomState::default()),
segment_index: HashMap::with_hasher(ahash::RandomState::default()),
sketch: CountMinSketch::new(total_cap),
doorkeeper: Doorkeeper::new(total_cap),
rng,
}
}
fn record_access(&mut self, key: &K) {
let kb = key_bytes(key);
let seen_before = self.doorkeeper.put(&kb);
if seen_before {
self.sketch.increment(&kb);
}
}
fn freq(&self, key: &K) -> u64 {
let kb = key_bytes(key);
self.sketch.estimate(&kb)
}
fn remove_if_expired(&mut self, key: &K) -> bool {
let seg = match self.segment_index.get(key).copied() {
Some(s) => s,
None => return false,
};
let expired = match seg {
CacheSegment::Window => self
.window
.get(key)
.map(|e| e.is_expired())
.unwrap_or(false),
CacheSegment::Probation => self
.probation
.get(key)
.map(|e| e.is_expired())
.unwrap_or(false),
CacheSegment::Protected => self
.protected
.get(key)
.map(|e| e.is_expired())
.unwrap_or(false),
};
if expired {
self.segment_index.remove(key);
match seg {
CacheSegment::Window => {
self.window.remove(key);
}
CacheSegment::Probation => {
self.probation.remove(key);
}
CacheSegment::Protected => {
self.protected.remove(key);
}
}
}
expired
}
fn promote_in_window(&mut self, key: &K) {
if let Some(entry) = self.window.remove(key) {
self.window.insert(key.clone(), entry);
}
}
fn promote_in_protected(&mut self, key: &K) {
if let Some(entry) = self.protected.remove(key) {
self.protected.insert(key.clone(), entry);
}
}
fn promote_probation_to_protected(&mut self, key: &K) {
if let Some(entry) = self.probation.remove(key) {
self.segment_index
.insert(key.clone(), CacheSegment::Protected);
self.protected.insert(key.clone(), entry);
if self.protected.len() > self.protected_cap {
if let Some((demoted_key, demoted_entry)) = self.protected.pop_front() {
self.segment_index
.insert(demoted_key.clone(), CacheSegment::Probation);
self.probation.insert(demoted_key, demoted_entry);
}
}
}
}
fn evict_from_window_if_over_cap(&mut self) -> Option<(K, CacheEntry<V>)> {
if self.window.len() > self.window_cap {
if let Some((k, entry)) = self.window.pop_front() {
self.segment_index.remove(&k);
return Some((k, entry));
}
}
None
}
fn run_admission_gate(&mut self, candidate_key: K, candidate_entry: CacheEntry<V>) {
let main_size = self.probation.len() + self.protected.len();
if main_size < self.main_cap {
self.segment_index
.insert(candidate_key.clone(), CacheSegment::Probation);
self.probation.insert(candidate_key, candidate_entry);
return;
}
let victim_key = match self.probation.front() {
Some((k, _)) => k.clone(),
None => {
return;
}
};
let candidate_freq = self.freq(&candidate_key);
let victim_freq = self.freq(&victim_key);
let admit = if candidate_freq > victim_freq {
true
} else if candidate_freq == victim_freq {
self.rng = xorshift64(self.rng);
(self.rng & 0xFF) < 128
} else {
false
};
if admit {
self.probation.pop_front();
self.segment_index.remove(&victim_key);
self.segment_index
.insert(candidate_key.clone(), CacheSegment::Probation);
self.probation.insert(candidate_key, candidate_entry);
}
}
fn insert_entry(&mut self, key: K, entry: CacheEntry<V>) -> Option<V> {
if let Some(seg) = self.segment_index.get(&key).copied() {
self.record_access(&key);
match seg {
CacheSegment::Window => {
if let Some(existing) = self.window.get_mut(&key) {
existing.expires_at = entry.expires_at;
existing.value = entry.value;
}
self.promote_in_window(&key);
}
CacheSegment::Probation => {
if let Some(existing) = self.probation.get_mut(&key) {
existing.expires_at = entry.expires_at;
existing.value = entry.value;
}
self.promote_probation_to_protected(&key);
}
CacheSegment::Protected => {
if let Some(existing) = self.protected.get_mut(&key) {
existing.expires_at = entry.expires_at;
existing.value = entry.value;
}
self.promote_in_protected(&key);
}
}
return None;
}
self.record_access(&key);
self.segment_index.insert(key.clone(), CacheSegment::Window);
self.window.insert(key.clone(), entry);
if self.window.len() > self.window_cap {
if let Some((candidate_key, candidate_entry)) = self.evict_from_window_if_over_cap() {
self.run_admission_gate(candidate_key, candidate_entry);
}
}
None
}
pub fn get(&mut self, key: &K) -> Option<&V> {
if self.remove_if_expired(key) {
return None;
}
let seg = self.segment_index.get(key).copied()?;
self.record_access(key);
match seg {
CacheSegment::Window => {
self.promote_in_window(key);
self.window.get(key).map(|e| &e.value)
}
CacheSegment::Probation => {
self.promote_probation_to_protected(key);
self.protected.get(key).map(|e| &e.value)
}
CacheSegment::Protected => {
self.promote_in_protected(key);
self.protected.get(key).map(|e| &e.value)
}
}
}
pub fn put(&mut self, key: K, value: V) -> Option<V> {
self.insert_entry(key, CacheEntry::new(value))
}
pub fn put_with_ttl(&mut self, key: K, value: V, ttl: std::time::Duration) -> Option<V> {
self.insert_entry(key, CacheEntry::with_ttl(value, ttl))
}
#[must_use]
pub fn len(&self) -> usize {
self.window.len() + self.protected.len() + self.probation.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn cap(&self) -> usize {
self.total_cap
}
#[must_use]
pub fn peek(&self, key: &K) -> Option<&V> {
let seg = self.segment_index.get(key)?;
let entry = match seg {
CacheSegment::Window => self.window.get(key)?,
CacheSegment::Probation => self.probation.get(key)?,
CacheSegment::Protected => self.protected.get(key)?,
};
if entry.is_expired() {
None
} else {
Some(&entry.value)
}
}
#[must_use]
pub fn contains_key(&self, key: &K) -> bool {
let seg = match self.segment_index.get(key) {
Some(s) => s,
None => return false,
};
let entry = match seg {
CacheSegment::Window => self.window.get(key),
CacheSegment::Probation => self.probation.get(key),
CacheSegment::Protected => self.protected.get(key),
};
entry.map(|e| !e.is_expired()).unwrap_or(false)
}
pub fn remove(&mut self, key: &K) -> Option<V> {
let seg = self.segment_index.remove(key)?;
let entry = match seg {
CacheSegment::Window => self.window.remove(key)?,
CacheSegment::Probation => self.probation.remove(key)?,
CacheSegment::Protected => self.protected.remove(key)?,
};
Some(entry.value)
}
pub fn clear(&mut self) {
self.window.clear();
self.protected.clear();
self.probation.clear();
self.segment_index.clear();
self.sketch.clear();
self.doorkeeper.clear();
}
pub fn resize(&mut self, new_cap: usize) {
let new_cap = new_cap.max(2);
self.total_cap = new_cap;
self.window_cap = (new_cap / 100).max(1);
self.main_cap = new_cap - self.window_cap;
self.protected_cap = self.main_cap * 4 / 5;
while self.window.len() > self.window_cap {
if let Some((k, _)) = self.window.pop_front() {
self.segment_index.remove(&k);
}
}
while self.protected.len() > self.protected_cap {
if let Some((k, _)) = self.protected.pop_front() {
self.segment_index.remove(&k);
}
}
let main_cap = self.main_cap;
while self.probation.len() + self.protected.len() > main_cap {
if let Some((k, _)) = self.probation.pop_front() {
self.segment_index.remove(&k);
}
}
}
}
impl<K, V> Cache<K, V> for WTinyLfuCache<K, V>
where
K: Eq + Hash + Clone,
{
fn get(&mut self, key: &K) -> Option<&V> {
WTinyLfuCache::get(self, key)
}
fn put(&mut self, key: K, value: V) -> Option<V> {
WTinyLfuCache::put(self, key, value)
}
fn put_with_ttl(&mut self, key: K, value: V, ttl: std::time::Duration) -> Option<V> {
WTinyLfuCache::put_with_ttl(self, key, value, ttl)
}
fn len(&self) -> usize {
WTinyLfuCache::len(self)
}
fn cap(&self) -> usize {
WTinyLfuCache::cap(self)
}
fn remove(&mut self, key: &K) -> Option<V> {
WTinyLfuCache::remove(self, key)
}
fn clear(&mut self) {
WTinyLfuCache::clear(self);
}
fn peek(&self, key: &K) -> Option<&V> {
WTinyLfuCache::peek(self, key)
}
fn contains_key(&self, key: &K) -> bool {
WTinyLfuCache::contains_key(self, key)
}
fn resize(&mut self, new_cap: usize) {
WTinyLfuCache::resize(self, new_cap);
}
}