use crate::ds::GhostList;
#[cfg(feature = "metrics")]
use crate::metrics::metrics_impl::ArcMetrics;
#[cfg(feature = "metrics")]
use crate::metrics::snapshot::ArcMetricsSnapshot;
#[cfg(feature = "metrics")]
use crate::metrics::traits::{ArcMetricsRecorder, CoreMetricsRecorder, MetricsSnapshotProvider};
use crate::prelude::ReadOnlyCache;
use crate::traits::{CoreCache, MutableCache};
use rustc_hash::FxHashMap;
use std::hash::Hash;
use std::iter::FusedIterator;
use std::marker::PhantomData;
use std::ptr::NonNull;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum ListKind {
T1,
T2,
}
#[repr(C)]
struct Node<K, V> {
prev: Option<NonNull<Node<K, V>>>,
next: Option<NonNull<Node<K, V>>>,
list: ListKind,
key: K,
value: V,
}
pub struct ArcCore<K, V> {
map: FxHashMap<K, NonNull<Node<K, V>>>,
t1_head: Option<NonNull<Node<K, V>>>,
t1_tail: Option<NonNull<Node<K, V>>>,
t1_len: usize,
t2_head: Option<NonNull<Node<K, V>>>,
t2_tail: Option<NonNull<Node<K, V>>>,
t2_len: usize,
b1: GhostList<K>,
b2: GhostList<K>,
p: usize,
capacity: usize,
#[cfg(feature = "metrics")]
metrics: ArcMetrics,
}
unsafe impl<K, V> Send for ArcCore<K, V>
where
K: Send,
V: Send,
{
}
unsafe impl<K, V> Sync for ArcCore<K, V>
where
K: Sync,
V: Sync,
{
}
impl<K, V> ArcCore<K, V> {
fn drop_all_nodes(&mut self) {
let mut current = self.t1_head;
while let Some(node_ptr) = current {
unsafe {
current = node_ptr.as_ref().next;
let _ = Box::from_raw(node_ptr.as_ptr());
}
}
let mut current = self.t2_head;
while let Some(node_ptr) = current {
unsafe {
current = node_ptr.as_ref().next;
let _ = Box::from_raw(node_ptr.as_ptr());
}
}
}
pub fn iter(&self) -> Iter<'_, K, V> {
Iter {
current: self.t1_head,
t2_head: self.t2_head,
in_t2: false,
remaining: self.t1_len + self.t2_len,
_marker: PhantomData,
}
}
pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
IterMut {
current: self.t1_head,
t2_head: self.t2_head,
in_t2: false,
remaining: self.t1_len + self.t2_len,
_marker: PhantomData,
}
}
}
impl<K, V> ArcCore<K, V>
where
K: Clone + Eq + Hash,
{
#[inline]
pub fn new(capacity: usize) -> Self {
Self {
map: FxHashMap::with_capacity_and_hasher(capacity, Default::default()),
t1_head: None,
t1_tail: None,
t1_len: 0,
t2_head: None,
t2_tail: None,
t2_len: 0,
b1: GhostList::new(capacity),
b2: GhostList::new(capacity),
p: capacity / 2,
capacity,
#[cfg(feature = "metrics")]
metrics: ArcMetrics::default(),
}
}
#[inline(always)]
fn detach(&mut self, node_ptr: NonNull<Node<K, V>>) {
unsafe {
let node = node_ptr.as_ref();
let prev = node.prev;
let next = node.next;
let list = node.list;
let (head, tail, len) = match list {
ListKind::T1 => (&mut self.t1_head, &mut self.t1_tail, &mut self.t1_len),
ListKind::T2 => (&mut self.t2_head, &mut self.t2_tail, &mut self.t2_len),
};
match prev {
Some(mut p) => p.as_mut().next = next,
None => *head = next,
}
match next {
Some(mut n) => n.as_mut().prev = prev,
None => *tail = prev,
}
*len -= 1;
}
}
#[inline(always)]
fn attach_t1_head(&mut self, mut node_ptr: NonNull<Node<K, V>>) {
unsafe {
let node = node_ptr.as_mut();
node.prev = None;
node.next = self.t1_head;
node.list = ListKind::T1;
match self.t1_head {
Some(mut h) => h.as_mut().prev = Some(node_ptr),
None => self.t1_tail = Some(node_ptr),
}
self.t1_head = Some(node_ptr);
self.t1_len += 1;
}
}
#[inline(always)]
fn attach_t2_head(&mut self, mut node_ptr: NonNull<Node<K, V>>) {
unsafe {
let node = node_ptr.as_mut();
node.prev = None;
node.next = self.t2_head;
node.list = ListKind::T2;
match self.t2_head {
Some(mut h) => h.as_mut().prev = Some(node_ptr),
None => self.t2_tail = Some(node_ptr),
}
self.t2_head = Some(node_ptr);
self.t2_len += 1;
}
}
fn replace(&mut self, in_b2: bool) {
#[cfg(feature = "metrics")]
self.metrics.record_evict_call();
let evict_from_t1 =
if self.t1_len > 0 && (self.t1_len > self.p || (self.t1_len == self.p && in_b2)) {
true
} else if self.t2_len > 0 {
false
} else {
self.t1_len > 0
};
if evict_from_t1 {
if let Some(victim_ptr) = self.t1_tail {
unsafe {
let victim = victim_ptr.as_ref();
let key = victim.key.clone();
self.detach(victim_ptr);
self.map.remove(&key);
self.b1.record(key.clone());
let _ = Box::from_raw(victim_ptr.as_ptr());
#[cfg(feature = "metrics")]
{
self.metrics.record_evicted_entry();
self.metrics.record_t1_eviction();
}
}
}
} else if let Some(victim_ptr) = self.t2_tail {
unsafe {
let victim = victim_ptr.as_ref();
let key = victim.key.clone();
self.detach(victim_ptr);
self.map.remove(&key);
self.b2.record(key.clone());
let _ = Box::from_raw(victim_ptr.as_ptr());
#[cfg(feature = "metrics")]
{
self.metrics.record_evicted_entry();
self.metrics.record_t2_eviction();
}
}
}
}
fn adapt(&mut self, in_b1: bool, in_b2: bool) {
if in_b1 {
let delta = if self.b2.len() >= self.b1.len() {
1
} else if !self.b1.is_empty() {
((self.b2.len() as f64 / self.b1.len() as f64).ceil() as usize).max(1)
} else {
1
};
self.p = (self.p + delta).min(self.capacity);
#[cfg(feature = "metrics")]
self.metrics.record_p_increase();
} else if in_b2 {
let delta = if self.b1.len() >= self.b2.len() {
1
} else if !self.b2.is_empty() {
((self.b1.len() as f64 / self.b2.len() as f64).ceil() as usize).max(1)
} else {
1
};
self.p = self.p.saturating_sub(delta);
#[cfg(feature = "metrics")]
self.metrics.record_p_decrease();
}
}
pub fn p_value(&self) -> usize {
self.p
}
pub fn t1_len(&self) -> usize {
self.t1_len
}
pub fn t2_len(&self) -> usize {
self.t2_len
}
pub fn b1_len(&self) -> usize {
self.b1.len()
}
pub fn b2_len(&self) -> usize {
self.b2.len()
}
#[cfg(any(test, debug_assertions))]
pub fn debug_validate_invariants(&self)
where
K: std::fmt::Debug,
{
assert_eq!(
self.len(),
self.t1_len + self.t2_len,
"len() should equal t1_len + t2_len"
);
assert_eq!(
self.map.len(),
self.t1_len + self.t2_len,
"map.len() should equal total entries"
);
assert!(
self.t1_len + self.t2_len <= self.capacity,
"total entries ({}) exceed capacity ({})",
self.t1_len + self.t2_len,
self.capacity
);
assert!(
self.p <= self.capacity,
"p ({}) exceeds capacity ({})",
self.p,
self.capacity
);
assert!(
self.b1.len() <= self.capacity,
"B1 length ({}) exceeds capacity ({})",
self.b1.len(),
self.capacity
);
assert!(
self.b2.len() <= self.capacity,
"B2 length ({}) exceeds capacity ({})",
self.b2.len(),
self.capacity
);
let mut t1_count = 0;
let mut current = self.t1_head;
let mut visited_t1 = std::collections::HashSet::new();
while let Some(node_ptr) = current {
unsafe {
let node = node_ptr.as_ref();
assert!(visited_t1.insert(node_ptr), "Cycle detected in T1 list");
assert_eq!(node.list, ListKind::T1, "Node in T1 has wrong list kind");
assert!(self.map.contains_key(&node.key), "T1 node key not in map");
t1_count += 1;
current = node.next;
}
}
assert_eq!(
t1_count, self.t1_len,
"T1 actual count doesn't match t1_len"
);
let mut t2_count = 0;
let mut current = self.t2_head;
let mut visited_t2 = std::collections::HashSet::new();
while let Some(node_ptr) = current {
unsafe {
let node = node_ptr.as_ref();
assert!(visited_t2.insert(node_ptr), "Cycle detected in T2 list");
assert_eq!(node.list, ListKind::T2, "Node in T2 has wrong list kind");
assert!(self.map.contains_key(&node.key), "T2 node key not in map");
t2_count += 1;
current = node.next;
}
}
assert_eq!(
t2_count, self.t2_len,
"T2 actual count doesn't match t2_len"
);
for t1_ptr in &visited_t1 {
assert!(
!visited_t2.contains(t1_ptr),
"Node appears in both T1 and T2"
);
}
assert_eq!(
visited_t1.len() + visited_t2.len(),
self.map.len(),
"Map contains entries not in T1 or T2"
);
for key in self.map.keys() {
assert!(
!self.b1.contains(key),
"Key {:?} is in both cache and B1",
key
);
assert!(
!self.b2.contains(key),
"Key {:?} is in both cache and B2",
key
);
}
}
}
impl<K, V> std::fmt::Debug for ArcCore<K, V>
where
K: Clone + Eq + Hash + std::fmt::Debug,
V: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ArcCore")
.field("capacity", &self.capacity)
.field("t1_len", &self.t1_len)
.field("t2_len", &self.t2_len)
.field("b1_len", &self.b1.len())
.field("b2_len", &self.b2.len())
.field("p", &self.p)
.field("total_len", &(self.t1_len + self.t2_len))
.finish()
}
}
impl<K, V> ReadOnlyCache<K, V> for ArcCore<K, V>
where
K: Clone + Eq + Hash,
{
fn contains(&self, key: &K) -> bool {
self.map.contains_key(key)
}
fn len(&self) -> usize {
self.t1_len + self.t2_len
}
fn capacity(&self) -> usize {
self.capacity
}
}
impl<K, V> CoreCache<K, V> for ArcCore<K, V>
where
K: Clone + Eq + Hash,
{
fn get(&mut self, key: &K) -> Option<&V> {
let node_ptr = match self.map.get(key) {
Some(&ptr) => ptr,
None => {
#[cfg(feature = "metrics")]
self.metrics.record_get_miss();
return None;
},
};
#[cfg(feature = "metrics")]
self.metrics.record_get_hit();
unsafe {
let node = node_ptr.as_ref();
match node.list {
ListKind::T1 => {
#[cfg(feature = "metrics")]
self.metrics.record_t1_to_t2_promotion();
self.detach(node_ptr);
self.attach_t2_head(node_ptr);
},
ListKind::T2 => {
self.detach(node_ptr);
self.attach_t2_head(node_ptr);
},
}
Some(&node_ptr.as_ref().value)
}
}
fn insert(&mut self, key: K, value: V) -> Option<V> {
#[cfg(feature = "metrics")]
self.metrics.record_insert_call();
if self.capacity == 0 {
return None;
}
if let Some(&node_ptr) = self.map.get(&key) {
#[cfg(feature = "metrics")]
self.metrics.record_insert_update();
unsafe {
let mut node_ptr = node_ptr;
let node = node_ptr.as_mut();
let old_value = std::mem::replace(&mut node.value, value);
match node.list {
ListKind::T1 => {
self.detach(node_ptr);
self.attach_t2_head(node_ptr);
},
ListKind::T2 => {
self.detach(node_ptr);
self.attach_t2_head(node_ptr);
},
}
return Some(old_value);
}
}
let in_b1 = self.b1.contains(&key);
let in_b2 = self.b2.contains(&key);
if in_b1 {
#[cfg(feature = "metrics")]
{
self.metrics.record_b1_ghost_hit();
self.metrics.record_insert_new();
}
self.adapt(true, false);
self.b1.remove(&key);
if self.t1_len + self.t2_len >= self.capacity {
self.replace(false);
}
let node = Box::new(Node {
prev: None,
next: None,
list: ListKind::T2,
key: key.clone(),
value,
});
let node_ptr = NonNull::new(Box::into_raw(node)).unwrap();
self.map.insert(key, node_ptr);
self.attach_t2_head(node_ptr);
return None;
}
if in_b2 {
#[cfg(feature = "metrics")]
{
self.metrics.record_b2_ghost_hit();
self.metrics.record_insert_new();
}
self.adapt(false, true);
self.b2.remove(&key);
if self.t1_len + self.t2_len >= self.capacity {
self.replace(true);
}
let node = Box::new(Node {
prev: None,
next: None,
list: ListKind::T2,
key: key.clone(),
value,
});
let node_ptr = NonNull::new(Box::into_raw(node)).unwrap();
self.map.insert(key, node_ptr);
self.attach_t2_head(node_ptr);
return None;
}
#[cfg(feature = "metrics")]
self.metrics.record_insert_new();
let l1_len = self.t1_len + self.b1.len();
if l1_len >= self.capacity {
if !self.b1.is_empty() {
self.b1.evict_lru();
}
if self.t1_len + self.t2_len >= self.capacity {
self.replace(false);
}
} else {
let total = self.t1_len + self.t2_len + self.b1.len() + self.b2.len();
if total >= 2 * self.capacity {
self.b2.evict_lru();
}
if self.t1_len + self.t2_len >= self.capacity {
self.replace(false);
}
}
let node = Box::new(Node {
prev: None,
next: None,
list: ListKind::T1,
key: key.clone(),
value,
});
let node_ptr = NonNull::new(Box::into_raw(node)).unwrap();
self.map.insert(key, node_ptr);
self.attach_t1_head(node_ptr);
None
}
fn clear(&mut self) {
#[cfg(feature = "metrics")]
self.metrics.record_clear();
self.drop_all_nodes();
self.map.clear();
self.t1_head = None;
self.t1_tail = None;
self.t1_len = 0;
self.t2_head = None;
self.t2_tail = None;
self.t2_len = 0;
self.b1.clear();
self.b2.clear();
self.p = self.capacity / 2;
}
}
impl<K, V> MutableCache<K, V> for ArcCore<K, V>
where
K: Clone + Eq + Hash,
{
fn remove(&mut self, key: &K) -> Option<V> {
let node_ptr = self.map.remove(key)?;
self.detach(node_ptr);
unsafe {
let node = Box::from_raw(node_ptr.as_ptr());
Some(node.value)
}
}
}
impl<K, V> Drop for ArcCore<K, V> {
fn drop(&mut self) {
self.drop_all_nodes();
}
}
impl<K, V> Clone for ArcCore<K, V>
where
K: Clone + Eq + Hash,
V: Clone,
{
fn clone(&self) -> Self {
let mut new_cache = ArcCore::new(self.capacity);
new_cache.p = self.p;
new_cache.b1 = self.b1.clone();
new_cache.b2 = self.b2.clone();
let mut t1_entries = Vec::with_capacity(self.t1_len);
let mut current = self.t1_tail;
while let Some(ptr) = current {
unsafe {
let node = ptr.as_ref();
t1_entries.push((node.key.clone(), node.value.clone()));
current = node.prev;
}
}
for (key, value) in t1_entries {
let node = Box::new(Node {
prev: None,
next: None,
list: ListKind::T1,
key: key.clone(),
value,
});
let ptr = NonNull::new(Box::into_raw(node)).unwrap();
new_cache.map.insert(key, ptr);
new_cache.attach_t1_head(ptr);
}
let mut t2_entries = Vec::with_capacity(self.t2_len);
let mut current = self.t2_tail;
while let Some(ptr) = current {
unsafe {
let node = ptr.as_ref();
t2_entries.push((node.key.clone(), node.value.clone()));
current = node.prev;
}
}
for (key, value) in t2_entries {
let node = Box::new(Node {
prev: None,
next: None,
list: ListKind::T2,
key: key.clone(),
value,
});
let ptr = NonNull::new(Box::into_raw(node)).unwrap();
new_cache.map.insert(key, ptr);
new_cache.attach_t2_head(ptr);
}
#[cfg(feature = "metrics")]
{
new_cache.metrics = self.metrics;
}
new_cache
}
}
impl<K, V> Default for ArcCore<K, V>
where
K: Clone + Eq + Hash,
{
fn default() -> Self {
Self::new(0)
}
}
#[cfg(feature = "metrics")]
impl<K, V> ArcCore<K, V>
where
K: Clone + Eq + Hash,
{
pub fn metrics_snapshot(&self) -> ArcMetricsSnapshot {
ArcMetricsSnapshot {
get_calls: self.metrics.get_calls,
get_hits: self.metrics.get_hits,
get_misses: self.metrics.get_misses,
insert_calls: self.metrics.insert_calls,
insert_updates: self.metrics.insert_updates,
insert_new: self.metrics.insert_new,
evict_calls: self.metrics.evict_calls,
evicted_entries: self.metrics.evicted_entries,
t1_to_t2_promotions: self.metrics.t1_to_t2_promotions,
b1_ghost_hits: self.metrics.b1_ghost_hits,
b2_ghost_hits: self.metrics.b2_ghost_hits,
p_increases: self.metrics.p_increases,
p_decreases: self.metrics.p_decreases,
t1_evictions: self.metrics.t1_evictions,
t2_evictions: self.metrics.t2_evictions,
cache_len: self.len(),
capacity: self.capacity,
}
}
}
#[cfg(feature = "metrics")]
impl<K, V> MetricsSnapshotProvider<ArcMetricsSnapshot> for ArcCore<K, V>
where
K: Clone + Eq + Hash,
{
fn snapshot(&self) -> ArcMetricsSnapshot {
self.metrics_snapshot()
}
}
pub struct Iter<'a, K, V> {
current: Option<NonNull<Node<K, V>>>,
t2_head: Option<NonNull<Node<K, V>>>,
in_t2: bool,
remaining: usize,
_marker: PhantomData<&'a (K, V)>,
}
impl<'a, K, V> Iterator for Iter<'a, K, V> {
type Item = (&'a K, &'a V);
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(node_ptr) = self.current {
unsafe {
let node = node_ptr.as_ref();
self.current = node.next;
self.remaining -= 1;
return Some((&node.key, &node.value));
}
} else if !self.in_t2 {
self.in_t2 = true;
self.current = self.t2_head;
} else {
return None;
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl<K, V> ExactSizeIterator for Iter<'_, K, V> {}
impl<K, V> FusedIterator for Iter<'_, K, V> {}
pub struct IterMut<'a, K, V> {
current: Option<NonNull<Node<K, V>>>,
t2_head: Option<NonNull<Node<K, V>>>,
in_t2: bool,
remaining: usize,
_marker: PhantomData<&'a mut (K, V)>,
}
impl<'a, K, V> Iterator for IterMut<'a, K, V> {
type Item = (&'a K, &'a mut V);
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(mut node_ptr) = self.current {
unsafe {
let node = node_ptr.as_mut();
self.current = node.next;
self.remaining -= 1;
return Some((&node.key, &mut node.value));
}
} else if !self.in_t2 {
self.in_t2 = true;
self.current = self.t2_head;
} else {
return None;
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl<K, V> ExactSizeIterator for IterMut<'_, K, V> {}
impl<K, V> FusedIterator for IterMut<'_, K, V> {}
pub struct IntoIter<K, V> {
current: Option<NonNull<Node<K, V>>>,
t2_head: Option<NonNull<Node<K, V>>>,
in_t2: bool,
remaining: usize,
}
impl<K, V> Iterator for IntoIter<K, V> {
type Item = (K, V);
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(node_ptr) = self.current {
unsafe {
let node = Box::from_raw(node_ptr.as_ptr());
self.current = node.next;
self.remaining -= 1;
return Some((node.key, node.value));
}
} else if !self.in_t2 {
self.in_t2 = true;
self.current = self.t2_head;
} else {
return None;
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl<K, V> ExactSizeIterator for IntoIter<K, V> {}
impl<K, V> FusedIterator for IntoIter<K, V> {}
impl<K, V> Drop for IntoIter<K, V> {
fn drop(&mut self) {
while self.next().is_some() {}
}
}
unsafe impl<K: Send, V: Send> Send for IntoIter<K, V> {}
unsafe impl<K: Sync, V: Sync> Sync for IntoIter<K, V> {}
impl<K, V> IntoIterator for ArcCore<K, V> {
type Item = (K, V);
type IntoIter = IntoIter<K, V>;
fn into_iter(mut self) -> IntoIter<K, V> {
let iter = IntoIter {
current: self.t1_head,
t2_head: self.t2_head,
in_t2: false,
remaining: self.t1_len + self.t2_len,
};
self.t1_head = None;
self.t1_tail = None;
self.t1_len = 0;
self.t2_head = None;
self.t2_tail = None;
self.t2_len = 0;
iter
}
}
impl<'a, K, V> IntoIterator for &'a ArcCore<K, V> {
type Item = (&'a K, &'a V);
type IntoIter = Iter<'a, K, V>;
fn into_iter(self) -> Iter<'a, K, V> {
self.iter()
}
}
impl<'a, K, V> IntoIterator for &'a mut ArcCore<K, V> {
type Item = (&'a K, &'a mut V);
type IntoIter = IterMut<'a, K, V>;
fn into_iter(self) -> IterMut<'a, K, V> {
self.iter_mut()
}
}
impl<K, V> FromIterator<(K, V)> for ArcCore<K, V>
where
K: Clone + Eq + Hash,
{
fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
let mut cache = ArcCore::new(lower);
cache.extend(iter);
cache
}
}
impl<K, V> Extend<(K, V)> for ArcCore<K, V>
where
K: Clone + Eq + Hash,
{
fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
for (key, value) in iter {
self.insert(key, value);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn arc_new_cache() {
let cache: ArcCore<String, i32> = ArcCore::new(100);
assert_eq!(cache.capacity(), 100);
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
assert_eq!(cache.t1_len(), 0);
assert_eq!(cache.t2_len(), 0);
assert_eq!(cache.b1_len(), 0);
assert_eq!(cache.b2_len(), 0);
assert_eq!(cache.p_value(), 50); }
#[test]
fn arc_insert_and_get() {
let mut cache = ArcCore::new(10);
cache.insert("key1", "value1");
assert_eq!(cache.len(), 1);
assert_eq!(cache.t1_len(), 1);
assert_eq!(cache.t2_len(), 0);
assert_eq!(cache.get(&"key1"), Some(&"value1"));
assert_eq!(cache.t1_len(), 0);
assert_eq!(cache.t2_len(), 1);
assert_eq!(cache.get(&"key1"), Some(&"value1"));
assert_eq!(cache.t1_len(), 0);
assert_eq!(cache.t2_len(), 1);
}
#[test]
fn arc_update_existing() {
let mut cache = ArcCore::new(10);
cache.insert("key1", "value1");
assert_eq!(cache.t1_len(), 1);
let old = cache.insert("key1", "new_value");
assert_eq!(old, Some("value1"));
assert_eq!(cache.len(), 1);
assert_eq!(cache.t1_len(), 0);
assert_eq!(cache.t2_len(), 1);
assert_eq!(cache.get(&"key1"), Some(&"new_value"));
}
#[test]
fn arc_eviction_fills_ghost_lists() {
let mut cache = ArcCore::new(2);
cache.insert("a", 1);
cache.insert("b", 2);
assert_eq!(cache.len(), 2);
assert_eq!(cache.t1_len(), 2);
cache.insert("c", 3);
assert_eq!(cache.len(), 2);
assert_eq!(cache.t1_len(), 2); assert_eq!(cache.b1_len(), 1); assert!(!cache.contains(&"a"));
}
#[test]
fn arc_ghost_hit_promotes_to_t2() {
let mut cache = ArcCore::new(2);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
cache.debug_validate_invariants();
assert!(!cache.contains(&"a"));
assert_eq!(cache.b1_len(), 1);
assert!(cache.b1.contains(&"a"));
cache.insert("a", 10);
cache.debug_validate_invariants();
println!(
"After ghost hit: len={}, t1={}, t2={}, b1={}, b2={}",
cache.len(),
cache.t1_len(),
cache.t2_len(),
cache.b1_len(),
cache.b2_len()
);
assert_eq!(
cache.len(),
2,
"Cache length should be 2, got {}",
cache.len()
);
assert_eq!(cache.t2_len(), 1); assert!(!cache.b1.contains(&"a")); }
#[test]
fn arc_adaptation_increases_p() {
let mut cache = ArcCore::new(4);
let initial_p = cache.p_value();
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
cache.insert("d", 4);
cache.insert("e", 5);
println!(
"Before ghost hit: p={}, t1={}, t2={}, b1={}, b2={}",
cache.p_value(),
cache.t1_len(),
cache.t2_len(),
cache.b1_len(),
cache.b2_len()
);
println!("B1 contains a={}", cache.b1.contains(&"a"));
cache.insert("a", 10);
println!(
"After ghost hit: p={}, t1={}, t2={}, b1={}, b2={}",
cache.p_value(),
cache.t1_len(),
cache.t2_len(),
cache.b1_len(),
cache.b2_len()
);
assert!(
cache.p_value() > initial_p,
"Expected p to increase from {} but got {}",
initial_p,
cache.p_value()
);
}
#[test]
fn arc_remove() {
let mut cache = ArcCore::new(10);
cache.insert("key1", "value1");
cache.insert("key2", "value2");
assert_eq!(cache.len(), 2);
let removed = cache.remove(&"key1");
assert_eq!(removed, Some("value1"));
assert_eq!(cache.len(), 1);
assert!(!cache.contains(&"key1"));
assert!(cache.contains(&"key2"));
}
#[test]
fn arc_clear() {
let mut cache = ArcCore::new(10);
cache.insert("key1", "value1");
cache.insert("key2", "value2");
cache.get(&"key1");
cache.clear();
assert_eq!(cache.len(), 0);
assert_eq!(cache.t1_len(), 0);
assert_eq!(cache.t2_len(), 0);
assert_eq!(cache.b1_len(), 0);
assert_eq!(cache.b2_len(), 0);
assert!(cache.is_empty());
}
#[test]
fn arc_contains() {
let mut cache = ArcCore::new(10);
assert!(!cache.contains(&"key1"));
cache.insert("key1", "value1");
assert!(cache.contains(&"key1"));
cache.remove(&"key1");
assert!(!cache.contains(&"key1"));
}
#[test]
fn arc_promotion_t1_to_t2() {
let mut cache = ArcCore::new(10);
cache.insert("key", "value");
assert_eq!(cache.t1_len(), 1);
assert_eq!(cache.t2_len(), 0);
cache.get(&"key");
assert_eq!(cache.t1_len(), 0);
assert_eq!(cache.t2_len(), 1);
cache.get(&"key");
assert_eq!(cache.t1_len(), 0);
assert_eq!(cache.t2_len(), 1);
}
#[test]
fn arc_multiple_entries() {
let mut cache = ArcCore::new(5);
for i in 0..5 {
cache.insert(i, i * 10);
}
assert_eq!(cache.len(), 5);
for i in 0..5 {
assert_eq!(cache.get(&i), Some(&(i * 10)));
}
assert_eq!(cache.t2_len(), 5);
assert_eq!(cache.t1_len(), 0);
}
#[test]
fn arc_eviction_and_ghost_tracking() {
let mut cache = ArcCore::new(3);
cache.insert(1, 100);
cache.insert(2, 200);
cache.insert(3, 300);
cache.get(&1);
cache.get(&2);
assert_eq!(cache.t1_len(), 1); assert_eq!(cache.t2_len(), 2);
cache.insert(4, 400);
assert_eq!(cache.len(), 3);
assert!(!cache.contains(&1)); assert!(cache.contains(&2)); assert!(cache.contains(&3)); assert!(cache.contains(&4)); assert_eq!(cache.b2_len(), 1); }
#[test]
fn arc_zero_capacity() {
let mut cache = ArcCore::new(0);
assert_eq!(cache.capacity(), 0);
assert_eq!(cache.len(), 0);
cache.insert("key", "value");
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&"key"));
}
#[test]
fn ghost_directory_size_within_two_times_capacity() {
let c = 5usize;
let mut cache: ArcCore<u64, u64> = ArcCore::new(c);
for i in 0..c as u64 {
cache.insert(i, i);
}
for i in 0..c as u64 {
cache.get(&i);
}
for i in c as u64..2 * c as u64 {
cache.insert(i, i);
}
for i in 2 * c as u64..3 * c as u64 {
cache.insert(i, i);
}
for i in 2 * c as u64..3 * c as u64 {
cache.get(&i);
}
for i in 3 * c as u64..8 * c as u64 {
cache.insert(i, i);
}
let t = cache.t1_len() + cache.t2_len();
let b = cache.b1_len() + cache.b2_len();
let total = t + b;
assert!(
total <= 2 * c,
"ARC directory size ({}) exceeds 2*capacity ({}). \
B1={}, B2={}, T1={}, T2={}",
total,
2 * c,
cache.b1_len(),
cache.b2_len(),
cache.t1_len(),
cache.t2_len(),
);
}
#[test]
fn ghost_lists_bounded_when_cache_full() {
let c = 10usize;
let mut cache: ArcCore<u64, u64> = ArcCore::new(c);
for i in 0..500u64 {
cache.insert(i, i);
}
let t = cache.t1_len() + cache.t2_len();
let b1 = cache.b1_len();
let b2 = cache.b2_len();
assert!(
b1 + b2 <= c,
"Ghost lists hold {} entries (B1={}, B2={}) while cache holds {} (T1+T2). \
Paper requires B1+B2 <= {}",
b1 + b2,
b1,
b2,
t,
c,
);
}
}