use crate::lru::raw::EntryNode;
use crate::lru::{
swap_value, CacheError, DefaultEvictCallback, KeysLRUIter, KeysMRUIter, LRUIter, LRUIterMut,
MRUIter, MRUIterMut, RawLRU, ValuesLRUIter, ValuesLRUIterMut, ValuesMRUIter, ValuesMRUIterMut,
};
use crate::{Cache, DefaultHashBuilder, KeyRef, PutResult};
use alloc::boxed::Box;
use alloc::fmt;
use core::borrow::Borrow;
use core::hash::{BuildHasher, Hash};
use crate::polyfill::floor;
pub const DEFAULT_2Q_RECENT_RATIO: f64 = 0.25;
pub const DEFAULT_2Q_GHOST_RATIO: f64 = 0.5;
pub struct TwoQueueCacheBuilder<
RH = DefaultHashBuilder,
FH = DefaultHashBuilder,
GH = DefaultHashBuilder,
> {
size: usize,
ghost_ratio: Option<f64>,
recent_ratio: Option<f64>,
recent_hasher: Option<RH>,
freq_hasher: Option<FH>,
ghost_hasher: Option<GH>,
}
impl Default for TwoQueueCacheBuilder {
fn default() -> Self {
Self {
size: 0,
ghost_ratio: Some(DEFAULT_2Q_GHOST_RATIO),
recent_ratio: Some(DEFAULT_2Q_RECENT_RATIO),
recent_hasher: Some(DefaultHashBuilder::default()),
freq_hasher: Some(DefaultHashBuilder::default()),
ghost_hasher: Some(DefaultHashBuilder::default()),
}
}
}
impl TwoQueueCacheBuilder {
pub fn new(size: usize) -> Self {
Self::default().set_size(size)
}
}
impl<RH: BuildHasher, FH: BuildHasher, GH: BuildHasher> TwoQueueCacheBuilder<RH, FH, GH> {
pub fn set_ghost_ratio(self, ratio: f64) -> Self {
TwoQueueCacheBuilder {
size: self.size,
ghost_ratio: Some(ratio),
recent_ratio: self.recent_ratio,
recent_hasher: self.recent_hasher,
freq_hasher: self.freq_hasher,
ghost_hasher: self.ghost_hasher,
}
}
pub fn set_recent_ratio(self, ratio: f64) -> Self {
TwoQueueCacheBuilder {
size: self.size,
ghost_ratio: self.ghost_ratio,
recent_ratio: Some(ratio),
recent_hasher: self.recent_hasher,
freq_hasher: self.freq_hasher,
ghost_hasher: self.ghost_hasher,
}
}
pub fn set_size(self, size: usize) -> Self {
TwoQueueCacheBuilder {
size,
ghost_ratio: self.ghost_ratio,
recent_ratio: self.recent_ratio,
recent_hasher: self.recent_hasher,
freq_hasher: self.freq_hasher,
ghost_hasher: self.ghost_hasher,
}
}
pub fn set_recent_hasher<NRH: BuildHasher>(
self,
hasher: NRH,
) -> TwoQueueCacheBuilder<NRH, FH, GH> {
TwoQueueCacheBuilder {
size: self.size,
ghost_ratio: self.ghost_ratio,
recent_ratio: self.recent_ratio,
recent_hasher: Some(hasher),
freq_hasher: self.freq_hasher,
ghost_hasher: self.ghost_hasher,
}
}
pub fn set_frequent_hasher<NFH: BuildHasher>(
self,
hasher: NFH,
) -> TwoQueueCacheBuilder<RH, NFH, GH> {
TwoQueueCacheBuilder {
size: self.size,
ghost_ratio: self.ghost_ratio,
recent_ratio: self.recent_ratio,
recent_hasher: self.recent_hasher,
freq_hasher: Some(hasher),
ghost_hasher: self.ghost_hasher,
}
}
pub fn set_ghost_hasher<NGH: BuildHasher>(
self,
hasher: NGH,
) -> TwoQueueCacheBuilder<RH, FH, NGH> {
TwoQueueCacheBuilder {
size: self.size,
ghost_ratio: self.ghost_ratio,
recent_ratio: self.recent_ratio,
recent_hasher: self.recent_hasher,
freq_hasher: self.freq_hasher,
ghost_hasher: Some(hasher),
}
}
pub fn finalize<K: Hash + Eq, V>(self) -> Result<TwoQueueCache<K, V, RH, FH, GH>, CacheError> {
let size = self.size;
if size == 0 {
return Err(CacheError::InvalidSize(0));
}
let rr = self.recent_ratio.unwrap();
if !(0.0..=1.0).contains(&rr) {
return Err(CacheError::InvalidRecentRatio(rr));
}
let gr = self.ghost_ratio.unwrap();
if !(0.0..=1.0).contains(&gr) {
return Err(CacheError::InvalidGhostRatio(gr));
}
let rs = floor((size as f64) * rr) as usize;
let es = floor((size as f64) * gr) as usize;
let recent = RawLRU::with_hasher(size, self.recent_hasher.unwrap()).unwrap();
let freq = RawLRU::with_hasher(size, self.freq_hasher.unwrap()).unwrap();
let ghost = RawLRU::with_hasher(es, self.ghost_hasher.unwrap()).unwrap();
Ok(TwoQueueCache {
size,
recent_size: rs,
recent,
frequent: freq,
ghost,
})
}
}
pub struct TwoQueueCache<
K: Hash + Eq,
V,
RH = DefaultHashBuilder,
FH = DefaultHashBuilder,
GH = DefaultHashBuilder,
> {
size: usize,
recent_size: usize,
recent: RawLRU<K, V, DefaultEvictCallback, RH>,
frequent: RawLRU<K, V, DefaultEvictCallback, FH>,
ghost: RawLRU<K, V, DefaultEvictCallback, GH>,
}
impl<K: Hash + Eq, V> TwoQueueCache<K, V> {
pub fn new(size: usize) -> Result<Self, CacheError> {
Self::with_2q_parameters(size, DEFAULT_2Q_RECENT_RATIO, DEFAULT_2Q_GHOST_RATIO)
}
pub fn builder(size: usize) -> TwoQueueCacheBuilder {
TwoQueueCacheBuilder::new(size)
}
pub fn with_recent_ratio(size: usize, rr: f64) -> Result<Self, CacheError> {
Self::with_2q_parameters(size, rr, DEFAULT_2Q_GHOST_RATIO)
}
pub fn with_ghost_ratio(size: usize, gr: f64) -> Result<Self, CacheError> {
Self::with_2q_parameters(size, DEFAULT_2Q_RECENT_RATIO, gr)
}
pub fn with_2q_parameters(size: usize, rr: f64, gr: f64) -> Result<Self, CacheError> {
if size == 0 {
return Err(CacheError::InvalidSize(size));
}
if !(0.0..=1.0).contains(&rr) {
return Err(CacheError::InvalidRecentRatio(rr));
}
if !(0.0..=1.0).contains(&gr) {
return Err(CacheError::InvalidGhostRatio(gr));
}
let rs = floor((size as f64) * rr) as usize;
let es = floor((size as f64) * gr) as usize;
let recent = RawLRU::new(size).unwrap();
let freq = RawLRU::new(size).unwrap();
let ghost = RawLRU::new(es)?;
Ok(Self {
size,
recent_size: rs,
recent,
frequent: freq,
ghost,
})
}
}
impl<K: Hash + Eq, V, RH: BuildHasher, FH: BuildHasher, GH: BuildHasher> Cache<K, V>
for TwoQueueCache<K, V, RH, FH, GH>
{
fn put(&mut self, k: K, mut v: V) -> PutResult<K, V> {
let key_ref = KeyRef { k: &k };
if let Some(ent_ptr) = self.frequent.map.get_mut(&key_ref).map(|node| {
let node_ptr: *mut EntryNode<K, V> = node.as_ptr();
node_ptr
}) {
self.frequent.update(&mut v, ent_ptr);
return PutResult::Update(v);
}
if self
.recent
.remove_and_return_ent(&k)
.map(|mut ent| {
unsafe {
swap_value(&mut v, ent.as_mut());
}
self.frequent.put_nonnull(ent)
})
.is_some()
{
return PutResult::Update(v);
}
let recent_len = self.recent.len();
let freq_len = self.frequent.len();
if self.ghost.contains(&k) {
return if recent_len + freq_len >= self.size {
let ent = if recent_len > self.recent_size {
self.recent.remove_lru_in().unwrap()
} else {
self.frequent.remove_lru_in().unwrap()
};
let rst = self.ghost.put_or_evict_nonnull(ent);
match self.ghost.map.remove(&key_ref) {
None => match rst {
None => PutResult::Put,
Some(mut ent) => {
unsafe {
let ent_ptr = ent.as_mut();
core::ptr::swap(&mut v, ent_ptr.val.as_mut_ptr());
}
self.frequent.put_nonnull(ent);
PutResult::Update(v)
}
},
Some(mut ent) => {
let ent_ptr = ent.as_ptr();
self.ghost.detach(ent_ptr);
unsafe {
let ent_ref = ent.as_mut();
core::ptr::swap(&mut v, ent_ref.val.as_mut_ptr());
self.frequent.put_nonnull(ent);
match rst {
None => PutResult::Update(v),
Some(ent) => {
let ptr = ent.as_ptr();
let ent = *Box::from_raw(ptr);
PutResult::EvictedAndUpdate {
evicted: (ent.key.assume_init(), ent.val.assume_init()),
update: v,
}
}
}
}
}
}
} else {
let mut ent = self.ghost.map.remove(&key_ref).unwrap();
let ent_ptr = ent.as_ptr();
self.ghost.detach(ent_ptr);
unsafe {
core::ptr::swap(&mut v, ent.as_mut().val.as_mut_ptr());
}
self.frequent.put_nonnull(ent);
PutResult::Update(v)
};
}
let bks = unsafe {
core::ptr::NonNull::new_unchecked(Box::into_raw(Box::new(EntryNode::new(k, v))))
};
if freq_len + recent_len < self.size {
return match self.recent.put_or_evict_nonnull(bks) {
None => PutResult::Put,
Some(evicted) => self.ghost.put_nonnull(evicted),
};
}
let ent = if recent_len >= self.recent_size {
self.recent.remove_lru_in().unwrap()
} else {
self.frequent.remove_lru_in().unwrap()
};
self.recent.put_nonnull(bks);
self.ghost.put_nonnull(ent)
}
fn get<Q>(&mut self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.frequent
.get_(k)
.or_else(|| {
self.recent
.peek_(k)
.and_then(|v| self.move_to_frequent(k, v))
})
.map(|v| unsafe { core::mem::transmute(v) })
}
fn get_mut<Q>(&mut self, k: &Q) -> Option<&mut V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.frequent
.get_mut_(k)
.or_else(|| {
self.recent
.peek_mut_(k)
.and_then(|v| self.move_to_frequent(k, v))
})
.map(|v| unsafe { core::mem::transmute(v) })
}
fn peek<Q>(&self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.frequent.peek(k).or_else(|| self.recent.peek(k))
}
fn peek_mut<Q>(&mut self, k: &Q) -> Option<&mut V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
match self.frequent.peek_mut(k) {
Some(v) => Some(v),
None => self.recent.peek_mut(k),
}
}
fn remove<Q>(&mut self, k: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.frequent
.remove(k)
.or_else(|| self.recent.remove(k))
.or_else(|| self.ghost.remove(k))
}
fn contains<Q>(&self, k: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.frequent.contains(k) || self.recent.contains(k)
}
fn purge(&mut self) {
self.frequent.purge();
self.recent.purge();
self.ghost.purge();
}
fn len(&self) -> usize {
self.recent.len() + self.frequent.len()
}
fn cap(&self) -> usize {
self.size
}
fn is_empty(&self) -> bool {
self.frequent.is_empty() && self.recent.is_empty() && self.ghost.is_empty()
}
}
impl<K: Hash + Eq, V, RH: BuildHasher, FH: BuildHasher, GH: BuildHasher>
TwoQueueCache<K, V, RH, FH, GH>
{
pub fn from_builder(builder: TwoQueueCacheBuilder<RH, FH, GH>) -> Result<Self, CacheError> {
builder.finalize()
}
pub fn recent_len(&self) -> usize {
self.recent.len()
}
pub fn frequent_len(&self) -> usize {
self.frequent.len()
}
pub fn ghost_len(&self) -> usize {
self.ghost.len()
}
pub fn recent_keys(&self) -> KeysMRUIter<'_, K, V> {
self.recent.keys()
}
pub fn recent_keys_lru(&self) -> KeysLRUIter<'_, K, V> {
self.recent.keys_lru()
}
pub fn recent_values(&self) -> ValuesMRUIter<'_, K, V> {
self.recent.values()
}
pub fn recent_values_lru(&self) -> ValuesLRUIter<'_, K, V> {
self.recent.values_lru()
}
pub fn recent_values_mut(&mut self) -> ValuesMRUIterMut<'_, K, V> {
self.recent.values_mut()
}
pub fn recent_values_lru_mut(&mut self) -> ValuesLRUIterMut<'_, K, V> {
self.recent.values_lru_mut()
}
pub fn recent_iter(&self) -> MRUIter<'_, K, V> {
self.recent.iter()
}
pub fn recent_iter_lru(&self) -> LRUIter<'_, K, V> {
self.recent.iter_lru()
}
pub fn recent_iter_mut(&mut self) -> MRUIterMut<'_, K, V> {
self.recent.iter_mut()
}
pub fn recent_iter_lru_mut(&mut self) -> LRUIterMut<'_, K, V> {
self.recent.iter_lru_mut()
}
pub fn ghost_keys(&self) -> KeysMRUIter<'_, K, V> {
self.ghost.keys()
}
pub fn ghost_keys_lru(&self) -> KeysLRUIter<'_, K, V> {
self.ghost.keys_lru()
}
pub fn ghost_values(&self) -> ValuesMRUIter<'_, K, V> {
self.ghost.values()
}
pub fn ghost_values_lru(&self) -> ValuesLRUIter<'_, K, V> {
self.ghost.values_lru()
}
pub fn ghost_values_mut(&mut self) -> ValuesMRUIterMut<'_, K, V> {
self.ghost.values_mut()
}
pub fn ghost_values_lru_mut(&mut self) -> ValuesLRUIterMut<'_, K, V> {
self.ghost.values_lru_mut()
}
pub fn ghost_iter(&self) -> MRUIter<'_, K, V> {
self.ghost.iter()
}
pub fn ghost_iter_lru(&self) -> LRUIter<'_, K, V> {
self.ghost.iter_lru()
}
pub fn ghost_iter_mut(&mut self) -> MRUIterMut<'_, K, V> {
self.ghost.iter_mut()
}
pub fn ghost_iter_lru_mut(&mut self) -> LRUIterMut<'_, K, V> {
self.ghost.iter_lru_mut()
}
pub fn frequent_keys(&self) -> KeysMRUIter<'_, K, V> {
self.frequent.keys()
}
pub fn frequent_keys_lru(&self) -> KeysLRUIter<'_, K, V> {
self.frequent.keys_lru()
}
pub fn frequent_values(&self) -> ValuesMRUIter<'_, K, V> {
self.frequent.values()
}
pub fn frequent_values_lru(&self) -> ValuesLRUIter<'_, K, V> {
self.frequent.values_lru()
}
pub fn frequent_values_mut(&mut self) -> ValuesMRUIterMut<'_, K, V> {
self.frequent.values_mut()
}
pub fn frequent_values_lru_mut(&mut self) -> ValuesLRUIterMut<'_, K, V> {
self.frequent.values_lru_mut()
}
pub fn frequent_iter(&self) -> MRUIter<'_, K, V> {
self.frequent.iter()
}
pub fn frequent_iter_lru(&self) -> LRUIter<'_, K, V> {
self.frequent.iter_lru()
}
pub fn frequent_iter_mut(&mut self) -> MRUIterMut<'_, K, V> {
self.frequent.iter_mut()
}
pub fn frequent_iter_lru_mut(&mut self) -> LRUIterMut<'_, K, V> {
self.frequent.iter_lru_mut()
}
fn move_to_frequent<T, Q>(&mut self, k: &Q, v: T) -> Option<T>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
if let Some(ent) = self.recent.remove_and_return_ent(k) {
match self.frequent.put_or_evict_nonnull(ent) {
None => Some(v),
Some(_) => Some(v),
}
} else {
None
}
}
}
impl<K: Hash + Eq, V, RH: BuildHasher, FH: BuildHasher, GH: BuildHasher> fmt::Debug
for TwoQueueCache<K, V, RH, FH, GH>
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("TwoQueueCache")
.field("len", &self.len())
.field("cap", &self.cap())
.finish()
}
}
#[cfg(test)]
mod test {
use crate::lru::two_queue::TwoQueueCache;
use crate::lru::CacheError;
use crate::{Cache, PutResult};
use alloc::vec::Vec;
use rand::seq::SliceRandom;
use rand::{thread_rng, Rng};
use std::format;
#[cfg_attr(miri, ignore)]
#[test]
fn test_2q_cache_random_ops() {
let size = 128;
let mut rng = thread_rng();
let mut cases: Vec<u64> = (0..200_000).collect();
cases.shuffle(&mut rng);
let mut cache = TwoQueueCache::new(size).unwrap();
(0..200_000).for_each(|_i| {
let k = rng.gen::<i64>() % 512;
let r: i64 = rng.gen();
match r % 3 {
0 => {
let _ = cache.put(k, k);
}
1 => {
let _ = cache.get(&k);
}
2 => {
let _ = cache.remove(&k);
}
_ => {}
}
assert!(cache.recent.len() + cache.frequent.len() <= size)
})
}
#[test]
fn test_cache_error() {
let cache = TwoQueueCache::<usize, usize>::with_ghost_ratio(0, 3.0).unwrap_err();
assert_eq!(CacheError::InvalidSize(0), cache);
let cache = TwoQueueCache::<usize, usize>::with_ghost_ratio(3, 3.0).unwrap_err();
assert_eq!(CacheError::InvalidGhostRatio(3.0), cache);
let cache = TwoQueueCache::<usize, usize>::with_recent_ratio(3, 3.0).unwrap_err();
assert_eq!(CacheError::InvalidRecentRatio(3.0), cache);
}
#[test]
fn test_2q_cache_get_recent_to_freq() {
let mut cache = TwoQueueCache::new(128).unwrap();
(0..128).for_each(|i| {
let _ = cache.put(i, i);
});
assert_eq!(cache.recent.len(), 128);
assert_eq!(cache.frequent.len(), 0);
(0..128).for_each(|i| {
let v = cache.get(&i);
assert_ne!(v, None, "missing: {}", i);
});
assert_eq!(cache.recent.len(), 0);
assert_eq!(cache.frequent.len(), 128);
}
#[test]
fn test_2q_cache_put_recent_to_freq() {
let mut cache = TwoQueueCache::new(128).unwrap();
cache.put(1, 1);
assert_eq!(cache.recent.len(), 1);
assert_eq!(cache.frequent.len(), 0);
cache.put(1, 1);
assert_eq!(cache.recent.len(), 0);
assert_eq!(cache.frequent.len(), 1);
cache.put(1, 1);
assert_eq!(cache.recent.len(), 0);
assert_eq!(cache.frequent.len(), 1);
}
#[test]
fn test_2q_cache_put() {
let mut cache = TwoQueueCache::new(4).unwrap();
cache.put(1, 1);
cache.put(2, 2);
cache.put(3, 3);
cache.put(4, 4);
cache.put(5, 5);
assert_eq!(cache.recent.len(), 4);
assert_eq!(cache.ghost.len(), 1,);
assert_eq!(cache.frequent.len(), 0);
assert_eq!(cache.put(1, 1), PutResult::Update(1));
assert_eq!(cache.recent.len(), 3);
assert_eq!(cache.ghost.len(), 1,);
assert_eq!(cache.frequent.len(), 1);
assert_eq!(
format!("{:?}", cache.put(6, 6).clone()),
format!("{:?}", PutResult::<i32, i32>::Put)
);
assert_eq!(cache.recent.len(), 3);
assert_eq!(cache.ghost.len(), 2,);
assert_eq!(cache.frequent.len(), 1);
assert_eq!(cache.put(7, 7), PutResult::Evicted { key: 2, value: 2 });
assert_eq!(cache.recent.len(), 3);
assert_eq!(cache.ghost.len(), 2,);
assert_eq!(cache.frequent.len(), 1);
assert_eq!(
format!("{:?}", cache.put(2, 11).clone()),
format!("{:?}", PutResult::Evicted { key: 3, value: 3 })
);
assert_eq!(cache.recent.len(), 3);
assert_eq!(cache.ghost.len(), 2,);
assert_eq!(cache.frequent.len(), 1);
assert_eq!(cache.put(4, 11), PutResult::Update(4));
assert_eq!(cache.recent.len(), 2);
assert_eq!(cache.ghost.len(), 2,);
assert_eq!(cache.frequent.len(), 2);
assert_eq!(cache.put(2, 22).clone(), PutResult::Update(11));
assert_eq!(cache.recent.len(), 1);
assert_eq!(cache.ghost.len(), 2,);
assert_eq!(cache.frequent.len(), 3);
assert_eq!(
format!("{:?}", cache.put(7, 77)),
format!("{:?}", PutResult::<i32, i32>::Update(7))
);
assert_eq!(cache.recent.len(), 0);
assert_eq!(cache.ghost.len(), 2);
assert_eq!(cache.frequent.len(), 4);
assert_eq!(
format!("{:?}", cache.put(6, 66).clone()),
format!(
"{:?}",
PutResult::EvictedAndUpdate {
evicted: (5, 5),
update: 6
}
)
);
assert_eq!(cache.recent.len(), 0);
assert_eq!(cache.ghost.len(), 1);
assert_eq!(cache.frequent.len(), 4);
}
#[test]
fn test_2q_cache() {
let mut cache = TwoQueueCache::new(128).unwrap();
(0..256u64).for_each(|i| {
cache.put(i, i);
});
assert_eq!(cache.len(), 128);
let rst = cache
.frequent
.keys_lru()
.chain(cache.recent.keys_lru())
.enumerate()
.map(|(idx, k)| (idx as u64, *k))
.collect::<Vec<(u64, u64)>>();
rst.into_iter().for_each(|(idx, k)| match cache.get(&k) {
None => panic!("bad: {}", k),
Some(val) => assert_eq!(*val, idx + 128),
});
(0..128).for_each(|k| {
assert_eq!(cache.get(&k), None);
});
(128..256).for_each(|k| {
assert_ne!(cache.get(&k), None);
});
(128..192).for_each(|k| {
cache.remove(&k);
assert_eq!(cache.get(&k), None);
});
cache.purge();
assert_eq!(cache.len(), 0);
assert_eq!(cache.get(&200), None);
}
#[test]
fn test_2q_cache_contains() {
let mut cache = TwoQueueCache::new(2).unwrap();
cache.put(1, 1);
cache.put(2, 2);
assert!(cache.contains(&1));
cache.put(3, 3);
assert!(
!cache.contains(&1),
"should be in ghost LRU, and the elements in the ghost is not counted as in cache"
);
}
#[test]
fn test_2q_cache_peek() {
let mut cache = TwoQueueCache::new(2).unwrap();
cache.put(1, 1);
cache.put(2, 2);
assert_eq!(cache.peek(&1), Some(&1));
cache.put(3, 3);
assert!(!cache.contains(&1));
}
}