use crate::prelude::ReadOnlyCache;
use crate::traits::{CoreCache, MutableCache};
use rustc_hash::FxHashMap;
use std::fmt::{Debug, Formatter};
use std::hash::Hash;
#[cfg(feature = "metrics")]
use crate::metrics::metrics_impl::NruMetrics;
#[cfg(feature = "metrics")]
use crate::metrics::snapshot::NruMetricsSnapshot;
#[cfg(feature = "metrics")]
use crate::metrics::traits::MetricsSnapshotProvider;
#[derive(Debug, Clone)]
struct Entry<V> {
index: usize,
value: V,
referenced: bool,
}
pub struct NruCache<K, V> {
map: FxHashMap<K, Entry<V>>,
keys: Vec<K>,
capacity: usize,
#[cfg(feature = "metrics")]
metrics: NruMetrics,
}
impl<K, V> NruCache<K, V>
where
K: Clone + Eq + Hash,
{
#[inline]
pub fn new(capacity: usize) -> Self {
Self {
map: FxHashMap::default(),
keys: Vec::with_capacity(capacity),
capacity,
#[cfg(feature = "metrics")]
metrics: NruMetrics::default(),
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
#[inline]
pub fn iter(&self) -> Iter<'_, K, V> {
Iter {
inner: self.map.iter(),
}
}
#[inline]
pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
IterMut {
inner: self.map.iter_mut(),
}
}
fn evict_one(&mut self) -> Option<(K, V)> {
if self.keys.is_empty() {
return None;
}
for (idx, key) in self.keys.iter().enumerate() {
#[cfg(feature = "metrics")]
{
self.metrics.sweep_steps += 1;
}
if let Some(entry) = self.map.get(key) {
if !entry.referenced {
let victim_key = self.keys.swap_remove(idx);
if idx < self.keys.len() {
let swapped_key = &self.keys[idx];
if let Some(swapped_entry) = self.map.get_mut(swapped_key) {
swapped_entry.index = idx;
}
}
let victim_entry = self.map.remove(&victim_key)?;
return Some((victim_key, victim_entry.value));
}
}
}
for key in &self.keys {
if let Some(entry) = self.map.get_mut(key) {
if entry.referenced {
entry.referenced = false;
#[cfg(feature = "metrics")]
{
self.metrics.ref_bit_resets += 1;
}
}
}
}
if !self.keys.is_empty() {
let victim_key = self.keys.swap_remove(0);
if !self.keys.is_empty() {
let swapped_key = &self.keys[0];
if let Some(swapped_entry) = self.map.get_mut(swapped_key) {
swapped_entry.index = 0;
}
}
let victim_entry = self.map.remove(&victim_key)?;
return Some((victim_key, victim_entry.value));
}
None
}
}
impl<K, V> ReadOnlyCache<K, V> for NruCache<K, V>
where
K: Clone + Eq + Hash,
{
#[inline]
fn contains(&self, key: &K) -> bool {
self.map.contains_key(key)
}
#[inline]
fn len(&self) -> usize {
self.map.len()
}
#[inline]
fn capacity(&self) -> usize {
self.capacity
}
}
impl<K, V> CoreCache<K, V> for NruCache<K, V>
where
K: Clone + Eq + Hash,
{
#[inline]
fn insert(&mut self, key: K, value: V) -> Option<V> {
#[cfg(feature = "metrics")]
{
self.metrics.insert_calls += 1;
}
if self.capacity == 0 {
return None;
}
if let Some(entry) = self.map.get_mut(&key) {
#[cfg(feature = "metrics")]
{
self.metrics.insert_updates += 1;
}
let old_value = std::mem::replace(&mut entry.value, value);
entry.referenced = true;
return Some(old_value);
}
#[cfg(feature = "metrics")]
{
self.metrics.insert_new += 1;
}
if self.map.len() >= self.capacity {
#[cfg(feature = "metrics")]
{
self.metrics.evict_calls += 1;
}
if self.evict_one().is_some() {
#[cfg(feature = "metrics")]
{
self.metrics.evicted_entries += 1;
}
}
}
let index = self.keys.len();
self.keys.push(key.clone());
self.map.insert(
key,
Entry {
index,
value,
referenced: false,
},
);
None
}
#[inline]
fn get(&mut self, key: &K) -> Option<&V> {
if let Some(entry) = self.map.get_mut(key) {
entry.referenced = true;
#[cfg(feature = "metrics")]
{
self.metrics.get_calls += 1;
self.metrics.get_hits += 1;
}
Some(&entry.value)
} else {
#[cfg(feature = "metrics")]
{
self.metrics.get_calls += 1;
self.metrics.get_misses += 1;
}
None
}
}
fn clear(&mut self) {
self.map.clear();
self.keys.clear();
#[cfg(feature = "metrics")]
{
use crate::metrics::traits::CoreMetricsRecorder;
self.metrics.record_clear();
}
}
}
impl<K, V> MutableCache<K, V> for NruCache<K, V>
where
K: Clone + Eq + Hash,
{
#[inline]
fn remove(&mut self, key: &K) -> Option<V> {
let entry = self.map.remove(key)?;
let idx = entry.index;
self.keys.swap_remove(idx);
if idx < self.keys.len() {
let swapped_key = &self.keys[idx];
if let Some(swapped_entry) = self.map.get_mut(swapped_key) {
swapped_entry.index = idx;
}
}
Some(entry.value)
}
}
#[cfg(feature = "metrics")]
impl<K, V> NruCache<K, V>
where
K: Clone + Eq + Hash,
{
pub fn metrics_snapshot(&self) -> NruMetricsSnapshot {
NruMetricsSnapshot {
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,
sweep_steps: self.metrics.sweep_steps,
ref_bit_resets: self.metrics.ref_bit_resets,
cache_len: self.map.len(),
capacity: self.capacity,
}
}
}
#[cfg(feature = "metrics")]
impl<K, V> MetricsSnapshotProvider<NruMetricsSnapshot> for NruCache<K, V>
where
K: Clone + Eq + Hash,
{
fn snapshot(&self) -> NruMetricsSnapshot {
self.metrics_snapshot()
}
}
impl<K, V> Debug for NruCache<K, V>
where
K: Clone + Eq + Hash + std::fmt::Debug,
V: std::fmt::Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NruCache")
.field("capacity", &self.capacity)
.field("len", &self.map.len())
.field("keys", &self.keys)
.finish()
}
}
impl<K, V> Clone for NruCache<K, V>
where
K: Clone + Eq + Hash,
V: Clone,
{
fn clone(&self) -> Self {
Self {
map: self.map.clone(),
keys: self.keys.clone(),
capacity: self.capacity,
#[cfg(feature = "metrics")]
metrics: self.metrics,
}
}
}
pub struct Iter<'a, K, V> {
inner: std::collections::hash_map::Iter<'a, K, Entry<V>>,
}
impl<'a, K, V> Iterator for Iter<'a, K, V> {
type Item = (&'a K, &'a V);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(k, e)| (k, &e.value))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<K, V> ExactSizeIterator for Iter<'_, K, V> {}
impl<K, V> std::iter::FusedIterator for Iter<'_, K, V> {}
pub struct IterMut<'a, K, V> {
inner: std::collections::hash_map::IterMut<'a, K, Entry<V>>,
}
impl<'a, K, V> Iterator for IterMut<'a, K, V> {
type Item = (&'a K, &'a mut V);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(k, e)| (k, &mut e.value))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<K, V> ExactSizeIterator for IterMut<'_, K, V> {}
impl<K, V> std::iter::FusedIterator for IterMut<'_, K, V> {}
pub struct IntoIter<K, V> {
inner: std::collections::hash_map::IntoIter<K, Entry<V>>,
}
impl<K, V> Iterator for IntoIter<K, V> {
type Item = (K, V);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(k, e)| (k, e.value))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<K, V> ExactSizeIterator for IntoIter<K, V> {}
impl<K, V> std::iter::FusedIterator for IntoIter<K, V> {}
impl<K, V> IntoIterator for NruCache<K, V>
where
K: Clone + Eq + Hash,
{
type Item = (K, V);
type IntoIter = IntoIter<K, V>;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
inner: self.map.into_iter(),
}
}
}
impl<'a, K, V> IntoIterator for &'a NruCache<K, V>
where
K: Clone + Eq + Hash,
{
type Item = (&'a K, &'a V);
type IntoIter = Iter<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, K, V> IntoIterator for &'a mut NruCache<K, V>
where
K: Clone + Eq + Hash,
{
type Item = (&'a K, &'a mut V);
type IntoIter = IterMut<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl<K, V> Extend<(K, V)> for NruCache<K, V>
where
K: Clone + Eq + Hash,
{
fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
for (k, v) in iter {
self.insert(k, v);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::{CoreCache, MutableCache};
#[allow(dead_code)]
const _: () = {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
fn check() {
assert_send::<NruCache<String, i32>>();
assert_sync::<NruCache<String, i32>>();
}
};
#[test]
fn test_new() {
let cache: NruCache<i32, i32> = NruCache::new(10);
assert_eq!(cache.capacity(), 10);
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
}
#[test]
fn test_insert_and_get() {
let mut cache = NruCache::new(3);
cache.insert(1, 100);
cache.insert(2, 200);
cache.insert(3, 300);
assert_eq!(cache.get(&1), Some(&100));
assert_eq!(cache.get(&2), Some(&200));
assert_eq!(cache.get(&3), Some(&300));
assert_eq!(cache.len(), 3);
}
#[test]
fn test_update_existing() {
let mut cache = NruCache::new(3);
cache.insert(1, 100);
let old = cache.insert(1, 999);
assert_eq!(old, Some(100));
assert_eq!(cache.get(&1), Some(&999));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_eviction_unreferenced() {
let mut cache = NruCache::new(3);
cache.insert(1, 100);
cache.insert(2, 200);
cache.insert(3, 300);
let _ = cache.get(&1);
let _ = cache.get(&3);
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));
}
#[test]
fn test_eviction_all_referenced() {
let mut cache = NruCache::new(3);
cache.insert(1, 100);
cache.insert(2, 200);
cache.insert(3, 300);
let _ = cache.get(&1);
let _ = cache.get(&2);
let _ = cache.get(&3);
cache.insert(4, 400);
assert_eq!(cache.len(), 3);
assert!(cache.contains(&4));
}
#[test]
fn test_remove() {
let mut cache = NruCache::new(3);
cache.insert(1, 100);
cache.insert(2, 200);
cache.insert(3, 300);
let removed = cache.remove(&2);
assert_eq!(removed, Some(200));
assert_eq!(cache.len(), 2);
assert!(!cache.contains(&2));
assert!(cache.contains(&1));
assert!(cache.contains(&3));
}
#[test]
fn test_clear() {
let mut cache = NruCache::new(3);
cache.insert(1, 100);
cache.insert(2, 200);
cache.insert(3, 300);
cache.clear();
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
assert!(!cache.contains(&1));
}
#[test]
fn test_contains_does_not_set_reference() {
let mut cache = NruCache::new(2);
cache.insert(1, 100);
cache.insert(2, 200);
assert!(cache.contains(&1));
if let Some(entry) = cache.map.get_mut(&1) {
entry.referenced = false;
}
cache.insert(3, 300);
assert!(!cache.contains(&1)); assert!(cache.contains(&2));
assert!(cache.contains(&3));
}
#[test]
fn test_zero_capacity() {
let mut cache = NruCache::new(0);
assert_eq!(cache.capacity(), 0);
cache.insert(1, 100);
assert!(!cache.contains(&1));
}
}