Skip to main content

cache_rs/
meta.rs

1//! Algorithm-Specific Metadata Types
2//!
3//! This module provides metadata types used by different cache algorithms.
4//! Each cache algorithm that needs per-entry metadata defines its own type here,
5//! which can be used with `CacheEntry<K, V, M>`.
6//!
7//! # Metadata Types
8//!
9//! | Algorithm | Metadata Type | Description |
10//! |-----------|---------------|-------------|
11//! | LRU       | `()` (none)   | Position in list is implicit |
12//! | LFU       | `LfuMeta`     | Access frequency counter |
13//! | LFUDA     | `LfudaMeta`   | Access frequency (age is cache-global) |
14//! | SLRU      | `SlruMeta`    | Segment location (probationary/protected) |
15//! | GDSF      | `GdsfMeta`    | Frequency and calculated priority |
16//!
17//! # Memory Overhead
18//!
19//! | Metadata Type | Size |
20//! |---------------|------|
21//! | `()`          | 0 bytes |
22//! | `LfuMeta`     | 8 bytes |
23//! | `LfudaMeta`   | 8 bytes |
24//! | `SlruMeta`    | 1 byte (+ padding) |
25//! | `GdsfMeta`    | 16 bytes |
26//!
27//! # Usage
28//!
29//! ```
30//! use cache_rs::meta::{LfuMeta, SlruSegment, SlruMeta, GdsfMeta};
31//!
32//! // LFU metadata with initial frequency
33//! let lfu_meta = LfuMeta::default();
34//! assert_eq!(lfu_meta.frequency, 0);
35//!
36//! // SLRU metadata for probationary segment
37//! let slru_meta = SlruMeta::new(SlruSegment::Probationary);
38//! assert_eq!(slru_meta.segment, SlruSegment::Probationary);
39//!
40//! // GDSF metadata with initial values
41//! let gdsf_meta = GdsfMeta::default();
42//! assert_eq!(gdsf_meta.frequency, 0);
43//! assert_eq!(gdsf_meta.priority, 0.0);
44//! ```
45
46/// Metadata for LFU (Least Frequently Used) cache entries.
47///
48/// LFU tracks access frequency to evict the least frequently accessed items.
49/// The frequency counter is incremented on each access.
50///
51/// # Examples
52///
53/// ```
54/// use cache_rs::meta::LfuMeta;
55///
56/// let mut meta = LfuMeta::default();
57/// assert_eq!(meta.frequency, 0);
58///
59/// // Simulate access
60/// meta.frequency += 1;
61/// assert_eq!(meta.frequency, 1);
62/// ```
63#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
64pub struct LfuMeta {
65    /// Access frequency count.
66    /// Incremented each time the entry is accessed.
67    pub frequency: u64,
68}
69
70impl LfuMeta {
71    /// Creates a new LFU metadata with the specified initial frequency.
72    ///
73    /// # Arguments
74    ///
75    /// * `frequency` - Initial frequency value (usually 0 or 1)
76    #[inline]
77    pub fn new(frequency: u64) -> Self {
78        Self { frequency }
79    }
80
81    /// Increments the frequency counter and returns the new value.
82    #[inline]
83    pub fn increment(&mut self) -> u64 {
84        self.frequency += 1;
85        self.frequency
86    }
87}
88
89/// Metadata for LFUDA (LFU with Dynamic Aging) cache entries.
90///
91/// LFUDA is similar to LFU but addresses the "aging problem" where old
92/// frequently-used items can prevent new items from being cached.
93/// The age factor is maintained at the cache level, not per-entry.
94///
95/// # Algorithm
96///
97/// Entry priority = frequency + age_at_insertion
98/// - When an item is evicted, global_age = evicted_item.priority
99/// - New items start with current global_age as their insertion age
100///
101/// # Examples
102///
103/// ```
104/// use cache_rs::meta::LfudaMeta;
105///
106/// let meta = LfudaMeta::new(1, 10); // frequency=1, age_at_insertion=10
107/// assert_eq!(meta.frequency, 1);
108/// assert_eq!(meta.age_at_insertion, 10);
109/// assert_eq!(meta.priority(), 11);
110/// ```
111#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
112pub struct LfudaMeta {
113    /// Access frequency count.
114    pub frequency: u64,
115    /// Age value when this item was inserted (snapshot of global_age).
116    pub age_at_insertion: u64,
117}
118
119impl LfudaMeta {
120    /// Creates a new LFUDA metadata with the specified initial frequency and age.
121    ///
122    /// # Arguments
123    ///
124    /// * `frequency` - Initial frequency value (usually 1 for new items)
125    /// * `age_at_insertion` - The global_age at the time of insertion
126    #[inline]
127    pub fn new(frequency: u64, age_at_insertion: u64) -> Self {
128        Self {
129            frequency,
130            age_at_insertion,
131        }
132    }
133
134    /// Increments the frequency counter and returns the new value.
135    #[inline]
136    pub fn increment(&mut self) -> u64 {
137        self.frequency += 1;
138        self.frequency
139    }
140
141    /// Calculates the effective priority (frequency + age_at_insertion).
142    #[inline]
143    pub fn priority(&self) -> u64 {
144        self.frequency + self.age_at_insertion
145    }
146}
147
148/// Segment location within an SLRU cache.
149///
150/// SLRU divides the cache into two segments:
151/// - **Probationary**: New entries start here
152/// - **Protected**: Entries promoted after multiple accesses
153///
154/// Items in the protected segment are shielded from one-time scans.
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
156pub enum SlruSegment {
157    /// Entry is in the probationary segment (initial placement).
158    /// Items here are candidates for eviction when the cache is full.
159    #[default]
160    Probationary,
161
162    /// Entry is in the protected segment (promoted after repeated access).
163    /// Items here are protected from eviction by one-time scans.
164    Protected,
165}
166
167/// Metadata for SLRU (Segmented LRU) cache entries.
168///
169/// SLRU uses two segments to provide scan resistance:
170/// - New items enter the probationary segment
171/// - Accessed items in probationary are promoted to protected
172/// - When protected is full, LRU items demote back to probationary
173///
174/// # Examples
175///
176/// ```
177/// use cache_rs::meta::{SlruMeta, SlruSegment};
178///
179/// // New entry starts in probationary
180/// let meta = SlruMeta::default();
181/// assert_eq!(meta.segment, SlruSegment::Probationary);
182///
183/// // After promotion
184/// let protected_meta = SlruMeta::new(SlruSegment::Protected);
185/// assert_eq!(protected_meta.segment, SlruSegment::Protected);
186/// ```
187#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
188pub struct SlruMeta {
189    /// Which segment this entry is currently in.
190    pub segment: SlruSegment,
191}
192
193impl SlruMeta {
194    /// Creates a new SLRU metadata with the specified segment.
195    ///
196    /// # Arguments
197    ///
198    /// * `segment` - The segment where this entry should be placed
199    #[inline]
200    pub fn new(segment: SlruSegment) -> Self {
201        Self { segment }
202    }
203
204    /// Creates metadata for a probationary segment entry.
205    #[inline]
206    pub fn probationary() -> Self {
207        Self {
208            segment: SlruSegment::Probationary,
209        }
210    }
211
212    /// Creates metadata for a protected segment entry.
213    #[inline]
214    pub fn protected() -> Self {
215        Self {
216            segment: SlruSegment::Protected,
217        }
218    }
219
220    /// Returns true if the entry is in the probationary segment.
221    #[inline]
222    pub fn is_probationary(&self) -> bool {
223        self.segment == SlruSegment::Probationary
224    }
225
226    /// Returns true if the entry is in the protected segment.
227    #[inline]
228    pub fn is_protected(&self) -> bool {
229        self.segment == SlruSegment::Protected
230    }
231
232    /// Promotes the entry to the protected segment.
233    #[inline]
234    pub fn promote(&mut self) {
235        self.segment = SlruSegment::Protected;
236    }
237
238    /// Demotes the entry to the probationary segment.
239    #[inline]
240    pub fn demote(&mut self) {
241        self.segment = SlruSegment::Probationary;
242    }
243}
244
245/// Metadata for GDSF (Greedy Dual-Size Frequency) cache entries.
246///
247/// GDSF is a sophisticated algorithm that considers:
248/// - **Frequency**: How often the item is accessed
249/// - **Size**: Larger items have lower priority per byte
250/// - **Aging**: Global clock advances when items are evicted
251///
252/// # Priority Calculation
253///
254/// ```text
255/// priority = (frequency / size) + global_age
256/// ```
257///
258/// Items with lower priority are evicted first.
259///
260/// # Examples
261///
262/// ```
263/// use cache_rs::meta::GdsfMeta;
264///
265/// let meta = GdsfMeta::new(1, 0.5); // frequency=1, priority=0.5
266/// assert_eq!(meta.frequency, 1);
267/// assert_eq!(meta.priority, 0.5);
268/// ```
269#[derive(Debug, Clone, Copy, Default, PartialEq)]
270pub struct GdsfMeta {
271    /// Access frequency count.
272    pub frequency: u64,
273
274    /// Calculated priority: (frequency / size) + clock.
275    /// Lower priority = more likely to be evicted.
276    pub priority: f64,
277}
278
279impl GdsfMeta {
280    /// Creates a new GDSF metadata with the specified frequency and priority.
281    ///
282    /// # Arguments
283    ///
284    /// * `frequency` - Initial frequency count
285    /// * `priority` - Calculated priority value
286    #[inline]
287    pub fn new(frequency: u64, priority: f64) -> Self {
288        Self {
289            frequency,
290            priority,
291        }
292    }
293
294    /// Increments the frequency counter and returns the new value.
295    #[inline]
296    pub fn increment(&mut self) -> u64 {
297        self.frequency += 1;
298        self.frequency
299    }
300
301    /// Calculates and updates the priority based on frequency, size, and global age.
302    ///
303    /// # Arguments
304    ///
305    /// * `size` - Size of the cached item
306    /// * `global_age` - Current global age (clock value) of the cache
307    ///
308    /// # Returns
309    ///
310    /// The newly calculated priority value.
311    #[inline]
312    pub fn calculate_priority(&mut self, size: u64, global_age: f64) -> f64 {
313        self.priority = if size == 0 {
314            f64::INFINITY
315        } else {
316            (self.frequency as f64 / size as f64) + global_age
317        };
318        self.priority
319    }
320}
321
322#[cfg(test)]
323mod tests {
324    extern crate alloc;
325
326    use super::*;
327    use alloc::format;
328
329    #[test]
330    fn test_lfu_meta_default() {
331        let meta = LfuMeta::default();
332        assert_eq!(meta.frequency, 0);
333    }
334
335    #[test]
336    fn test_lfu_meta_new() {
337        let meta = LfuMeta::new(5);
338        assert_eq!(meta.frequency, 5);
339    }
340
341    #[test]
342    fn test_lfu_meta_increment() {
343        let mut meta = LfuMeta::new(0);
344        assert_eq!(meta.increment(), 1);
345        assert_eq!(meta.increment(), 2);
346        assert_eq!(meta.frequency, 2);
347    }
348
349    #[test]
350    fn test_lfuda_meta_default() {
351        let meta = LfudaMeta::default();
352        assert_eq!(meta.frequency, 0);
353        assert_eq!(meta.age_at_insertion, 0);
354    }
355
356    #[test]
357    fn test_lfuda_meta_increment() {
358        let mut meta = LfudaMeta::new(10, 5);
359        assert_eq!(meta.frequency, 10);
360        assert_eq!(meta.age_at_insertion, 5);
361        assert_eq!(meta.priority(), 15);
362        assert_eq!(meta.increment(), 11);
363        assert_eq!(meta.priority(), 16);
364    }
365
366    #[test]
367    fn test_slru_segment_default() {
368        let segment = SlruSegment::default();
369        assert_eq!(segment, SlruSegment::Probationary);
370    }
371
372    #[test]
373    fn test_slru_meta_default() {
374        let meta = SlruMeta::default();
375        assert_eq!(meta.segment, SlruSegment::Probationary);
376        assert!(meta.is_probationary());
377        assert!(!meta.is_protected());
378    }
379
380    #[test]
381    fn test_slru_meta_promote_demote() {
382        let mut meta = SlruMeta::probationary();
383        assert!(meta.is_probationary());
384
385        meta.promote();
386        assert!(meta.is_protected());
387
388        meta.demote();
389        assert!(meta.is_probationary());
390    }
391
392    #[test]
393    fn test_slru_meta_constructors() {
394        let prob = SlruMeta::probationary();
395        assert_eq!(prob.segment, SlruSegment::Probationary);
396
397        let prot = SlruMeta::protected();
398        assert_eq!(prot.segment, SlruSegment::Protected);
399    }
400
401    #[test]
402    fn test_gdsf_meta_default() {
403        let meta = GdsfMeta::default();
404        assert_eq!(meta.frequency, 0);
405        assert_eq!(meta.priority, 0.0);
406    }
407
408    #[test]
409    fn test_gdsf_meta_new() {
410        let meta = GdsfMeta::new(5, 1.5);
411        assert_eq!(meta.frequency, 5);
412        assert_eq!(meta.priority, 1.5);
413    }
414
415    #[test]
416    fn test_gdsf_meta_increment() {
417        let mut meta = GdsfMeta::new(0, 0.0);
418        assert_eq!(meta.increment(), 1);
419        assert_eq!(meta.frequency, 1);
420    }
421
422    #[test]
423    fn test_gdsf_meta_calculate_priority() {
424        let mut meta = GdsfMeta::new(4, 0.0);
425        let global_age = 10.0;
426
427        // priority = frequency/size + global_age = 4/2 + 10 = 12
428        let priority = meta.calculate_priority(2, global_age);
429        assert_eq!(priority, 12.0);
430        assert_eq!(meta.priority, 12.0);
431    }
432
433    #[test]
434    fn test_gdsf_meta_calculate_priority_zero_size() {
435        let mut meta = GdsfMeta::new(4, 0.0);
436        let priority = meta.calculate_priority(0, 10.0);
437        assert!(priority.is_infinite());
438    }
439
440    #[test]
441    fn test_metadata_clone() {
442        let lfu = LfuMeta::new(5);
443        let cloned = lfu;
444        assert_eq!(lfu, cloned);
445
446        let gdsf = GdsfMeta::new(3, 1.5);
447        let cloned = gdsf;
448        assert_eq!(gdsf.frequency, cloned.frequency);
449        assert_eq!(gdsf.priority, cloned.priority);
450    }
451
452    #[test]
453    fn test_metadata_debug() {
454        let lfu = LfuMeta::new(5);
455        let debug_str = format!("{:?}", lfu);
456        assert!(debug_str.contains("LfuMeta"));
457        assert!(debug_str.contains("5"));
458
459        let slru = SlruMeta::protected();
460        let debug_str = format!("{:?}", slru);
461        assert!(debug_str.contains("SlruMeta"));
462        assert!(debug_str.contains("Protected"));
463    }
464}