extern crate alloc;
use crate::config::GdsfCacheConfig;
use crate::entry::CacheEntry;
use crate::list::{List, ListEntry};
use crate::metrics::{CacheMetrics, GdsfCacheMetrics};
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct GdsfMeta {
pub frequency: u64,
pub priority: f64,
}
impl GdsfMeta {
#[inline]
pub fn new(frequency: u64, priority: f64) -> Self {
Self {
frequency,
priority,
}
}
#[inline]
pub fn increment(&mut self) -> u64 {
self.frequency += 1;
self.frequency
}
#[inline]
pub fn calculate_priority(&mut self, size: u64, global_age: f64) -> f64 {
self.priority = if size == 0 {
f64::INFINITY
} else {
(self.frequency as f64 / size as f64) + global_age
};
self.priority
}
}
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use core::borrow::Borrow;
use core::hash::{BuildHasher, Hash};
use core::num::NonZeroUsize;
#[cfg(feature = "hashbrown")]
use hashbrown::DefaultHashBuilder;
#[cfg(feature = "hashbrown")]
use hashbrown::HashMap;
#[cfg(not(feature = "hashbrown"))]
use std::collections::hash_map::RandomState as DefaultHashBuilder;
#[cfg(not(feature = "hashbrown"))]
use std::collections::HashMap;
pub(crate) struct GdsfSegment<K, V, S = DefaultHashBuilder> {
config: GdsfCacheConfig,
global_age: f64,
min_priority: f64,
map: HashMap<K, *mut ListEntry<CacheEntry<K, V, GdsfMeta>>, S>,
priority_lists: BTreeMap<u64, List<CacheEntry<K, V, GdsfMeta>>>,
metrics: GdsfCacheMetrics,
current_size: u64,
}
unsafe impl<K: Send, V: Send, S: Send> Send for GdsfSegment<K, V, S> {}
unsafe impl<K: Send, V: Send, S: Sync> Sync for GdsfSegment<K, V, S> {}
impl<K: Hash + Eq, V: Clone, S: BuildHasher> GdsfSegment<K, V, S> {
#[allow(dead_code)] pub(crate) fn init(config: GdsfCacheConfig, hasher: S) -> Self {
let map_capacity = config.capacity.get().next_power_of_two();
GdsfSegment {
global_age: config.initial_age,
min_priority: 0.0,
map: HashMap::with_capacity_and_hasher(map_capacity, hasher),
priority_lists: BTreeMap::new(),
metrics: GdsfCacheMetrics::new(config.max_size),
current_size: 0,
config,
}
}
#[inline]
pub(crate) fn cap(&self) -> NonZeroUsize {
self.config.capacity
}
#[inline]
pub(crate) fn len(&self) -> usize {
self.map.len()
}
#[inline]
pub(crate) fn is_empty(&self) -> bool {
self.map.is_empty()
}
#[inline]
pub(crate) fn global_age(&self) -> f64 {
self.global_age
}
#[inline]
pub(crate) fn current_size(&self) -> u64 {
self.current_size
}
#[inline]
pub(crate) fn max_size(&self) -> u64 {
self.config.max_size
}
#[inline]
pub(crate) fn metrics(&self) -> &GdsfCacheMetrics {
&self.metrics
}
#[inline]
pub(crate) fn record_miss(&mut self, object_size: u64) {
self.metrics.core.record_miss(object_size);
}
fn calculate_priority(&self, frequency: u64, size: u64) -> f64 {
if size == 0 {
return f64::INFINITY;
}
(frequency as f64 / size as f64) + self.global_age
}
unsafe fn update_priority_by_node(
&mut self,
node: *mut ListEntry<CacheEntry<K, V, GdsfMeta>>,
) -> *mut ListEntry<CacheEntry<K, V, GdsfMeta>>
where
K: Clone + Hash + Eq,
{
let entry = (*node).get_value_mut();
let key_cloned = entry.key.clone();
let size = entry.metadata.size;
let meta = &mut entry.metadata.algorithm;
let old_priority = meta.priority;
meta.increment();
let global_age = self.global_age;
let new_priority = meta.calculate_priority(size, global_age);
let old_priority_key = (old_priority * 1000.0) as u64;
let new_priority_key = (new_priority * 1000.0) as u64;
if old_priority_key == new_priority_key {
self.priority_lists
.get_mut(&new_priority_key)
.unwrap()
.move_to_front(node);
return node;
}
let boxed_entry = self
.priority_lists
.get_mut(&old_priority_key)
.unwrap()
.remove(node)
.unwrap();
if self
.priority_lists
.get(&old_priority_key)
.unwrap()
.is_empty()
{
self.priority_lists.remove(&old_priority_key);
}
let entry_ptr = Box::into_raw(boxed_entry);
let capacity = self.config.capacity;
self.priority_lists
.entry(new_priority_key)
.or_insert_with(|| List::new(capacity));
self.priority_lists
.get_mut(&new_priority_key)
.unwrap()
.attach_from_other_list(entry_ptr);
self.map.insert(key_cloned, entry_ptr);
entry_ptr
}
pub(crate) fn get<Q>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q> + Clone,
Q: ?Sized + Hash + Eq,
{
if let Some(&node) = self.map.get(key) {
unsafe {
let entry = (*node).get_value();
let entry_size = entry.metadata.size;
let meta = &entry.metadata.algorithm;
self.metrics.core.record_hit(entry_size);
self.metrics
.record_item_access(meta.frequency, entry.metadata.size, meta.priority);
let new_node = self.update_priority_by_node(node);
let value = (*new_node).get_value().value.clone();
Some(value)
}
} else {
None
}
}
pub(crate) fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
where
K: Borrow<Q> + Clone,
Q: ?Sized + Hash + Eq,
{
if let Some(&node) = self.map.get(key) {
unsafe {
let entry = (*node).get_value();
let entry_size = entry.metadata.size;
let meta = &entry.metadata.algorithm;
self.metrics.core.record_hit(entry_size);
self.metrics
.record_item_access(meta.frequency, entry.metadata.size, meta.priority);
let new_node = self.update_priority_by_node(node);
let entry_mut = (*new_node).get_value_mut();
Some(&mut entry_mut.value)
}
} else {
None
}
}
pub(crate) fn put(&mut self, key: K, val: V, size: u64) -> Option<Vec<(K, V)>>
where
K: Clone,
{
if size == 0 {
return None;
}
if let Some(&node) = self.map.get(&key) {
unsafe {
let entry = (*node).get_value_mut();
let old_size = entry.metadata.size;
let meta = &mut entry.metadata.algorithm;
let old_priority_key = (meta.priority * 1000.0) as u64;
let frequency = meta.frequency;
let list = self.priority_lists.get_mut(&old_priority_key).unwrap();
let boxed_entry = list.remove(node).unwrap();
if list.is_empty() {
self.priority_lists.remove(&old_priority_key);
}
let entry_ptr = Box::into_raw(boxed_entry);
let cache_entry = (*entry_ptr).take_value();
let _ = (cache_entry.key, cache_entry.value);
let _ = Box::from_raw(entry_ptr);
self.current_size = self.current_size.saturating_sub(old_size);
self.current_size += size;
let new_priority = self.calculate_priority(frequency, size);
let new_priority_key = (new_priority * 1000.0) as u64;
let new_entry = CacheEntry::with_algorithm_metadata(
key.clone(),
val,
size,
GdsfMeta::new(frequency, new_priority),
);
let capacity = self.cap();
let list = self
.priority_lists
.entry(new_priority_key)
.or_insert_with(|| List::new(capacity));
if let Some(new_node) = list.add(new_entry) {
self.map.insert(key, new_node);
self.metrics.core.record_size_change(old_size, size);
self.metrics.core.bytes_written_to_cache += size;
return None;
} else {
self.map.remove(&key);
return None;
}
}
}
let capacity = self.config.capacity.get();
let max_size = self.config.max_size;
let mut evicted = Vec::new();
while self.len() >= capacity
|| (self.current_size + size > max_size && !self.map.is_empty())
{
if let Some(entry) = self.evict() {
self.metrics.core.evictions += 1;
evicted.push(entry);
} else {
break;
}
}
let priority = self.calculate_priority(1, size);
let priority_key = (priority * 1000.0) as u64;
let cap = self.config.capacity;
let list = self
.priority_lists
.entry(priority_key)
.or_insert_with(|| List::new(cap));
let cache_entry =
CacheEntry::with_algorithm_metadata(key.clone(), val, size, GdsfMeta::new(1, priority));
if let Some(node) = list.add(cache_entry) {
self.map.insert(key, node);
self.current_size += size;
if self.len() == 1 || priority < self.min_priority {
self.min_priority = priority;
}
self.metrics.core.record_insertion(size);
self.metrics
.record_item_cached(size, self.metrics.average_item_size());
self.metrics.record_item_access(1, size, priority);
}
if evicted.is_empty() {
None
} else {
Some(evicted)
}
}
fn evict(&mut self) -> Option<(K, V)> {
if self.is_empty() {
return None;
}
let min_priority_key = *self.priority_lists.keys().next()?;
let list = self.priority_lists.get_mut(&min_priority_key)?;
let entry = list.remove_last()?;
unsafe {
let entry_ptr = Box::into_raw(entry);
let cache_entry = (*entry_ptr).take_value();
let evicted_size = cache_entry.metadata.size;
let priority_to_update = cache_entry.metadata.algorithm.priority;
self.global_age = priority_to_update;
self.metrics.core.record_removal(evicted_size);
self.metrics.record_size_based_eviction();
self.metrics.record_aging_event(priority_to_update);
self.map.remove(&cache_entry.key);
self.current_size = self.current_size.saturating_sub(evicted_size);
if list.is_empty() {
self.priority_lists.remove(&min_priority_key);
}
let _ = Box::from_raw(entry_ptr);
Some((cache_entry.key, cache_entry.value))
}
}
pub(crate) fn remove<Q>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
if let Some(node) = self.map.remove(key) {
unsafe {
let priority = (*node).get_value().metadata.algorithm.priority;
let priority_key = (priority * 1000.0) as u64;
let list = self.priority_lists.get_mut(&priority_key).unwrap();
let boxed_entry = list.remove(node).unwrap();
if list.is_empty() {
self.priority_lists.remove(&priority_key);
}
let entry_ptr = Box::into_raw(boxed_entry);
let cache_entry = (*entry_ptr).take_value();
let removed_size = cache_entry.metadata.size;
self.current_size = self.current_size.saturating_sub(removed_size);
self.metrics.core.record_removal(removed_size);
let _ = Box::from_raw(entry_ptr);
Some(cache_entry.value)
}
} else {
None
}
}
pub(crate) fn clear(&mut self) {
self.map.clear();
self.priority_lists.clear();
self.global_age = 0.0;
self.min_priority = 0.0;
self.current_size = 0;
}
#[inline]
pub(crate) fn contains<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.map.contains_key(key)
}
pub(crate) fn peek<Q>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let &node = self.map.get(key)?;
unsafe {
let entry = (*node).get_value();
Some(&entry.value)
}
}
}
impl<K, V, S> core::fmt::Debug for GdsfSegment<K, V, S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("GdsfSegment")
.field("capacity", &self.config.capacity)
.field("len", &self.map.len())
.field("global_age", &self.global_age)
.finish()
}
}
#[derive(Debug)]
pub struct GdsfCache<K, V, S = DefaultHashBuilder> {
segment: GdsfSegment<K, V, S>,
}
impl<K: Hash + Eq, V: Clone, S: BuildHasher> GdsfCache<K, V, S> {
#[inline]
pub fn cap(&self) -> NonZeroUsize {
self.segment.cap()
}
#[inline]
pub fn len(&self) -> usize {
self.segment.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.segment.is_empty()
}
#[inline]
pub fn current_size(&self) -> u64 {
self.segment.current_size()
}
#[inline]
pub fn max_size(&self) -> u64 {
self.segment.max_size()
}
#[inline]
pub fn global_age(&self) -> f64 {
self.segment.global_age()
}
#[inline]
pub fn record_miss(&mut self, object_size: u64) {
self.segment.record_miss(object_size);
}
#[inline]
pub fn get<Q>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q> + Clone,
Q: ?Sized + Hash + Eq,
{
self.segment.get(key)
}
#[inline]
pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
where
K: Borrow<Q> + Clone,
Q: ?Sized + Hash + Eq,
{
self.segment.get_mut(key)
}
#[inline]
pub fn put(&mut self, key: K, val: V, size: u64) -> Option<Vec<(K, V)>>
where
K: Clone,
{
self.segment.put(key, val, size)
}
#[inline]
pub fn clear(&mut self) {
self.segment.clear()
}
#[inline]
pub fn contains<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.segment.contains(key)
}
#[inline]
pub fn peek<Q>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.segment.peek(key)
}
#[inline]
pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.segment.remove(key)
}
}
impl<K: Hash + Eq, V: Clone, S: BuildHasher> CacheMetrics for GdsfCache<K, V, S> {
fn metrics(&self) -> BTreeMap<String, f64> {
self.segment.metrics().metrics()
}
fn algorithm_name(&self) -> &'static str {
self.segment.metrics().algorithm_name()
}
}
impl<K: Hash + Eq, V: Clone> GdsfCache<K, V, DefaultHashBuilder> {
pub fn init(config: GdsfCacheConfig, hasher: Option<DefaultHashBuilder>) -> Self {
GdsfCache {
segment: GdsfSegment::init(config, hasher.unwrap_or_default()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::GdsfCacheConfig;
use core::num::NonZeroUsize;
fn make_cache<K: Hash + Eq + Clone, V: Clone>(cap: usize) -> GdsfCache<K, V> {
let config = GdsfCacheConfig {
capacity: NonZeroUsize::new(cap).unwrap(),
initial_age: 0.0,
max_size: u64::MAX,
};
GdsfCache::init(config, None)
}
#[test]
fn test_gdsf_basic_operations() {
let mut cache = make_cache(3);
assert_eq!(cache.put("a", 1, 1), None);
assert_eq!(cache.put("b", 2, 2), None);
assert_eq!(cache.put("c", 3, 1), None);
assert_eq!(cache.len(), 3);
assert_eq!(cache.get(&"a"), Some(1));
assert_eq!(cache.get(&"b"), Some(2));
assert_eq!(cache.get(&"c"), Some(3));
assert!(cache.contains(&"a"));
assert!(!cache.contains(&"d"));
}
#[test]
fn test_gdsf_frequency_priority() {
let mut cache = make_cache(2);
cache.put("a", 1, 1);
cache.put("b", 2, 1);
cache.get(&"a");
cache.get(&"a");
cache.put("c", 3, 1);
assert!(cache.contains(&"a"));
assert!(!cache.contains(&"b"));
assert!(cache.contains(&"c"));
}
#[test]
fn test_gdsf_size_consideration() {
let mut cache = make_cache(2);
cache.put("small", 1, 1);
cache.put("large", 2, 10);
cache.put("medium", 3, 5);
assert!(cache.contains(&"small"));
assert!(!cache.contains(&"large"));
assert!(cache.contains(&"medium"));
}
#[test]
fn test_gdsf_update_existing() {
let mut cache = make_cache(2);
cache.put("key", 1, 1);
assert_eq!(cache.get(&"key"), Some(1));
assert!(cache.put("key", 2, 2).is_none());
assert_eq!(cache.get(&"key"), Some(2));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_gdsf_zero_size_rejection() {
let mut cache = make_cache(2);
assert_eq!(cache.put("key", 1, 0), None);
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&"key"));
}
#[test]
fn test_gdsf_remove_by_key_legacy() {
let mut cache = make_cache(2);
cache.put("a", 1, 1);
cache.put("b", 2, 1);
assert_eq!(cache.remove(&"a"), Some(1));
assert_eq!(cache.len(), 1);
assert!(!cache.contains(&"a"));
assert!(cache.contains(&"b"));
assert_eq!(cache.remove(&"nonexistent"), None);
}
#[test]
fn test_gdsf_clear() {
let mut cache = make_cache(2);
cache.put("a", 1, 1);
cache.put("b", 2, 1);
assert_eq!(cache.len(), 2);
cache.clear();
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
assert!(!cache.contains(&"a"));
assert!(!cache.contains(&"b"));
}
#[test]
fn test_gdsf_global_aging() {
let mut cache = make_cache(2);
cache.put("a", 1, 1);
cache.put("b", 2, 1);
let initial_age = cache.global_age();
cache.put("c", 3, 1);
assert!(cache.global_age() > initial_age);
}
#[test]
fn test_miri_stacked_borrows_fix() {
let mut cache = make_cache(10);
cache.put("a", 1, 10);
cache.put("b", 2, 20);
cache.put("c", 3, 15);
for _ in 0..3 {
assert_eq!(cache.get(&"a"), Some(1));
assert_eq!(cache.get(&"b"), Some(2));
assert_eq!(cache.get(&"c"), Some(3));
}
assert_eq!(cache.len(), 3);
if let Some(val) = cache.get_mut(&"a") {
*val += 10;
}
assert_eq!(cache.get(&"a"), Some(11));
}
#[test]
fn test_gdsf_segment_directly() {
let config = GdsfCacheConfig {
capacity: NonZeroUsize::new(2).unwrap(),
initial_age: 0.0,
max_size: u64::MAX,
};
let mut segment: GdsfSegment<&str, i32, DefaultHashBuilder> =
GdsfSegment::init(config, DefaultHashBuilder::default());
assert_eq!(segment.len(), 0);
assert!(segment.is_empty());
assert_eq!(segment.cap().get(), 2);
segment.put("a", 1, 1);
segment.put("b", 2, 2);
assert_eq!(segment.len(), 2);
assert_eq!(segment.get(&"a"), Some(1));
assert_eq!(segment.get(&"b"), Some(2));
}
#[test]
fn test_gdsf_concurrent_access() {
extern crate std;
use std::sync::{Arc, Mutex};
use std::thread;
use std::vec::Vec;
let cache = Arc::new(Mutex::new(make_cache::<String, i32>(100)));
let num_threads = 4;
let ops_per_thread = 100;
let mut handles: Vec<std::thread::JoinHandle<()>> = Vec::new();
for t in 0..num_threads {
let cache = Arc::clone(&cache);
handles.push(thread::spawn(move || {
for i in 0..ops_per_thread {
let key = std::format!("key_{}_{}", t, i);
let size = ((i % 10) + 1) as u64; let mut guard = cache.lock().unwrap();
guard.put(key.clone(), i, size);
let _ = guard.get(&key);
}
}));
}
for handle in handles {
handle.join().unwrap();
}
let mut guard = cache.lock().unwrap();
assert!(guard.len() <= 100);
guard.clear(); }
#[test]
fn test_gdsf_size_aware_tracking() {
let mut cache = make_cache(10);
assert_eq!(cache.current_size(), 0);
assert_eq!(cache.max_size(), u64::MAX);
cache.put("a", 1, 100);
cache.put("b", 2, 200);
cache.put("c", 3, 150);
assert_eq!(cache.current_size(), 450);
assert_eq!(cache.len(), 3);
cache.clear();
assert_eq!(cache.current_size(), 0);
}
#[test]
fn test_gdsf_init_constructor() {
let config = GdsfCacheConfig {
capacity: NonZeroUsize::new(1000).unwrap(),
initial_age: 0.0,
max_size: 1024 * 1024,
};
let cache: GdsfCache<String, i32> = GdsfCache::init(config, None);
assert_eq!(cache.current_size(), 0);
assert_eq!(cache.max_size(), 1024 * 1024);
}
#[test]
fn test_gdsf_with_limits_constructor() {
let config = GdsfCacheConfig {
capacity: NonZeroUsize::new(100).unwrap(),
initial_age: 0.0,
max_size: 1024 * 1024,
};
let cache: GdsfCache<String, String> = GdsfCache::init(config, None);
assert_eq!(cache.current_size(), 0);
assert_eq!(cache.max_size(), 1024 * 1024);
assert_eq!(cache.cap().get(), 100);
}
#[test]
fn test_gdsf_contains_non_promoting() {
let mut cache = make_cache(2);
cache.put("a", 1, 10);
cache.put("b", 2, 10);
assert!(cache.contains(&"a"));
assert!(cache.contains(&"b"));
assert!(!cache.contains(&"c"));
cache.get(&"b");
assert!(cache.contains(&"a"));
cache.put("c", 3, 10);
assert!(!cache.contains(&"a")); assert!(cache.contains(&"b")); assert!(cache.contains(&"c")); }
#[test]
fn test_put_returns_none_when_no_eviction() {
let mut cache = make_cache(10);
assert!(cache.put("a", 1, 10).is_none());
assert!(cache.put("b", 2, 10).is_none());
}
#[test]
fn test_put_returns_single_eviction() {
let mut cache = make_cache(2);
cache.put("a", 1, 10);
cache.put("b", 2, 10);
let result = cache.put("c", 3, 10);
assert!(result.is_some());
let evicted = result.unwrap();
assert_eq!(evicted.len(), 1);
}
#[test]
fn test_put_replacement_returns_none() {
let mut cache = make_cache(10);
cache.put("a", 1, 10);
let result = cache.put("a", 2, 10);
assert!(result.is_none());
assert_eq!(cache.get(&"a"), Some(2));
}
#[test]
fn test_put_returns_multiple_evictions_size_based() {
let config = GdsfCacheConfig {
capacity: NonZeroUsize::new(10).unwrap(),
initial_age: 0.0,
max_size: 100,
};
let mut cache = GdsfCache::init(config, None);
for i in 0..10 {
cache.put(i, i, 10);
}
let result = cache.put(99, 99, 50);
let evicted = result.unwrap();
assert_eq!(evicted.len(), 5);
}
#[test]
fn test_gdsf_remove_by_key() {
let mut cache = make_cache(2);
cache.put("a", 1, 10);
cache.put("b", 2, 10);
assert_eq!(cache.remove(&"a"), Some(1));
assert_eq!(cache.len(), 1);
assert!(!cache.contains(&"a"));
assert!(cache.contains(&"b"));
assert_eq!(cache.remove(&"nonexistent"), None);
}
}