1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//! Concurrent Cache Implementations
//!
//! This module provides thread-safe concurrent cache implementations using the
//! Shared Segment Pattern for high-performance multi-threaded access.
//!
//! # Architecture
//!
//! Each concurrent cache uses segmented storage where:
//! - The key space is partitioned across multiple segments using hash-based sharding
//! - Each segment is protected by its own lock (using `parking_lot::Mutex`)
//! - Operations only lock the relevant segment, allowing concurrent access to different segments
//!
//! This design provides near-linear scalability with thread count for workloads
//! with good key distribution.
//!
//! ## Why Mutex Instead of RwLock?
//!
//! Cache algorithms like LRU, LFU, LFUDA, GDSF, and SLRU require **mutable access
//! even for read operations**. Every `get()` call must update internal state:
//!
//! - **LRU**: Moves the accessed item to the front of the recency list
//! - **LFU**: Increments the frequency counter and may move the item between frequency buckets
//! - **LFUDA**: Updates frequency and recalculates priority with the aging factor
//! - **GDSF**: Recalculates priority based on size, frequency, and cost
//! - **SLRU**: May promote items from the probationary to protected segment
//!
//! Since `get()` is inherently a write operation, using `RwLock` would provide no benefit—
//! every access would still require an exclusive write lock. `Mutex` is preferred because:
//!
//! 1. **Lower overhead**: `Mutex` has less bookkeeping than `RwLock`
//! 2. **No false promises**: Makes it clear that all operations are mutually exclusive
//! 3. **Better performance**: `parking_lot::Mutex` is highly optimized for this use case
//!
//! Concurrency is achieved through **segmentation** instead: different keys can be accessed
//! in parallel as long as they hash to different segments.
//!
//! # Available Concurrent Caches
//!
//! | Type | Description |
//! |------|-------------|
//! | [`ConcurrentLruCache`] | Thread-safe LRU cache with segmented storage |
//! | [`ConcurrentSlruCache`] | Thread-safe Segmented LRU cache |
//! | [`ConcurrentLfuCache`] | Thread-safe LFU cache |
//! | [`ConcurrentLfudaCache`] | Thread-safe LFUDA cache |
//! | [`ConcurrentGdsfCache`] | Thread-safe GDSF cache |
//!
//! # Performance Characteristics
//!
//! - **Read/Write Latency**: O(1) average case, same as single-threaded variants
//! - **Concurrency**: Near-linear scaling up to segment count
//! - **Memory Overhead**: ~1 Mutex per segment (typically 16 segments by default)
//!
//! # Default Segment Count
//!
//! By default, concurrent caches use `min(num_cpus * 4, 64)` segments, which provides
//! good parallelism while limiting memory overhead. You can customize this using the
//! `with_segments()` constructor.
//!
//! # Example
//!
//! ```rust,ignore
//! use cache_rs::concurrent::ConcurrentLruCache;
//! use std::sync::Arc;
//! use std::thread;
//!
//! // Create a concurrent cache (can be shared across threads)
//! let cache = Arc::new(ConcurrentLruCache::new(1000));
//!
//! // Spawn multiple threads that access the cache concurrently
//! let handles: Vec<_> = (0..4).map(|t| {
//! let cache = Arc::clone(&cache);
//! thread::spawn(move || {
//! for i in 0..1000 {
//! let key = format!("key_{}_{}", t, i);
//! cache.put(key.clone(), i);
//! let _ = cache.get(&key);
//! }
//! })
//! }).collect();
//!
//! for handle in handles {
//! handle.join().unwrap();
//! }
//! ```
//!
//! # Thread Safety
//!
//! All concurrent cache types implement `Send` and `Sync`, making them safe to share
//! across threads. They can be wrapped in `Arc` for shared ownership.
//!
//! # Zero-Copy Access
//!
//! For performance-critical code paths, use the `get_with()` method which provides
//! access to the value while holding the segment lock, avoiding unnecessary cloning:
//!
//! ```rust,ignore
//! let result = cache.get_with(&key, |value| {
//! // Process value while lock is held
//! value.some_method()
//! });
//! ```
pub use ConcurrentGdsfCache;
pub use ConcurrentLfuCache;
pub use ConcurrentLfudaCache;
pub use ConcurrentLruCache;
pub use ConcurrentSlruCache;
/// Returns the default number of segments based on CPU count.
///
/// This provides a good balance between parallelism and memory overhead.
/// The formula is `min(num_cpus * 4, 64)`.