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}