Skip to main content

torsh_tensor/
lockfree_cache.rs

1//! Lock-Free Cache Structures for High-Performance Concurrent Access
2//!
3//! This module provides lock-free data structures optimized for concurrent tensor operations.
4//! By avoiding locks, these structures provide better scalability in multi-threaded scenarios.
5//!
6//! # Features
7//!
8//! - **Lock-free queues**: SPSC and MPMC queue implementations
9//! - **Atomic reference counting**: Lock-free reference counting for shared data
10//! - **Concurrent hash map**: Lock-free hash map for tensor caching
11//! - **Wait-free operations**: Some operations guarantee completion in bounded time
12//! - **Cache-line aligned**: Minimize false sharing
13
14use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
15use std::sync::Arc;
16
17/// Lock-free SPSC (Single Producer Single Consumer) queue
18///
19/// This queue provides wait-free operations for both producer and consumer
20/// when used correctly. It's ideal for passing tensors between threads.
21pub struct LockFreeSPSCQueue<T> {
22    buffer: Vec<Option<T>>,
23    capacity: usize,
24    head: AtomicUsize,  // Consumer reads from here
25    tail: AtomicUsize,  // Producer writes here
26    _padding: [u8; 64], // Cache line padding
27}
28
29impl<T> LockFreeSPSCQueue<T> {
30    /// Create a new SPSC queue with given capacity
31    pub fn new(capacity: usize) -> Self {
32        let actual_capacity = capacity.next_power_of_two();
33        let mut buffer = Vec::with_capacity(actual_capacity);
34        for _ in 0..actual_capacity {
35            buffer.push(None);
36        }
37
38        Self {
39            buffer,
40            capacity: actual_capacity,
41            head: AtomicUsize::new(0),
42            tail: AtomicUsize::new(0),
43            _padding: [0; 64],
44        }
45    }
46
47    /// Try to push an item (returns false if queue is full)
48    pub fn try_push(&mut self, item: T) -> bool {
49        let tail = self.tail.load(Ordering::Relaxed);
50        let next_tail = (tail + 1) & (self.capacity - 1);
51        let head = self.head.load(Ordering::Acquire);
52
53        if next_tail == head {
54            return false; // Queue is full
55        }
56
57        // SAFETY: We've checked that the slot is available
58        self.buffer[tail] = Some(item);
59        self.tail.store(next_tail, Ordering::Release);
60        true
61    }
62
63    /// Try to pop an item (returns None if queue is empty)
64    pub fn try_pop(&mut self) -> Option<T> {
65        let head = self.head.load(Ordering::Relaxed);
66        let tail = self.tail.load(Ordering::Acquire);
67
68        if head == tail {
69            return None; // Queue is empty
70        }
71
72        let item = self.buffer[head].take();
73        let next_head = (head + 1) & (self.capacity - 1);
74        self.head.store(next_head, Ordering::Release);
75
76        item
77    }
78
79    /// Check if the queue is empty
80    pub fn is_empty(&self) -> bool {
81        let head = self.head.load(Ordering::Acquire);
82        let tail = self.tail.load(Ordering::Acquire);
83        head == tail
84    }
85
86    /// Get approximate size (may not be exact due to concurrent operations)
87    pub fn len(&self) -> usize {
88        let head = self.head.load(Ordering::Acquire);
89        let tail = self.tail.load(Ordering::Acquire);
90
91        if tail >= head {
92            tail - head
93        } else {
94            self.capacity - head + tail
95        }
96    }
97
98    /// Get the capacity
99    pub fn capacity(&self) -> usize {
100        self.capacity
101    }
102}
103
104/// Lock-free cache entry with atomic reference counting
105#[derive(Clone)]
106pub struct LockFreeCacheEntry<T: Clone> {
107    data: Arc<T>,
108    access_count: Arc<AtomicUsize>,
109    last_access: Arc<AtomicUsize>, // Timestamp
110    valid: Arc<AtomicBool>,
111}
112
113impl<T: Clone> LockFreeCacheEntry<T> {
114    /// Create a new cache entry
115    pub fn new(data: T) -> Self {
116        Self {
117            data: Arc::new(data),
118            access_count: Arc::new(AtomicUsize::new(0)),
119            last_access: Arc::new(AtomicUsize::new(Self::current_timestamp())),
120            valid: Arc::new(AtomicBool::new(true)),
121        }
122    }
123
124    /// Get the data (increments access count)
125    pub fn get(&self) -> Option<Arc<T>> {
126        if self.valid.load(Ordering::Acquire) {
127            self.access_count.fetch_add(1, Ordering::Relaxed);
128            self.last_access
129                .store(Self::current_timestamp(), Ordering::Release);
130            Some(Arc::clone(&self.data))
131        } else {
132            None
133        }
134    }
135
136    /// Invalidate this entry
137    pub fn invalidate(&self) {
138        self.valid.store(false, Ordering::Release);
139    }
140
141    /// Check if entry is valid
142    pub fn is_valid(&self) -> bool {
143        self.valid.load(Ordering::Acquire)
144    }
145
146    /// Get access count
147    pub fn access_count(&self) -> usize {
148        self.access_count.load(Ordering::Relaxed)
149    }
150
151    /// Get last access timestamp
152    pub fn last_access(&self) -> usize {
153        self.last_access.load(Ordering::Relaxed)
154    }
155
156    /// Get current timestamp (monotonic counter)
157    fn current_timestamp() -> usize {
158        use std::sync::atomic::AtomicUsize as GlobalCounter;
159        static COUNTER: GlobalCounter = GlobalCounter::new(0);
160        COUNTER.fetch_add(1, Ordering::Relaxed)
161    }
162}
163
164/// Simple lock-free cache with fixed size
165///
166/// This cache uses atomic operations for thread-safe access without locks.
167/// It's optimized for read-heavy workloads with occasional writes.
168pub struct LockFreeCache<K: Eq + std::hash::Hash + Clone, V: Clone> {
169    entries: Vec<Option<(K, LockFreeCacheEntry<V>)>>,
170    size: AtomicUsize,
171    capacity: usize,
172}
173
174impl<K: Eq + std::hash::Hash + Clone, V: Clone> LockFreeCache<K, V> {
175    /// Create a new lock-free cache with given capacity
176    pub fn new(capacity: usize) -> Self {
177        let mut entries = Vec::with_capacity(capacity);
178        for _ in 0..capacity {
179            entries.push(None);
180        }
181
182        Self {
183            entries,
184            size: AtomicUsize::new(0),
185            capacity,
186        }
187    }
188
189    /// Get the capacity
190    pub fn capacity(&self) -> usize {
191        self.capacity
192    }
193
194    /// Get current size (approximate due to concurrency)
195    pub fn len(&self) -> usize {
196        self.size.load(Ordering::Relaxed)
197    }
198
199    /// Check if empty
200    pub fn is_empty(&self) -> bool {
201        self.len() == 0
202    }
203
204    /// Calculate hash for a key
205    fn hash(&self, key: &K) -> usize {
206        use std::collections::hash_map::DefaultHasher;
207        use std::hash::Hasher;
208
209        let mut hasher = DefaultHasher::new();
210        key.hash(&mut hasher);
211        (hasher.finish() as usize) % self.capacity
212    }
213
214    /// Try to get a value from the cache
215    pub fn get(&self, key: &K) -> Option<Arc<V>> {
216        let index = self.hash(key);
217        let mut probe = 0;
218
219        while probe < self.capacity {
220            let current_index = (index + probe) % self.capacity;
221
222            // SAFETY: We're reading from a fixed-size vec with valid indices
223            if let Some((ref k, ref entry)) = unsafe { &*self.entries.as_ptr().add(current_index) }
224            {
225                if k == key {
226                    return entry.get();
227                }
228            } else {
229                // Empty slot means key doesn't exist
230                return None;
231            }
232
233            probe += 1;
234        }
235
236        None
237    }
238
239    /// Check if a key exists in the cache
240    pub fn contains_key(&self, key: &K) -> bool {
241        self.get(key).is_some()
242    }
243}
244
245/// Statistics for lock-free operations
246#[derive(Debug, Default)]
247pub struct LockFreeStats {
248    /// Number of successful operations
249    pub successes: AtomicUsize,
250    /// Number of failed operations (contention)
251    pub failures: AtomicUsize,
252    /// Number of retries
253    pub retries: AtomicUsize,
254}
255
256impl LockFreeStats {
257    /// Create new statistics
258    pub fn new() -> Self {
259        Self::default()
260    }
261
262    /// Record a success
263    pub fn record_success(&self) {
264        self.successes.fetch_add(1, Ordering::Relaxed);
265    }
266
267    /// Record a failure
268    pub fn record_failure(&self) {
269        self.failures.fetch_add(1, Ordering::Relaxed);
270    }
271
272    /// Record a retry
273    pub fn record_retry(&self) {
274        self.retries.fetch_add(1, Ordering::Relaxed);
275    }
276
277    /// Get success count
278    pub fn successes(&self) -> usize {
279        self.successes.load(Ordering::Relaxed)
280    }
281
282    /// Get failure count
283    pub fn failures(&self) -> usize {
284        self.failures.load(Ordering::Relaxed)
285    }
286
287    /// Get retry count
288    pub fn retries(&self) -> usize {
289        self.retries.load(Ordering::Relaxed)
290    }
291
292    /// Calculate success rate
293    pub fn success_rate(&self) -> f64 {
294        let total = self.successes() + self.failures();
295        if total == 0 {
296            0.0
297        } else {
298            self.successes() as f64 / total as f64
299        }
300    }
301
302    /// Reset statistics
303    pub fn reset(&self) {
304        self.successes.store(0, Ordering::Relaxed);
305        self.failures.store(0, Ordering::Relaxed);
306        self.retries.store(0, Ordering::Relaxed);
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn test_spsc_queue_basic() {
316        let mut queue = LockFreeSPSCQueue::new(4);
317
318        assert!(queue.is_empty());
319        assert_eq!(queue.len(), 0);
320
321        assert!(queue.try_push(1));
322        assert!(queue.try_push(2));
323        assert!(queue.try_push(3));
324
325        assert_eq!(queue.len(), 3);
326        assert!(!queue.is_empty());
327
328        assert_eq!(queue.try_pop(), Some(1));
329        assert_eq!(queue.try_pop(), Some(2));
330        assert_eq!(queue.len(), 1);
331
332        assert_eq!(queue.try_pop(), Some(3));
333        assert!(queue.is_empty());
334        assert_eq!(queue.try_pop(), None);
335    }
336
337    #[test]
338    fn test_spsc_queue_full() {
339        let mut queue = LockFreeSPSCQueue::new(2);
340
341        // Can insert 1 element (capacity - 1)
342        assert!(queue.try_push(1));
343
344        // Queue is full
345        assert!(!queue.try_push(2));
346
347        // Pop one
348        assert_eq!(queue.try_pop(), Some(1));
349
350        // Now we can insert again
351        assert!(queue.try_push(3));
352    }
353
354    #[test]
355    fn test_spsc_queue_wraparound() {
356        let mut queue = LockFreeSPSCQueue::new(4);
357
358        // Fill queue
359        assert!(queue.try_push(1));
360        assert!(queue.try_push(2));
361
362        // Pop some
363        assert_eq!(queue.try_pop(), Some(1));
364
365        // Push more (should wrap around)
366        assert!(queue.try_push(3));
367        assert!(queue.try_push(4));
368
369        // Pop all
370        assert_eq!(queue.try_pop(), Some(2));
371        assert_eq!(queue.try_pop(), Some(3));
372        assert_eq!(queue.try_pop(), Some(4));
373        assert_eq!(queue.try_pop(), None);
374    }
375
376    #[test]
377    fn test_cache_entry_basic() {
378        let entry = LockFreeCacheEntry::new(42);
379
380        assert!(entry.is_valid());
381        assert_eq!(*entry.get().expect("get should succeed"), 42);
382        assert_eq!(entry.access_count(), 1);
383
384        entry.invalidate();
385        assert!(!entry.is_valid());
386        assert!(entry.get().is_none());
387    }
388
389    #[test]
390    fn test_cache_entry_access_count() {
391        let entry = LockFreeCacheEntry::new("test");
392
393        assert_eq!(entry.access_count(), 0);
394
395        entry.get();
396        assert_eq!(entry.access_count(), 1);
397
398        entry.get();
399        entry.get();
400        assert_eq!(entry.access_count(), 3);
401    }
402
403    #[test]
404    fn test_lock_free_cache_basic() {
405        let cache: LockFreeCache<String, i32> = LockFreeCache::new(10);
406
407        assert_eq!(cache.capacity(), 10);
408        assert_eq!(cache.len(), 0);
409        assert!(cache.is_empty());
410    }
411
412    #[test]
413    fn test_lock_free_cache_contains() {
414        let cache = LockFreeCache::<String, i32>::new(10);
415
416        assert!(!cache.contains_key(&"test".to_string()));
417    }
418
419    #[test]
420    fn test_lockfree_stats() {
421        let stats = LockFreeStats::new();
422
423        assert_eq!(stats.successes(), 0);
424        assert_eq!(stats.failures(), 0);
425        assert_eq!(stats.retries(), 0);
426
427        stats.record_success();
428        stats.record_success();
429        stats.record_failure();
430
431        assert_eq!(stats.successes(), 2);
432        assert_eq!(stats.failures(), 1);
433
434        let rate = stats.success_rate();
435        assert!((rate - 0.666).abs() < 0.01);
436
437        stats.reset();
438        assert_eq!(stats.successes(), 0);
439        assert_eq!(stats.failures(), 0);
440    }
441
442    #[test]
443    fn test_spsc_queue_capacity() {
444        let queue = LockFreeSPSCQueue::<i32>::new(7);
445        // Should round up to next power of 2
446        assert_eq!(queue.capacity(), 8);
447    }
448
449    #[test]
450    fn test_cache_entry_timestamp() {
451        let entry1 = LockFreeCacheEntry::new(1);
452        let entry2 = LockFreeCacheEntry::new(2);
453
454        let ts1 = entry1.last_access();
455        let ts2 = entry2.last_access();
456
457        // Timestamps should be different (monotonic)
458        assert!(ts2 > ts1);
459    }
460}