use std::{
collections::VecDeque,
sync::atomic::{AtomicI8, Ordering},
};
mod key;
pub use key::S3FIFOKey;
pub struct S3FIFO<K, V> {
small: VecDeque<Item<K, V>>,
main: VecDeque<Item<K, V>>,
ghost: VecDeque<Key<K>>,
}
impl<K: PartialEq + Clone, V> S3FIFO<K, V> {
pub fn new(capacity: usize) -> Self {
let small_capacity = capacity / 10;
let main_capacity = capacity * 9 / 10;
S3FIFO {
small: VecDeque::with_capacity(small_capacity),
main: VecDeque::with_capacity(main_capacity),
ghost: VecDeque::with_capacity(main_capacity),
}
}
pub fn get(&self, key: &K) -> Option<&V> {
if let Some(item) = self.small.iter().find(|item| item.key == *key) {
let _ = item
.freq
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |x| {
if x > 2 {
Some(3)
} else {
Some(x + 1)
}
});
return Some(&item.value);
}
if let Some(item) = self.main.iter().find(|item| item.key == *key) {
let _ = item
.freq
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |x| {
if x > 2 {
Some(3)
} else {
Some(x + 1)
}
});
return Some(&item.value);
}
None
}
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
if let Some(item) = self.small.iter_mut().find(|item| item.key == *key) {
let _ = item
.freq
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |x| {
if x > 2 {
Some(3)
} else {
Some(x + 1)
}
});
return Some(&mut item.value);
}
if let Some(item) = self.main.iter_mut().find(|item| item.key == *key) {
let _ = item
.freq
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |x| {
if x > 2 {
Some(3)
} else {
Some(x + 1)
}
});
return Some(&mut item.value);
}
None
}
pub fn put(&mut self, key: K, value: V) -> (&mut V, Option<V>) {
if let Some(item) = self.get_mut(&key) {
let item = item as *mut V;
return (unsafe { &mut *item }, None);
}
let mut evicted = None;
if let Some(key) = self.ghost.iter().find(|k| k.key == key) {
let item = Item {
key: key.key.clone(),
value,
freq: key.freq.load(Ordering::Relaxed).into(),
};
if self.main.capacity() == self.main.len() {
evicted = self.evict_main();
}
self.main.push_front(item);
return (&mut self.main.front_mut().unwrap().value, evicted);
} else {
let item = Item {
key,
value,
freq: 0.into(),
};
if self.small.capacity() == self.small.len() {
evicted = self.evict_small();
}
self.small.push_front(item);
return (&mut self.small.front_mut().unwrap().value, evicted);
}
}
pub fn pop(&mut self) -> Option<V> {
while !self.small.is_empty() {
if let Some(value) = self.evict_small() {
return Some(value);
}
}
self.evict_main()
}
pub fn drain(&mut self) -> Vec<V> {
self.ghost.clear();
let mut values = Vec::with_capacity(self.small.len() + self.main.len());
values.extend(self.small.drain(..).map(|item| item.value));
values.extend(self.main.drain(..).map(|item| item.value));
values
}
fn evict_small(&mut self) -> Option<V> {
if self.small.is_empty() {
return None;
}
let item = self.small.pop_back().unwrap();
let freq = item.freq.load(Ordering::Relaxed);
if freq > 1 {
let mut value = None;
if self.main.capacity() == self.main.len() {
value = self.evict_main();
}
self.main.push_front(item);
value
} else {
let Item { key, value, freq } = item;
if self.ghost.capacity() == self.ghost.len() {
self.ghost.pop_back();
}
self.ghost.push_front(Key { key, freq });
Some(value)
}
}
fn evict_main(&mut self) -> Option<V> {
let mut iters = (3 * self.main.len() + 1) as isize;
while iters > 0 {
let Some(item) = self.main.pop_back() else {
return None;
};
iters -= 1;
let freq = item.freq.load(Ordering::Relaxed);
if freq > 0 {
item.freq.fetch_sub(1, Ordering::Relaxed);
self.main.push_front(item);
} else {
return Some(item.value);
}
}
None
}
}
struct Item<K, V> {
key: K,
value: V,
freq: AtomicI8, }
struct Key<K> {
key: K,
freq: AtomicI8, }
#[cfg(test)]
mod tests {
use super::*;
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
#[derive(Hash, Clone)]
struct Abc {
a: u8,
b: u16,
c: u32,
}
#[test]
fn can_use_cache() {
let test_value = Abc { a: 1, b: 2, c: 3 };
let mut hasher = DefaultHasher::new();
test_value.hash(&mut hasher);
let test_key = hasher.finish();
let mut cache = S3FIFO::new(10);
cache.put(test_key, test_value);
assert!(cache.get(&test_key).is_some());
}
#[test]
fn can_fill_cache() {
let mut cache = S3FIFO::new(10);
for i in 0..10 {
let test_value = Abc {
a: i as u8,
b: i as u16,
c: i as u32,
};
let test_key = S3FIFOKey::new(&test_value);
assert!(cache.get(&test_key).is_none());
cache.put(test_key.clone(), test_value);
assert!(cache.get(&test_key).is_some());
}
assert_eq!(cache.small.len(), 1);
assert_eq!(cache.ghost.len(), 9);
let repeat_value = Abc { a: 0, b: 0, c: 0 };
let repeat_key = S3FIFOKey::new(&repeat_value);
assert!(cache.get(&repeat_key).is_none());
cache.put(repeat_key, repeat_value);
assert_eq!(cache.small.len(), 1);
assert_eq!(cache.ghost.len(), 9);
assert_eq!(cache.main.len(), 1);
let repeat_value = Abc { a: 0, b: 0, c: 0 };
let repeat_key = S3FIFOKey::new(&repeat_value);
assert!(cache.get(&repeat_key).is_some());
assert_eq!(cache.small.len(), 1);
assert_eq!(cache.ghost.len(), 9);
assert_eq!(cache.main.len(), 1);
}
}