use rustc_hash::FxHashMap;
use std::hash::Hash;
use std::mem;
use std::ptr::NonNull;
#[cfg(feature = "metrics")]
use crate::metrics::metrics_impl::LruMetrics;
#[cfg(feature = "metrics")]
use crate::metrics::snapshot::LruMetricsSnapshot;
#[cfg(feature = "metrics")]
use crate::metrics::traits::{
CoreMetricsRecorder, LruMetricsReadRecorder, LruMetricsRecorder, MetricsSnapshotProvider,
};
pub struct FastLru<K, V> {
map: FxHashMap<K, NonNull<Node<K, V>>>,
head: Option<NonNull<Node<K, V>>>,
tail: Option<NonNull<Node<K, V>>>,
capacity: usize,
#[cfg(feature = "metrics")]
metrics: LruMetrics,
}
#[repr(C)]
struct Node<K, V> {
prev: Option<NonNull<Node<K, V>>>,
next: Option<NonNull<Node<K, V>>>,
key: K,
value: V,
}
impl<K, V> FastLru<K, V>
where
K: Eq + Hash + Clone,
{
#[inline]
pub fn new(capacity: usize) -> Self {
Self {
map: FxHashMap::with_capacity_and_hasher(capacity, Default::default()),
head: None,
tail: None,
capacity,
#[cfg(feature = "metrics")]
metrics: LruMetrics::default(),
}
}
#[inline]
pub fn len(&self) -> usize {
self.map.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}
#[inline]
pub fn contains(&self, key: &K) -> bool {
self.map.contains_key(key)
}
#[inline(always)]
pub 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();
self.detach(node_ptr);
self.attach_front(node_ptr);
Some(unsafe { &(*node_ptr.as_ptr()).value })
}
#[inline(always)]
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
let node_ptr = *self.map.get(key)?;
self.detach(node_ptr);
self.attach_front(node_ptr);
Some(unsafe { &mut (*node_ptr.as_ptr()).value })
}
#[inline(always)]
pub fn peek(&self, key: &K) -> Option<&V> {
self.map
.get(key)
.map(|node_ptr| unsafe { &(*node_ptr.as_ptr()).value })
}
#[inline(always)]
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
#[cfg(feature = "metrics")]
self.metrics.record_insert_call();
if let Some(&node_ptr) = self.map.get(&key) {
#[cfg(feature = "metrics")]
self.metrics.record_insert_update();
let old_value = unsafe {
let node = node_ptr.as_ptr();
mem::replace(&mut (*node).value, value)
};
self.detach(node_ptr);
self.attach_front(node_ptr);
return Some(old_value);
}
if self.capacity > 0 && self.map.len() >= self.capacity {
#[cfg(feature = "metrics")]
self.metrics.record_evict_call();
if self.pop_lru().is_some() {
#[cfg(feature = "metrics")]
self.metrics.record_evicted_entry();
}
}
if self.capacity == 0 {
return None;
}
#[cfg(feature = "metrics")]
self.metrics.record_insert_new();
let node = Box::new(Node {
prev: None,
next: None,
key: key.clone(),
value,
});
let node_ptr = NonNull::new(Box::into_raw(node)).unwrap();
self.map.insert(key, node_ptr);
self.attach_front(node_ptr);
None
}
pub fn remove(&mut self, key: &K) -> Option<V> {
let node_ptr = self.map.remove(key)?;
self.detach(node_ptr);
let node = unsafe { Box::from_raw(node_ptr.as_ptr()) };
Some(node.value)
}
pub fn pop_lru(&mut self) -> Option<(K, V)> {
#[cfg(feature = "metrics")]
self.metrics.record_pop_lru_call();
let tail_ptr = self.tail?;
let key = unsafe { (*tail_ptr.as_ptr()).key.clone() };
self.map.remove(&key);
self.detach(tail_ptr);
let node = unsafe { Box::from_raw(tail_ptr.as_ptr()) };
#[cfg(feature = "metrics")]
self.metrics.record_pop_lru_found();
Some((node.key, node.value))
}
pub fn peek_lru(&self) -> Option<(&K, &V)> {
#[cfg(feature = "metrics")]
(&self.metrics).record_peek_lru_call();
self.tail.map(|node_ptr| {
#[cfg(feature = "metrics")]
(&self.metrics).record_peek_lru_found();
unsafe {
let node = node_ptr.as_ptr();
(&(*node).key, &(*node).value)
}
})
}
pub fn clear(&mut self) {
#[cfg(feature = "metrics")]
self.metrics.record_clear();
while self.pop_lru().is_some() {}
}
#[inline(always)]
pub fn touch(&mut self, key: &K) -> bool {
#[cfg(feature = "metrics")]
self.metrics.record_touch_call();
if let Some(&node_ptr) = self.map.get(key) {
self.detach(node_ptr);
self.attach_front(node_ptr);
#[cfg(feature = "metrics")]
self.metrics.record_touch_found();
true
} else {
false
}
}
#[inline(always)]
fn detach(&mut self, node_ptr: NonNull<Node<K, V>>) {
unsafe {
let node = node_ptr.as_ptr();
let prev = (*node).prev;
let next = (*node).next;
match prev {
Some(prev_ptr) => (*prev_ptr.as_ptr()).next = next,
None => self.head = next,
}
match next {
Some(next_ptr) => (*next_ptr.as_ptr()).prev = prev,
None => self.tail = prev,
}
(*node).prev = None;
(*node).next = None;
}
}
#[inline(always)]
fn attach_front(&mut self, node_ptr: NonNull<Node<K, V>>) {
unsafe {
let node = node_ptr.as_ptr();
(*node).prev = None;
(*node).next = self.head;
match self.head {
Some(head_ptr) => (*head_ptr.as_ptr()).prev = Some(node_ptr),
None => self.tail = Some(node_ptr),
}
self.head = Some(node_ptr);
}
}
}
#[cfg(feature = "metrics")]
impl<K, V> FastLru<K, V>
where
K: Eq + Hash + Clone,
{
pub fn metrics_snapshot(&self) -> LruMetricsSnapshot {
LruMetricsSnapshot {
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,
pop_lru_calls: self.metrics.pop_lru_calls,
pop_lru_found: self.metrics.pop_lru_found,
peek_lru_calls: self.metrics.peek_lru_calls.get(),
peek_lru_found: self.metrics.peek_lru_found.get(),
touch_calls: self.metrics.touch_calls,
touch_found: self.metrics.touch_found,
recency_rank_calls: self.metrics.recency_rank_calls.get(),
recency_rank_found: self.metrics.recency_rank_found.get(),
recency_rank_scan_steps: self.metrics.recency_rank_scan_steps.get(),
cache_len: self.map.len(),
capacity: self.capacity,
}
}
}
#[cfg(feature = "metrics")]
impl<K, V> MetricsSnapshotProvider<LruMetricsSnapshot> for FastLru<K, V>
where
K: Eq + Hash + Clone,
{
fn snapshot(&self) -> LruMetricsSnapshot {
self.metrics_snapshot()
}
}
impl<K, V> Drop for FastLru<K, V> {
fn drop(&mut self) {
let mut current = self.head;
while let Some(node_ptr) = current {
unsafe {
let node = Box::from_raw(node_ptr.as_ptr());
current = node.next;
}
}
}
}
unsafe impl<K: Send, V: Send> Send for FastLru<K, V> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_operations() {
let mut cache = FastLru::new(3);
assert!(cache.is_empty());
assert_eq!(cache.capacity(), 3);
cache.insert(1, "one");
cache.insert(2, "two");
cache.insert(3, "three");
assert_eq!(cache.len(), 3);
assert_eq!(cache.get(&1), Some(&"one"));
assert_eq!(cache.get(&2), Some(&"two"));
assert_eq!(cache.get(&3), Some(&"three"));
}
#[test]
fn test_eviction() {
let mut cache = FastLru::new(2);
cache.insert(1, "one");
cache.insert(2, "two");
cache.get(&1);
cache.insert(3, "three");
assert!(cache.contains(&1));
assert!(!cache.contains(&2));
assert!(cache.contains(&3));
}
#[test]
fn test_update() {
let mut cache = FastLru::new(10);
cache.insert(1, "one");
let old = cache.insert(1, "ONE");
assert_eq!(old, Some("one"));
assert_eq!(cache.get(&1), Some(&"ONE"));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_remove() {
let mut cache = FastLru::new(10);
cache.insert(1, "one");
cache.insert(2, "two");
assert_eq!(cache.remove(&1), Some("one"));
assert_eq!(cache.remove(&1), None);
assert_eq!(cache.len(), 1);
}
#[test]
fn test_pop_lru() {
let mut cache = FastLru::new(10);
cache.insert(1, "one");
cache.insert(2, "two");
cache.insert(3, "three");
assert_eq!(cache.pop_lru(), Some((1, "one")));
assert_eq!(cache.pop_lru(), Some((2, "two")));
assert_eq!(cache.pop_lru(), Some((3, "three")));
assert_eq!(cache.pop_lru(), None);
}
#[test]
fn test_touch() {
let mut cache = FastLru::new(3);
cache.insert(1, "one");
cache.insert(2, "two");
cache.insert(3, "three");
assert!(cache.touch(&1));
cache.insert(4, "four");
assert!(cache.contains(&1));
assert!(!cache.contains(&2));
}
#[test]
fn test_zero_capacity() {
let mut cache: FastLru<i32, &str> = FastLru::new(0);
assert_eq!(cache.insert(1, "one"), None);
assert!(cache.is_empty());
assert_eq!(cache.get(&1), None);
}
#[test]
fn test_clear() {
let mut cache = FastLru::new(10);
cache.insert(1, "one");
cache.insert(2, "two");
cache.clear();
assert!(cache.is_empty());
assert_eq!(cache.get(&1), None);
}
#[test]
fn test_peek() {
let mut cache = FastLru::new(3);
cache.insert(1, "one");
cache.insert(2, "two");
cache.insert(3, "three");
assert_eq!(cache.peek(&1), Some(&"one"));
cache.insert(4, "four");
assert!(!cache.contains(&1));
}
}