Skip to main content

oxirs_tdb/storage/
memory_optimization.rs

1//! Advanced memory optimizations for high-performance I/O
2//!
3//! This module provides advanced memory optimization techniques:
4//! - Sequential access prefetching
5//! - Memory pooling for zero-copy operations
6//! - Page alignment for optimal cache usage
7//! - Readahead strategies for predictive I/O
8
9use crate::error::Result;
10use crate::storage::page::{PageId, PAGE_SIZE};
11use parking_lot::RwLock;
12use std::collections::VecDeque;
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::sync::Arc;
15
16/// Memory optimization configuration
17#[derive(Debug, Clone)]
18pub struct MemoryOptimizationConfig {
19    /// Enable sequential access prefetching
20    pub enable_prefetch: bool,
21    /// Number of pages to prefetch ahead
22    pub prefetch_pages: usize,
23    /// Enable memory pooling for zero-copy
24    pub enable_memory_pool: bool,
25    /// Memory pool size (in pages)
26    pub pool_size: usize,
27    /// Readahead strategy
28    pub readahead_strategy: ReadaheadStrategy,
29}
30
31impl Default for MemoryOptimizationConfig {
32    fn default() -> Self {
33        Self {
34            enable_prefetch: true,
35            prefetch_pages: 8,
36            enable_memory_pool: true,
37            pool_size: 256,
38            readahead_strategy: ReadaheadStrategy::Adaptive,
39        }
40    }
41}
42
43/// Readahead strategy for predictive I/O
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum ReadaheadStrategy {
46    /// No readahead
47    None,
48    /// Fixed readahead window
49    Fixed,
50    /// Adaptive readahead based on access patterns
51    Adaptive,
52    /// Aggressive prefetching for sequential scans
53    Aggressive,
54}
55
56/// Access pattern detector
57#[derive(Debug)]
58struct AccessPatternDetector {
59    /// Recent accesses
60    recent_accesses: RwLock<VecDeque<PageId>>,
61    /// Sequential access counter
62    sequential_count: AtomicU64,
63    /// Random access counter
64    random_count: AtomicU64,
65    /// History size
66    history_size: usize,
67}
68
69impl AccessPatternDetector {
70    /// Create a new pattern detector
71    fn new(history_size: usize) -> Self {
72        Self {
73            recent_accesses: RwLock::new(VecDeque::with_capacity(history_size)),
74            sequential_count: AtomicU64::new(0),
75            random_count: AtomicU64::new(0),
76            history_size,
77        }
78    }
79
80    /// Record a page access
81    fn record_access(&self, page_id: PageId) {
82        let mut accesses = self.recent_accesses.write();
83
84        // Check if sequential
85        if let Some(&last_page) = accesses.back() {
86            if page_id == last_page + 1 || page_id == last_page.saturating_sub(1) {
87                self.sequential_count.fetch_add(1, Ordering::Relaxed);
88            } else {
89                self.random_count.fetch_add(1, Ordering::Relaxed);
90            }
91        }
92
93        accesses.push_back(page_id);
94
95        // Limit history size
96        while accesses.len() > self.history_size {
97            accesses.pop_front();
98        }
99    }
100
101    /// Detect if access pattern is sequential
102    fn is_sequential(&self) -> bool {
103        let sequential = self.sequential_count.load(Ordering::Relaxed);
104        let random = self.random_count.load(Ordering::Relaxed);
105        let total = sequential + random;
106
107        if total == 0 {
108            false
109        } else {
110            // Consider sequential if >70% of accesses are sequential
111            (sequential as f64 / total as f64) > 0.7
112        }
113    }
114
115    /// Get recommended prefetch distance
116    fn recommended_prefetch_distance(&self) -> usize {
117        if self.is_sequential() {
118            // Aggressive prefetching for sequential access
119            16
120        } else {
121            // Conservative prefetching for random access
122            4
123        }
124    }
125
126    /// Reset statistics
127    fn reset(&self) {
128        self.recent_accesses.write().clear();
129        self.sequential_count.store(0, Ordering::Relaxed);
130        self.random_count.store(0, Ordering::Relaxed);
131    }
132}
133
134/// Memory pool for zero-copy operations
135#[derive(Debug)]
136struct MemoryPool {
137    /// Pre-allocated page buffers
138    buffers: RwLock<Vec<Vec<u8>>>,
139    /// Pool capacity
140    capacity: usize,
141    /// Statistics
142    allocations: AtomicU64,
143    /// Pool hits (reused buffer)
144    pool_hits: AtomicU64,
145    /// Pool misses (new allocation)
146    pool_misses: AtomicU64,
147}
148
149impl MemoryPool {
150    /// Create a new memory pool
151    fn new(capacity: usize) -> Self {
152        let mut buffers = Vec::with_capacity(capacity);
153
154        // Pre-allocate page-aligned buffers
155        for _ in 0..capacity {
156            let buffer = vec![0; PAGE_SIZE];
157            buffers.push(buffer);
158        }
159
160        Self {
161            buffers: RwLock::new(buffers),
162            capacity,
163            allocations: AtomicU64::new(0),
164            pool_hits: AtomicU64::new(0),
165            pool_misses: AtomicU64::new(0),
166        }
167    }
168
169    /// Acquire a buffer from the pool
170    fn acquire(&self) -> Vec<u8> {
171        self.allocations.fetch_add(1, Ordering::Relaxed);
172
173        let mut buffers = self.buffers.write();
174
175        if let Some(buffer) = buffers.pop() {
176            self.pool_hits.fetch_add(1, Ordering::Relaxed);
177            buffer
178        } else {
179            self.pool_misses.fetch_add(1, Ordering::Relaxed);
180            vec![0; PAGE_SIZE]
181        }
182    }
183
184    /// Return a buffer to the pool
185    fn release(&self, mut buffer: Vec<u8>) {
186        // Clear buffer before returning to pool
187        buffer.fill(0);
188
189        let mut buffers = self.buffers.write();
190
191        if buffers.len() < self.capacity {
192            buffers.push(buffer);
193        }
194        // Otherwise, buffer is dropped and deallocated
195    }
196
197    /// Get pool hit rate
198    fn hit_rate(&self) -> f64 {
199        let hits = self.pool_hits.load(Ordering::Relaxed) as f64;
200        let total = self.allocations.load(Ordering::Relaxed) as f64;
201
202        if total == 0.0 {
203            0.0
204        } else {
205            hits / total
206        }
207    }
208
209    /// Get pool statistics
210    fn stats(&self) -> MemoryPoolStats {
211        MemoryPoolStats {
212            capacity: self.capacity,
213            available: self.buffers.read().len(),
214            allocations: self.allocations.load(Ordering::Relaxed),
215            hits: self.pool_hits.load(Ordering::Relaxed),
216            misses: self.pool_misses.load(Ordering::Relaxed),
217            hit_rate: self.hit_rate(),
218        }
219    }
220}
221
222/// Memory pool statistics
223#[derive(Debug, Clone)]
224pub struct MemoryPoolStats {
225    /// Pool capacity
226    pub capacity: usize,
227    /// Available buffers
228    pub available: usize,
229    /// Total allocations
230    pub allocations: u64,
231    /// Pool hits
232    pub hits: u64,
233    /// Pool misses
234    pub misses: u64,
235    /// Hit rate
236    pub hit_rate: f64,
237}
238
239/// Memory optimization manager
240pub struct MemoryOptimizer {
241    /// Configuration
242    config: MemoryOptimizationConfig,
243    /// Access pattern detector
244    pattern_detector: Arc<AccessPatternDetector>,
245    /// Memory pool
246    memory_pool: Option<Arc<MemoryPool>>,
247    /// Statistics
248    stats: MemoryOptimizerStats,
249}
250
251/// Memory optimizer statistics
252#[derive(Debug, Default)]
253pub struct MemoryOptimizerStats {
254    /// Total prefetch requests
255    pub prefetch_requests: AtomicU64,
256    /// Successful prefetches
257    pub prefetch_hits: AtomicU64,
258    /// Sequential accesses detected
259    pub sequential_accesses: AtomicU64,
260    /// Random accesses detected
261    pub random_accesses: AtomicU64,
262}
263
264impl MemoryOptimizerStats {
265    /// Get prefetch hit rate
266    pub fn prefetch_hit_rate(&self) -> f64 {
267        let requests = self.prefetch_requests.load(Ordering::Relaxed) as f64;
268        if requests == 0.0 {
269            0.0
270        } else {
271            let hits = self.prefetch_hits.load(Ordering::Relaxed) as f64;
272            hits / requests
273        }
274    }
275
276    /// Get sequential access percentage
277    pub fn sequential_percentage(&self) -> f64 {
278        let sequential = self.sequential_accesses.load(Ordering::Relaxed) as f64;
279        let random = self.random_accesses.load(Ordering::Relaxed) as f64;
280        let total = sequential + random;
281
282        if total == 0.0 {
283            0.0
284        } else {
285            (sequential / total) * 100.0
286        }
287    }
288}
289
290impl MemoryOptimizer {
291    /// Create a new memory optimizer
292    pub fn new(config: MemoryOptimizationConfig) -> Self {
293        let pattern_detector = Arc::new(AccessPatternDetector::new(100));
294
295        let memory_pool = if config.enable_memory_pool {
296            Some(Arc::new(MemoryPool::new(config.pool_size)))
297        } else {
298            None
299        };
300
301        Self {
302            config,
303            pattern_detector,
304            memory_pool,
305            stats: MemoryOptimizerStats::default(),
306        }
307    }
308
309    /// Record a page access
310    pub fn record_access(&self, page_id: PageId) {
311        self.pattern_detector.record_access(page_id);
312
313        if self.pattern_detector.is_sequential() {
314            self.stats
315                .sequential_accesses
316                .fetch_add(1, Ordering::Relaxed);
317        } else {
318            self.stats.random_accesses.fetch_add(1, Ordering::Relaxed);
319        }
320    }
321
322    /// Get prefetch recommendations
323    pub fn get_prefetch_recommendations(&self, current_page: PageId) -> Vec<PageId> {
324        if !self.config.enable_prefetch {
325            return Vec::new();
326        }
327
328        let prefetch_distance = match self.config.readahead_strategy {
329            ReadaheadStrategy::None => return Vec::new(),
330            ReadaheadStrategy::Fixed => self.config.prefetch_pages,
331            ReadaheadStrategy::Adaptive => self.pattern_detector.recommended_prefetch_distance(),
332            ReadaheadStrategy::Aggressive => 32, // Very aggressive prefetching
333        };
334
335        // Generate prefetch list (sequential forward)
336        let mut recommendations = Vec::with_capacity(prefetch_distance);
337        for i in 1..=prefetch_distance {
338            recommendations.push(current_page + i as u64);
339        }
340
341        self.stats
342            .prefetch_requests
343            .fetch_add(prefetch_distance as u64, Ordering::Relaxed);
344
345        recommendations
346    }
347
348    /// Acquire a buffer from the memory pool
349    pub fn acquire_buffer(&self) -> Vec<u8> {
350        if let Some(ref pool) = self.memory_pool {
351            pool.acquire()
352        } else {
353            vec![0; PAGE_SIZE]
354        }
355    }
356
357    /// Release a buffer back to the memory pool
358    pub fn release_buffer(&self, buffer: Vec<u8>) {
359        if let Some(ref pool) = self.memory_pool {
360            pool.release(buffer);
361        }
362        // Otherwise, buffer is dropped
363    }
364
365    /// Get memory pool statistics (if enabled)
366    pub fn pool_stats(&self) -> Option<MemoryPoolStats> {
367        self.memory_pool.as_ref().map(|pool| pool.stats())
368    }
369
370    /// Get optimizer statistics
371    pub fn stats(&self) -> &MemoryOptimizerStats {
372        &self.stats
373    }
374
375    /// Check if access pattern is sequential
376    pub fn is_sequential_access(&self) -> bool {
377        self.pattern_detector.is_sequential()
378    }
379
380    /// Reset access pattern detection
381    pub fn reset_pattern_detection(&self) {
382        self.pattern_detector.reset();
383        self.stats.sequential_accesses.store(0, Ordering::Relaxed);
384        self.stats.random_accesses.store(0, Ordering::Relaxed);
385    }
386
387    /// Get recommended readahead distance
388    pub fn recommended_readahead(&self) -> usize {
389        self.pattern_detector.recommended_prefetch_distance()
390    }
391}
392
393impl Default for MemoryOptimizer {
394    fn default() -> Self {
395        Self::new(MemoryOptimizationConfig::default())
396    }
397}
398
399#[cfg(test)]
400mod tests {
401    use super::*;
402
403    #[test]
404    fn test_memory_optimizer_creation() {
405        let config = MemoryOptimizationConfig::default();
406        let optimizer = MemoryOptimizer::new(config);
407
408        assert!(!optimizer.is_sequential_access());
409    }
410
411    #[test]
412    fn test_sequential_access_detection() {
413        let optimizer = MemoryOptimizer::default();
414
415        // Simulate sequential access pattern
416        for page_id in 0..20 {
417            optimizer.record_access(page_id);
418        }
419
420        assert!(optimizer.is_sequential_access());
421        assert!(optimizer.stats().sequential_percentage() > 50.0);
422    }
423
424    #[test]
425    fn test_random_access_detection() {
426        let optimizer = MemoryOptimizer::default();
427
428        // Simulate random access pattern
429        let random_pages = [5, 100, 3, 200, 7, 150, 9, 250];
430        for &page_id in &random_pages {
431            optimizer.record_access(page_id);
432        }
433
434        assert!(!optimizer.is_sequential_access());
435    }
436
437    #[test]
438    fn test_prefetch_recommendations() {
439        let config = MemoryOptimizationConfig {
440            enable_prefetch: true,
441            prefetch_pages: 4,
442            readahead_strategy: ReadaheadStrategy::Fixed,
443            ..Default::default()
444        };
445        let optimizer = MemoryOptimizer::new(config);
446
447        let recommendations = optimizer.get_prefetch_recommendations(100);
448
449        assert_eq!(recommendations.len(), 4);
450        assert_eq!(recommendations[0], 101);
451        assert_eq!(recommendations[1], 102);
452        assert_eq!(recommendations[2], 103);
453        assert_eq!(recommendations[3], 104);
454    }
455
456    #[test]
457    fn test_adaptive_prefetch() {
458        let config = MemoryOptimizationConfig {
459            readahead_strategy: ReadaheadStrategy::Adaptive,
460            ..Default::default()
461        };
462        let optimizer = MemoryOptimizer::new(config);
463
464        // Establish sequential pattern
465        for page_id in 0..10 {
466            optimizer.record_access(page_id);
467        }
468
469        let recommendations = optimizer.get_prefetch_recommendations(10);
470
471        // Should recommend aggressive prefetching for sequential access
472        assert!(recommendations.len() >= 4);
473    }
474
475    #[test]
476    fn test_memory_pool_acquire_release() {
477        let config = MemoryOptimizationConfig {
478            enable_memory_pool: true,
479            pool_size: 10,
480            ..Default::default()
481        };
482        let optimizer = MemoryOptimizer::new(config);
483
484        // Acquire buffer
485        let buffer1 = optimizer.acquire_buffer();
486        assert_eq!(buffer1.len(), PAGE_SIZE);
487
488        // Release buffer
489        optimizer.release_buffer(buffer1);
490
491        // Acquire again (should reuse)
492        let buffer2 = optimizer.acquire_buffer();
493        assert_eq!(buffer2.len(), PAGE_SIZE);
494
495        // Check pool stats
496        let stats = optimizer.pool_stats().unwrap();
497        assert!(stats.allocations >= 2);
498        assert!(stats.hit_rate > 0.0);
499    }
500
501    #[test]
502    fn test_memory_pool_statistics() {
503        let config = MemoryOptimizationConfig {
504            enable_memory_pool: true,
505            pool_size: 5,
506            ..Default::default()
507        };
508        let optimizer = MemoryOptimizer::new(config);
509
510        // Acquire and release multiple buffers
511        for _ in 0..10 {
512            let buffer = optimizer.acquire_buffer();
513            optimizer.release_buffer(buffer);
514        }
515
516        let stats = optimizer.pool_stats().unwrap();
517        assert_eq!(stats.capacity, 5);
518        assert!(stats.hits > 0);
519        assert!(stats.hit_rate > 0.0);
520    }
521
522    #[test]
523    fn test_disabled_prefetch() {
524        let config = MemoryOptimizationConfig {
525            enable_prefetch: false,
526            ..Default::default()
527        };
528        let optimizer = MemoryOptimizer::new(config);
529
530        let recommendations = optimizer.get_prefetch_recommendations(10);
531        assert!(recommendations.is_empty());
532    }
533
534    #[test]
535    fn test_aggressive_readahead() {
536        let config = MemoryOptimizationConfig {
537            readahead_strategy: ReadaheadStrategy::Aggressive,
538            ..Default::default()
539        };
540        let optimizer = MemoryOptimizer::new(config);
541
542        let recommendations = optimizer.get_prefetch_recommendations(10);
543        assert_eq!(recommendations.len(), 32);
544    }
545
546    #[test]
547    fn test_pattern_reset() {
548        let optimizer = MemoryOptimizer::default();
549
550        // Establish pattern
551        for page_id in 0..10 {
552            optimizer.record_access(page_id);
553        }
554
555        assert!(optimizer.is_sequential_access());
556
557        // Reset
558        optimizer.reset_pattern_detection();
559
560        // Pattern should be reset
561        assert_eq!(optimizer.stats().sequential_percentage(), 0.0);
562    }
563
564    #[test]
565    fn test_recommended_readahead_distance() {
566        let optimizer = MemoryOptimizer::default();
567
568        // Sequential access
569        for page_id in 0..20 {
570            optimizer.record_access(page_id);
571        }
572
573        let distance = optimizer.recommended_readahead();
574        assert!(distance >= 8); // Should recommend larger distance for sequential
575    }
576
577    #[test]
578    fn test_memory_pool_capacity_limit() {
579        let config = MemoryOptimizationConfig {
580            enable_memory_pool: true,
581            pool_size: 2,
582            ..Default::default()
583        };
584        let optimizer = MemoryOptimizer::new(config);
585
586        // Acquire more buffers than capacity
587        let buf1 = optimizer.acquire_buffer();
588        let buf2 = optimizer.acquire_buffer();
589        let buf3 = optimizer.acquire_buffer();
590
591        // Release all
592        optimizer.release_buffer(buf1);
593        optimizer.release_buffer(buf2);
594        optimizer.release_buffer(buf3);
595
596        // Pool should not exceed capacity
597        let stats = optimizer.pool_stats().unwrap();
598        assert_eq!(stats.available, 2); // Only keeps 2 buffers
599    }
600
601    #[test]
602    fn test_prefetch_statistics() {
603        let optimizer = MemoryOptimizer::default();
604
605        // Generate prefetch recommendations
606        for i in 0..5 {
607            optimizer.get_prefetch_recommendations(i * 10);
608        }
609
610        let stats = optimizer.stats();
611        assert!(stats.prefetch_requests.load(Ordering::Relaxed) > 0);
612    }
613
614    #[test]
615    fn test_mixed_access_pattern() {
616        let optimizer = MemoryOptimizer::default();
617
618        // Mix sequential and random
619        for i in 0..5 {
620            optimizer.record_access(i);
621        }
622        optimizer.record_access(100);
623        optimizer.record_access(200);
624
625        // Should detect mixed pattern
626        let stats = optimizer.stats();
627        assert!(stats.sequential_accesses.load(Ordering::Relaxed) > 0);
628        assert!(stats.random_accesses.load(Ordering::Relaxed) > 0);
629    }
630}