Skip to main content

scirs2_io/gpu/
memory_management.rs

1//! Advanced GPU memory management for efficient I/O operations
2//!
3//! This module provides sophisticated GPU memory pooling, buffer lifecycle
4//! management, and fragmentation prevention for optimal performance.
5
6use crate::error::{IoError, Result};
7use scirs2_core::gpu::{GpuBuffer, GpuContext, GpuDataType, GpuDevice};
8use std::collections::{BTreeMap, HashMap, VecDeque};
9use std::sync::{Arc, Mutex};
10use std::time::{Duration, Instant};
11
12/// Advanced GPU memory pool with smart buffer reuse and fragmentation prevention
13#[derive(Debug)]
14pub struct AdvancedGpuMemoryPool {
15    device: GpuDevice,
16    free_buffers: BTreeMap<usize, VecDeque<PooledBuffer>>,
17    allocated_buffers: HashMap<usize, BufferMetadata>,
18    allocation_stats: AllocationStats,
19    config: PoolConfig,
20    fragmentation_manager: FragmentationManager,
21    buffer_id_counter: usize,
22}
23
24impl AdvancedGpuMemoryPool {
25    /// Create a new advanced GPU memory pool
26    pub fn new(device: GpuDevice, config: PoolConfig) -> Self {
27        Self {
28            device,
29            free_buffers: BTreeMap::new(),
30            allocated_buffers: HashMap::new(),
31            allocation_stats: AllocationStats::default(),
32            config,
33            fragmentation_manager: FragmentationManager::new(),
34            buffer_id_counter: 0,
35        }
36    }
37
38    /// Allocate a buffer from the pool
39    pub fn allocate(&mut self, size: usize) -> Result<PooledBuffer> {
40        let aligned_size = self.align_size(size);
41
42        // Try to reuse an existing buffer
43        if let Some(buffer) = self.find_reusable_buffer(aligned_size) {
44            self.allocation_stats.cache_hits += 1;
45            return Ok(buffer);
46        }
47
48        // Create new buffer
49        self.allocation_stats.cache_misses += 1;
50        self.create_new_buffer(aligned_size)
51    }
52
53    /// Return a buffer to the pool
54    pub fn deallocate(&mut self, mut buffer: PooledBuffer) -> Result<()> {
55        // Update statistics
56        buffer.touch();
57        self.allocation_stats.total_deallocations += 1;
58
59        // Check if buffer should be kept in pool
60        if buffer.metadata.size <= self.config.max_buffer_size
61            && self.get_total_pool_size() < self.config.max_pool_size
62        {
63            // Return to appropriate size bucket
64            let size_bucket = self.get_size_bucket(buffer.metadata.size);
65            self.free_buffers
66                .entry(size_bucket)
67                .or_insert_with(VecDeque::new)
68                .push_back(buffer);
69        }
70        // Otherwise, buffer will be dropped and GPU memory freed
71
72        // Check for fragmentation and compact if needed
73        if self.fragmentation_manager.needs_compaction() {
74            self.compact_pool()?;
75        }
76
77        Ok(())
78    }
79
80    /// Get pool statistics
81    pub fn get_stats(&self) -> PoolStats {
82        PoolStats {
83            total_buffers: self.allocated_buffers.len(),
84            free_buffers: self.free_buffers.values().map(|v| v.len()).sum(),
85            total_pool_size: self.get_total_pool_size(),
86            fragmentation_ratio: self.fragmentation_manager.get_fragmentation_ratio(),
87            cache_hit_rate: self.allocation_stats.get_cache_hit_rate(),
88            allocation_stats: self.allocation_stats.clone(),
89        }
90    }
91
92    /// Force garbage collection of expired buffers
93    pub fn garbage_collect(&mut self) -> Result<usize> {
94        let mut freed_count = 0;
95        let now = Instant::now();
96
97        for buffers in self.free_buffers.values_mut() {
98            let original_len = buffers.len();
99            buffers.retain(|buffer| !buffer.is_expired(self.config.buffer_timeout));
100            freed_count += original_len - buffers.len();
101        }
102
103        // Update fragmentation
104        self.fragmentation_manager.update_after_gc();
105
106        Ok(freed_count)
107    }
108
109    /// Compact the pool to reduce fragmentation
110    pub fn compact_pool(&mut self) -> Result<()> {
111        if !self.config.enable_compaction {
112            return Ok(());
113        }
114
115        // Merge adjacent free buffers of similar sizes
116        for buffers in self.free_buffers.values_mut() {
117            // Sort by creation time and merge similar-sized buffers
118            let mut merged_buffers = VecDeque::new();
119
120            while let Some(buffer) = buffers.pop_front() {
121                // Try to merge with existing buffers or add as new
122                merged_buffers.push_back(buffer);
123            }
124
125            *buffers = merged_buffers;
126        }
127
128        self.fragmentation_manager.reset_fragmentation();
129        Ok(())
130    }
131
132    /// Clear all free buffers
133    pub fn clear(&mut self) {
134        self.free_buffers.clear();
135        self.allocation_stats.reset();
136        self.fragmentation_manager.reset_fragmentation();
137    }
138
139    // Private helper methods
140    fn find_reusable_buffer(&mut self, size: usize) -> Option<PooledBuffer> {
141        let size_bucket = self.get_size_bucket(size);
142
143        // Look for exact size match first
144        if let Some(buffers) = self.free_buffers.get_mut(&size_bucket) {
145            if let Some(mut buffer) = buffers.pop_front() {
146                buffer.touch();
147                return Some(buffer);
148            }
149        }
150
151        // Look for larger buffers that can be reused
152        for (&bucket_size, buffers) in self.free_buffers.range_mut(size_bucket..) {
153            if bucket_size <= size * 2 {
154                // Don't waste too much memory
155                if let Some(mut buffer) = buffers.pop_front() {
156                    buffer.touch();
157                    return Some(buffer);
158                }
159            }
160        }
161
162        None
163    }
164
165    fn create_new_buffer(&mut self, size: usize) -> Result<PooledBuffer> {
166        if size > self.config.max_buffer_size {
167            return Err(IoError::Other(format!(
168                "Buffer size {} exceeds maximum {}",
169                size, self.config.max_buffer_size
170            )));
171        }
172
173        // Create a GPU context and allocate buffer through it
174        let context = GpuContext::new(self.device.backend())
175            .map_err(|e| IoError::Other(format!("Failed to create GPU context: {}", e)))?;
176        let buffer: GpuBuffer<u8> = context.create_buffer(size);
177
178        let buffer_id = self.buffer_id_counter;
179        self.buffer_id_counter += 1;
180
181        let pooled_buffer = PooledBuffer::new(buffer, buffer_id, "memory_pool".to_string());
182
183        // Track allocation
184        self.allocation_stats.total_allocations += 1;
185        self.allocation_stats.bytes_allocated += size;
186        self.allocated_buffers
187            .insert(buffer_id, pooled_buffer.metadata.clone());
188
189        Ok(pooled_buffer)
190    }
191
192    fn align_size(&self, size: usize) -> usize {
193        let alignment = self.config.alignment;
194        (size + alignment - 1) & !(alignment - 1)
195    }
196
197    fn get_size_bucket(&self, size: usize) -> usize {
198        // Use power-of-2 buckets for efficient lookup
199        if size <= self.config.min_buffer_size {
200            self.config.min_buffer_size
201        } else {
202            size.next_power_of_two()
203        }
204    }
205
206    fn get_total_pool_size(&self) -> usize {
207        self.free_buffers
208            .iter()
209            .map(|(&size, buffers)| size * buffers.len())
210            .sum()
211    }
212}
213
214/// Configuration for the memory pool
215#[derive(Debug, Clone)]
216pub struct PoolConfig {
217    /// Maximum total pool size in bytes
218    pub max_pool_size: usize,
219    /// Minimum buffer size in bytes
220    pub min_buffer_size: usize,
221    /// Maximum buffer size in bytes
222    pub max_buffer_size: usize,
223    /// Memory alignment in bytes
224    pub alignment: usize,
225    /// Fragmentation threshold for triggering defragmentation (0.0-1.0)
226    pub defragmentation_threshold: f64,
227    /// Buffer timeout duration
228    pub buffer_timeout: Duration,
229    /// Enable memory compaction
230    pub enable_compaction: bool,
231    /// Enable memory prefetching
232    pub enable_prefetch: bool,
233}
234
235impl Default for PoolConfig {
236    fn default() -> Self {
237        Self {
238            max_pool_size: 1024 * 1024 * 1024,        // 1GB default
239            min_buffer_size: 4096,                    // 4KB minimum
240            max_buffer_size: 64 * 1024 * 1024,        // 64MB maximum single allocation
241            alignment: 256,                           // GPU-optimal alignment
242            defragmentation_threshold: 0.3,           // Defrag when 30% fragmented
243            buffer_timeout: Duration::from_secs(300), // 5 minutes timeout
244            enable_compaction: true,
245            enable_prefetch: true,
246        }
247    }
248}
249
250/// Metadata for tracking buffer usage and performance
251#[derive(Debug, Clone)]
252pub struct BufferMetadata {
253    /// Unique buffer ID
254    pub id: usize,
255    /// Buffer size in bytes
256    pub size: usize,
257    /// Timestamp when buffer was allocated
258    pub allocated_at: Instant,
259    /// Number of times the buffer has been accessed
260    pub access_count: usize,
261    /// Timestamp of last access
262    pub last_access: Instant,
263    /// Source of the allocation for debugging
264    pub allocation_source: String,
265}
266
267/// Buffer wrapper with lifecycle tracking
268pub struct PooledBuffer {
269    /// GPU buffer handle
270    pub buffer: GpuBuffer<u8>,
271    /// Buffer metadata
272    pub metadata: BufferMetadata,
273    /// Timestamp when buffer was created in pool
274    pub created_at: Instant,
275    /// Timestamp of last use
276    pub last_used: Instant,
277    /// Number of times the buffer has been used
278    pub use_count: usize,
279}
280
281impl std::fmt::Debug for PooledBuffer {
282    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
283        f.debug_struct("PooledBuffer")
284            .field("buffer_size", &self.buffer.len())
285            .field("metadata", &self.metadata)
286            .field("created_at", &self.created_at)
287            .field("last_used", &self.last_used)
288            .field("use_count", &self.use_count)
289            .finish()
290    }
291}
292
293impl PooledBuffer {
294    fn new(buffer: GpuBuffer<u8>, id: usize, allocation_source: String) -> Self {
295        let now = Instant::now();
296        let size = buffer.len();
297
298        Self {
299            buffer,
300            metadata: BufferMetadata {
301                id,
302                size,
303                allocated_at: now,
304                access_count: 0,
305                last_access: now,
306                allocation_source,
307            },
308            created_at: now,
309            last_used: now,
310            use_count: 0,
311        }
312    }
313
314    fn touch(&mut self) {
315        self.last_used = Instant::now();
316        self.use_count += 1;
317        self.metadata.access_count += 1;
318        self.metadata.last_access = self.last_used;
319    }
320
321    fn is_expired(&self, timeout: Duration) -> bool {
322        self.last_used.elapsed() > timeout
323    }
324
325    /// Get buffer utilization efficiency
326    pub fn get_utilization_efficiency(&self) -> f64 {
327        if self.use_count == 0 {
328            0.0
329        } else {
330            let age_seconds = self.created_at.elapsed().as_secs_f64();
331            self.use_count as f64 / age_seconds.max(1.0)
332        }
333    }
334}
335
336/// Allocation statistics for performance monitoring
337#[derive(Debug, Default, Clone)]
338pub struct AllocationStats {
339    /// Total number of allocations
340    pub total_allocations: usize,
341    /// Total number of deallocations
342    pub total_deallocations: usize,
343    /// Number of cache hits
344    pub cache_hits: usize,
345    /// Number of cache misses
346    pub cache_misses: usize,
347    /// Total bytes allocated
348    pub bytes_allocated: usize,
349    /// Total bytes deallocated
350    pub bytes_deallocated: usize,
351    /// Peak memory usage in bytes
352    pub peak_memory_usage: usize,
353    /// Number of memory compactions performed
354    pub compaction_count: usize,
355}
356
357impl AllocationStats {
358    /// Calculate cache hit rate as a percentage (0.0-1.0)
359    pub fn get_cache_hit_rate(&self) -> f64 {
360        let total_requests = self.cache_hits + self.cache_misses;
361        if total_requests == 0 {
362            0.0
363        } else {
364            self.cache_hits as f64 / total_requests as f64
365        }
366    }
367
368    /// Reset all statistics to default values
369    pub fn reset(&mut self) {
370        *self = Self::default();
371    }
372}
373
374/// Fragmentation management for optimal memory usage
375#[derive(Debug)]
376pub struct FragmentationManager {
377    internal_fragmentation: f64,
378    external_fragmentation: f64,
379    compaction_threshold: f64,
380    last_compaction: Instant,
381    fragmentation_history: VecDeque<f64>,
382}
383
384impl FragmentationManager {
385    /// Create a new fragmentation manager
386    pub fn new() -> Self {
387        Self {
388            internal_fragmentation: 0.0,
389            external_fragmentation: 0.0,
390            compaction_threshold: 0.3,
391            last_compaction: Instant::now(),
392            fragmentation_history: VecDeque::with_capacity(100),
393        }
394    }
395
396    /// Check if memory compaction is needed
397    pub fn needs_compaction(&self) -> bool {
398        self.external_fragmentation > self.compaction_threshold
399            && self.last_compaction.elapsed() > Duration::from_secs(60)
400    }
401
402    /// Get current fragmentation ratio (0.0-1.0)
403    pub fn get_fragmentation_ratio(&self) -> f64 {
404        (self.internal_fragmentation + self.external_fragmentation) / 2.0
405    }
406
407    /// Update fragmentation measurements
408    pub fn update_fragmentation(&mut self, internal: f64, external: f64) {
409        self.internal_fragmentation = internal;
410        self.external_fragmentation = external;
411
412        let avg_fragmentation = self.get_fragmentation_ratio();
413        self.fragmentation_history.push_back(avg_fragmentation);
414
415        if self.fragmentation_history.len() > 100 {
416            self.fragmentation_history.pop_front();
417        }
418    }
419
420    /// Reset fragmentation counters after compaction
421    pub fn reset_fragmentation(&mut self) {
422        self.internal_fragmentation = 0.0;
423        self.external_fragmentation = 0.0;
424        self.last_compaction = Instant::now();
425    }
426
427    /// Update fragmentation estimates after garbage collection
428    pub fn update_after_gc(&mut self) {
429        // Fragmentation typically reduces after garbage collection
430        self.external_fragmentation *= 0.8;
431    }
432
433    /// Get fragmentation trend based on history
434    pub fn get_trend(&self) -> FragmentationTrend {
435        if self.fragmentation_history.len() < 10 {
436            return FragmentationTrend::Stable;
437        }
438
439        let recent_avg = self.fragmentation_history.iter().rev().take(5).sum::<f64>() / 5.0;
440        let older_avg = self
441            .fragmentation_history
442            .iter()
443            .rev()
444            .skip(5)
445            .take(5)
446            .sum::<f64>()
447            / 5.0;
448
449        if recent_avg > older_avg * 1.1 {
450            FragmentationTrend::Increasing
451        } else if recent_avg < older_avg * 0.9 {
452            FragmentationTrend::Decreasing
453        } else {
454            FragmentationTrend::Stable
455        }
456    }
457}
458
459impl Default for FragmentationManager {
460    fn default() -> Self {
461        Self::new()
462    }
463}
464
465/// Fragmentation trend direction
466#[derive(Debug, Clone, Copy, PartialEq, Eq)]
467pub enum FragmentationTrend {
468    /// Fragmentation is increasing over time
469    Increasing,
470    /// Fragmentation is stable
471    Stable,
472    /// Fragmentation is decreasing over time
473    Decreasing,
474}
475
476/// Pool statistics for monitoring and optimization
477#[derive(Debug, Clone)]
478pub struct PoolStats {
479    /// Total number of buffers in pool
480    pub total_buffers: usize,
481    /// Number of free buffers
482    pub free_buffers: usize,
483    /// Total pool size in bytes
484    pub total_pool_size: usize,
485    /// Current fragmentation ratio (0.0-1.0)
486    pub fragmentation_ratio: f64,
487    /// Cache hit rate (0.0-1.0)
488    pub cache_hit_rate: f64,
489    /// Allocation statistics
490    pub allocation_stats: AllocationStats,
491}
492
493impl PoolStats {
494    /// Get memory efficiency score (0.0 to 1.0)
495    pub fn get_efficiency_score(&self) -> f64 {
496        let utilization = if self.total_buffers == 0 {
497            0.0
498        } else {
499            (self.total_buffers - self.free_buffers) as f64 / self.total_buffers as f64
500        };
501
502        let fragmentation_penalty = 1.0 - self.fragmentation_ratio.min(1.0);
503        let cache_bonus = self.cache_hit_rate;
504
505        (utilization + fragmentation_penalty + cache_bonus) / 3.0
506    }
507}
508
509/// Memory type for different allocation strategies
510#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
511pub enum MemoryType {
512    /// GPU device memory
513    Device,
514    /// Unified memory (accessible by both CPU and GPU)
515    Unified,
516    /// Pinned host memory for fast transfers
517    Pinned,
518    /// Memory-mapped buffers
519    Mapped,
520}
521
522/// GPU memory pool manager for multiple pools
523#[derive(Debug)]
524pub struct GpuMemoryPoolManager {
525    pools: HashMap<MemoryType, AdvancedGpuMemoryPool>,
526    device: GpuDevice,
527    global_stats: AllocationStats,
528}
529
530impl GpuMemoryPoolManager {
531    /// Create a new GPU memory pool manager
532    pub fn new(device: GpuDevice) -> Result<Self> {
533        let mut pools = HashMap::new();
534
535        // Create pools for different memory types
536        for memory_type in [MemoryType::Device, MemoryType::Unified, MemoryType::Pinned] {
537            let config = PoolConfig::default();
538            let pool = AdvancedGpuMemoryPool::new(device.clone(), config);
539            pools.insert(memory_type, pool);
540        }
541
542        Ok(Self {
543            pools,
544            device,
545            global_stats: AllocationStats::default(),
546        })
547    }
548
549    /// Create a memory pool with specific configuration
550    pub fn create_pool(
551        &mut self,
552        total_size: usize,
553        memory_type: MemoryType,
554    ) -> Result<&mut AdvancedGpuMemoryPool> {
555        let mut config = PoolConfig::default();
556        config.max_pool_size = total_size;
557
558        let pool = AdvancedGpuMemoryPool::new(self.device.clone(), config);
559        self.pools.insert(memory_type, pool);
560
561        Ok(self.pools.get_mut(&memory_type).expect("Operation failed"))
562    }
563
564    /// Allocate from specific memory type
565    pub fn allocate(&mut self, size: usize, memory_type: MemoryType) -> Result<PooledBuffer> {
566        let pool = self
567            .pools
568            .get_mut(&memory_type)
569            .ok_or_else(|| IoError::Other(format!("Memory pool {:?} not found", memory_type)))?;
570
571        let buffer = pool.allocate(size)?;
572        self.global_stats.total_allocations += 1;
573        self.global_stats.bytes_allocated += size;
574
575        Ok(buffer)
576    }
577
578    /// Return buffer to appropriate pool
579    pub fn deallocate(&mut self, buffer: PooledBuffer, memory_type: MemoryType) -> Result<()> {
580        let pool = self
581            .pools
582            .get_mut(&memory_type)
583            .ok_or_else(|| IoError::Other(format!("Memory pool {:?} not found", memory_type)))?;
584
585        self.global_stats.total_deallocations += 1;
586        self.global_stats.bytes_deallocated += buffer.metadata.size;
587
588        pool.deallocate(buffer)
589    }
590
591    /// Get global statistics
592    pub fn get_global_stats(&self) -> GlobalPoolStats {
593        let pool_stats: Vec<_> = self
594            .pools
595            .iter()
596            .map(|(&memory_type, pool)| (memory_type, pool.get_stats()))
597            .collect();
598
599        let total_buffers: usize = pool_stats
600            .iter()
601            .map(|(_, stats)| stats.total_buffers)
602            .sum();
603        let total_pool_size: usize = pool_stats
604            .iter()
605            .map(|(_, stats)| stats.total_pool_size)
606            .sum();
607        let avg_fragmentation: f64 = if pool_stats.is_empty() {
608            0.0
609        } else {
610            pool_stats
611                .iter()
612                .map(|(_, stats)| stats.fragmentation_ratio)
613                .sum::<f64>()
614                / pool_stats.len() as f64
615        };
616
617        GlobalPoolStats {
618            total_buffers,
619            total_pool_size,
620            pool_count: self.pools.len(),
621            average_fragmentation: avg_fragmentation,
622            global_allocation_stats: self.global_stats.clone(),
623            pool_stats,
624        }
625    }
626
627    /// Perform garbage collection on all pools
628    pub fn garbage_collect_all(&mut self) -> Result<usize> {
629        let mut total_freed = 0;
630        for pool in self.pools.values_mut() {
631            total_freed += pool.garbage_collect()?;
632        }
633        Ok(total_freed)
634    }
635
636    /// Get the total size of a specific pool
637    pub fn get_pool_size(&self, memory_type: MemoryType) -> usize {
638        self.pools
639            .get(&memory_type)
640            .map(|pool| pool.get_total_pool_size())
641            .unwrap_or(0)
642    }
643}
644
645/// Global statistics across all memory pools
646#[derive(Debug, Clone)]
647pub struct GlobalPoolStats {
648    /// Total number of buffers across all pools
649    pub total_buffers: usize,
650    /// Total size of all pools
651    pub total_pool_size: usize,
652    /// Number of memory pools
653    pub pool_count: usize,
654    /// Average fragmentation across all pools
655    pub average_fragmentation: f64,
656    /// Global allocation statistics
657    pub global_allocation_stats: AllocationStats,
658    /// Per-pool statistics
659    pub pool_stats: Vec<(MemoryType, PoolStats)>,
660}
661
662#[cfg(test)]
663mod tests {
664    use super::*;
665    use scirs2_core::gpu::{GpuBackend, GpuDevice};
666
667    fn create_test_device() -> GpuDevice {
668        // Use CPU backend for testing
669        GpuDevice::new(GpuBackend::Cpu, 0)
670    }
671
672    #[test]
673    fn test_pool_config_defaults() {
674        let config = PoolConfig::default();
675        assert_eq!(config.min_buffer_size, 4096);
676        assert_eq!(config.max_buffer_size, 64 * 1024 * 1024);
677        assert_eq!(config.alignment, 256);
678    }
679
680    #[test]
681    fn test_fragmentation_manager() {
682        let mut manager = FragmentationManager::new();
683        assert_eq!(manager.get_fragmentation_ratio(), 0.0);
684
685        manager.update_fragmentation(0.2, 0.3);
686        assert_eq!(manager.get_fragmentation_ratio(), 0.25);
687
688        assert!(!manager.needs_compaction()); // Should not need compaction yet
689    }
690
691    #[test]
692    fn test_allocation_stats() {
693        let mut stats = AllocationStats::default();
694        stats.cache_hits = 8;
695        stats.cache_misses = 2;
696
697        assert_eq!(stats.get_cache_hit_rate(), 0.8);
698    }
699
700    #[test]
701    fn test_memory_pool_manager_creation() {
702        let device = create_test_device();
703        let manager = GpuMemoryPoolManager::new(device);
704        assert!(manager.is_ok());
705
706        let manager = manager.expect("Operation failed");
707        assert_eq!(manager.pools.len(), 3); // Device, Unified, Pinned
708    }
709
710    #[test]
711    fn test_pool_stats_efficiency() {
712        let stats = PoolStats {
713            total_buffers: 10,
714            free_buffers: 2,
715            total_pool_size: 1024 * 1024,
716            fragmentation_ratio: 0.1,
717            cache_hit_rate: 0.9,
718            allocation_stats: AllocationStats::default(),
719        };
720
721        let efficiency = stats.get_efficiency_score();
722        assert!(efficiency > 0.8); // Should be high efficiency
723    }
724}