Skip to main content

optirs_gpu/memory/allocation/
strategies.rs

1// Allocation strategies for GPU memory management
2//
3// This module provides various allocation strategies optimized for different
4// workload patterns and memory usage scenarios.
5
6use std::collections::{HashMap, VecDeque};
7use std::time::Instant;
8
9/// Available allocation strategies
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
11pub enum AllocationStrategy {
12    /// First-fit allocation (fastest)
13    FirstFit,
14    /// Best-fit allocation (memory efficient)
15    BestFit,
16    /// Worst-fit allocation (reduces fragmentation)
17    WorstFit,
18    /// Buddy system allocation (power-of-2 sizes)
19    BuddySystem,
20    /// Segregated list allocation (size-based pools)
21    SegregatedList,
22    /// Adaptive strategy based on workload
23    #[default]
24    Adaptive,
25    /// Machine learning based allocation
26    MLBased,
27    /// Hybrid strategy combining multiple approaches
28    Hybrid,
29}
30
31/// Memory block representation
32#[derive(Debug, Clone)]
33pub struct MemoryBlock {
34    pub ptr: *mut u8,
35    pub size: usize,
36    pub is_free: bool,
37    pub allocated_at: Option<Instant>,
38    pub last_accessed: Option<Instant>,
39    pub access_count: u64,
40    pub fragmentation_score: f32,
41}
42
43impl MemoryBlock {
44    pub fn new(ptr: *mut u8, size: usize) -> Self {
45        Self {
46            ptr,
47            size,
48            is_free: true,
49            allocated_at: None,
50            last_accessed: None,
51            access_count: 0,
52            fragmentation_score: 0.0,
53        }
54    }
55
56    pub fn mark_used(&mut self) {
57        self.is_free = false;
58        self.allocated_at = Some(Instant::now());
59        self.access_count += 1;
60    }
61
62    pub fn mark_free(&mut self) {
63        self.is_free = true;
64        self.allocated_at = None;
65    }
66
67    pub fn update_access(&mut self) {
68        self.last_accessed = Some(Instant::now());
69        self.access_count += 1;
70    }
71}
72
73/// Allocation event for pattern analysis
74#[derive(Debug, Clone)]
75pub struct AllocationEvent {
76    /// Size of allocation
77    pub size: usize,
78    /// Timestamp of allocation
79    pub timestamp: Instant,
80    /// Whether allocation was satisfied from cache
81    pub cache_hit: bool,
82    /// Allocation latency (microseconds)
83    pub latency_us: u64,
84    /// Thread ID that made the allocation
85    pub thread_id: Option<u64>,
86    /// Kernel context information
87    pub kernel_context: Option<String>,
88}
89
90impl AllocationEvent {
91    pub fn new(size: usize, cache_hit: bool, latency_us: u64) -> Self {
92        Self {
93            size,
94            timestamp: Instant::now(),
95            cache_hit,
96            latency_us,
97            thread_id: None,
98            kernel_context: None,
99        }
100    }
101}
102
103/// Allocation statistics
104#[derive(Debug, Clone, Default)]
105pub struct AllocationStats {
106    pub total_allocations: u64,
107    pub total_deallocations: u64,
108    pub cache_hits: u64,
109    pub cache_misses: u64,
110    pub fragmentation_events: u64,
111    pub total_allocated_bytes: u64,
112    pub peak_allocated_bytes: u64,
113    pub average_allocation_size: f64,
114    pub allocation_latency_ms: f64,
115}
116
117impl AllocationStats {
118    pub fn record_allocation(&mut self, size: usize, cache_hit: bool, latency_us: u64) {
119        self.total_allocations += 1;
120        self.total_allocated_bytes += size as u64;
121
122        if self.total_allocated_bytes > self.peak_allocated_bytes {
123            self.peak_allocated_bytes = self.total_allocated_bytes;
124        }
125
126        if cache_hit {
127            self.cache_hits += 1;
128        } else {
129            self.cache_misses += 1;
130        }
131
132        // Update average allocation size
133        self.average_allocation_size =
134            self.total_allocated_bytes as f64 / self.total_allocations as f64;
135
136        // Update average latency
137        self.allocation_latency_ms = (self.allocation_latency_ms
138            * (self.total_allocations - 1) as f64
139            + latency_us as f64 / 1000.0)
140            / self.total_allocations as f64;
141    }
142
143    pub fn record_deallocation(&mut self, size: usize) {
144        self.total_deallocations += 1;
145        self.total_allocated_bytes = self.total_allocated_bytes.saturating_sub(size as u64);
146    }
147
148    pub fn get_cache_hit_rate(&self) -> f64 {
149        if self.total_allocations == 0 {
150            0.0
151        } else {
152            self.cache_hits as f64 / self.total_allocations as f64
153        }
154    }
155
156    pub fn get_fragmentation_rate(&self) -> f64 {
157        if self.total_allocations == 0 {
158            0.0
159        } else {
160            self.fragmentation_events as f64 / self.total_allocations as f64
161        }
162    }
163}
164
165/// Core allocation strategy implementation
166pub struct AllocationStrategyManager {
167    strategy: AllocationStrategy,
168    free_blocks: HashMap<usize, VecDeque<MemoryBlock>>,
169    allocation_history: VecDeque<AllocationEvent>,
170    stats: AllocationStats,
171    adaptive_config: AdaptiveConfig,
172    hybrid_config: HybridConfig,
173    ml_config: Option<MLConfig>,
174}
175
176/// Configuration for adaptive allocation strategy
177#[derive(Debug, Clone)]
178pub struct AdaptiveConfig {
179    pub history_window: usize,
180    pub small_allocation_threshold: usize,
181    pub large_allocation_threshold: usize,
182    pub fragmentation_threshold: f32,
183    pub enable_pattern_detection: bool,
184    pub adaptation_interval: u64,
185}
186
187impl Default for AdaptiveConfig {
188    fn default() -> Self {
189        Self {
190            history_window: 1000,
191            small_allocation_threshold: 4096,
192            large_allocation_threshold: 1024 * 1024,
193            fragmentation_threshold: 0.3,
194            enable_pattern_detection: true,
195            adaptation_interval: 100,
196        }
197    }
198}
199
200/// Configuration for hybrid allocation strategy
201#[derive(Debug, Clone)]
202pub struct HybridConfig {
203    pub primary_strategy: AllocationStrategy,
204    pub secondary_strategy: AllocationStrategy,
205    pub switch_threshold_fragmentation: f32,
206    pub switch_threshold_utilization: f32,
207    pub evaluation_window: usize,
208}
209
210impl Default for HybridConfig {
211    fn default() -> Self {
212        Self {
213            primary_strategy: AllocationStrategy::BestFit,
214            secondary_strategy: AllocationStrategy::FirstFit,
215            switch_threshold_fragmentation: 0.4,
216            switch_threshold_utilization: 0.8,
217            evaluation_window: 100,
218        }
219    }
220}
221
222/// Configuration for ML-based allocation strategy
223#[derive(Debug, Clone)]
224pub struct MLConfig {
225    pub model_type: MLModelType,
226    pub feature_window: usize,
227    pub training_interval: u64,
228    pub prediction_confidence_threshold: f32,
229    pub fallback_strategy: AllocationStrategy,
230}
231
232#[derive(Debug, Clone)]
233pub enum MLModelType {
234    LinearRegression,
235    DecisionTree,
236    NeuralNetwork,
237    ReinforcementLearning,
238}
239
240impl AllocationStrategyManager {
241    pub fn new(strategy: AllocationStrategy) -> Self {
242        Self {
243            strategy,
244            free_blocks: HashMap::new(),
245            allocation_history: VecDeque::new(),
246            stats: AllocationStats::default(),
247            adaptive_config: AdaptiveConfig::default(),
248            hybrid_config: HybridConfig::default(),
249            ml_config: None,
250        }
251    }
252
253    pub fn with_adaptive_config(mut self, config: AdaptiveConfig) -> Self {
254        self.adaptive_config = config;
255        self
256    }
257
258    pub fn with_hybrid_config(mut self, config: HybridConfig) -> Self {
259        self.hybrid_config = config;
260        self
261    }
262
263    pub fn with_ml_config(mut self, config: MLConfig) -> Self {
264        self.ml_config = Some(config);
265        self
266    }
267
268    /// Find free block using the configured allocation strategy
269    pub fn find_free_block(&mut self, size: usize) -> Option<*mut u8> {
270        let start_time = Instant::now();
271
272        let result = match self.strategy {
273            AllocationStrategy::FirstFit => self.find_first_fit(size),
274            AllocationStrategy::BestFit => self.find_best_fit(size),
275            AllocationStrategy::WorstFit => self.find_worst_fit(size),
276            AllocationStrategy::BuddySystem => self.find_buddy_block(size),
277            AllocationStrategy::SegregatedList => self.find_segregated_block(size),
278            AllocationStrategy::Adaptive => self.find_adaptive_block(size),
279            AllocationStrategy::MLBased => self.find_ml_based_block(size),
280            AllocationStrategy::Hybrid => self.find_hybrid_block(size),
281        };
282
283        let latency_us = start_time.elapsed().as_micros() as u64;
284        let cache_hit = result.is_some();
285
286        self.stats.record_allocation(size, cache_hit, latency_us);
287        self.allocation_history
288            .push_back(AllocationEvent::new(size, cache_hit, latency_us));
289
290        // Maintain history window
291        if self.allocation_history.len() > self.adaptive_config.history_window {
292            self.allocation_history.pop_front();
293        }
294
295        result
296    }
297
298    /// First-fit allocation: Find first block that fits
299    pub fn find_first_fit(&mut self, size: usize) -> Option<*mut u8> {
300        for (&block_size, blocks) in &mut self.free_blocks {
301            if block_size >= size && !blocks.is_empty() {
302                if let Some(mut block) = blocks.pop_front() {
303                    block.mark_used();
304                    return Some(block.ptr);
305                }
306            }
307        }
308        None
309    }
310
311    /// Best-fit allocation: Find smallest block that fits
312    pub fn find_best_fit(&mut self, size: usize) -> Option<*mut u8> {
313        let mut best_size = None;
314        let mut best_fit_size = usize::MAX;
315
316        for (&block_size, blocks) in &self.free_blocks {
317            if block_size >= size && block_size < best_fit_size && !blocks.is_empty() {
318                best_fit_size = block_size;
319                best_size = Some(block_size);
320            }
321        }
322
323        if let Some(block_size) = best_size {
324            if let Some(blocks) = self.free_blocks.get_mut(&block_size) {
325                if let Some(mut block) = blocks.pop_front() {
326                    block.mark_used();
327                    return Some(block.ptr);
328                }
329            }
330        }
331
332        None
333    }
334
335    /// Worst-fit allocation: Find largest block that fits (reduces fragmentation)
336    pub fn find_worst_fit(&mut self, size: usize) -> Option<*mut u8> {
337        let mut worst_size = None;
338        let mut worst_fit_size = 0;
339
340        for (&block_size, blocks) in &self.free_blocks {
341            if block_size >= size && block_size > worst_fit_size && !blocks.is_empty() {
342                worst_fit_size = block_size;
343                worst_size = Some(block_size);
344            }
345        }
346
347        if let Some(block_size) = worst_size {
348            if let Some(blocks) = self.free_blocks.get_mut(&block_size) {
349                if let Some(mut block) = blocks.pop_front() {
350                    block.mark_used();
351                    return Some(block.ptr);
352                }
353            }
354        }
355
356        None
357    }
358
359    /// Buddy system allocation: Find power-of-2 sized block
360    pub fn find_buddy_block(&mut self, size: usize) -> Option<*mut u8> {
361        let buddy_size = size.next_power_of_two();
362
363        if let Some(blocks) = self.free_blocks.get_mut(&buddy_size) {
364            if let Some(mut block) = blocks.pop_front() {
365                block.mark_used();
366                return Some(block.ptr);
367            }
368        }
369
370        None
371    }
372
373    /// Segregated list allocation: Different size classes
374    pub fn find_segregated_block(&mut self, size: usize) -> Option<*mut u8> {
375        let size_class = self.get_size_class(size);
376
377        // Search from the appropriate size class upwards
378        let mut search_sizes: Vec<usize> = self
379            .free_blocks
380            .keys()
381            .filter(|&&s| s >= size_class)
382            .cloned()
383            .collect();
384        search_sizes.sort();
385
386        for class_size in search_sizes {
387            if let Some(blocks) = self.free_blocks.get_mut(&class_size) {
388                if let Some(mut block) = blocks.pop_front() {
389                    block.mark_used();
390                    return Some(block.ptr);
391                }
392            }
393        }
394
395        None
396    }
397
398    /// Adaptive allocation based on allocation patterns and workload analysis
399    pub fn find_adaptive_block(&mut self, size: usize) -> Option<*mut u8> {
400        // Analyze recent allocation patterns
401        let pattern = self.analyze_allocation_patterns();
402
403        // Choose strategy based on pattern analysis
404        let chosen_strategy = match pattern {
405            AllocationPattern::SmallFrequent => AllocationStrategy::FirstFit,
406            AllocationPattern::LargeInfrequent => AllocationStrategy::BestFit,
407            AllocationPattern::Mixed => AllocationStrategy::WorstFit,
408            AllocationPattern::Sequential => AllocationStrategy::SegregatedList,
409            AllocationPattern::Random => AllocationStrategy::BuddySystem,
410            AllocationPattern::Unknown => AllocationStrategy::BestFit,
411        };
412
413        // Apply the chosen strategy
414        match chosen_strategy {
415            AllocationStrategy::FirstFit => self.find_first_fit(size),
416            AllocationStrategy::BestFit => self.find_best_fit(size),
417            AllocationStrategy::WorstFit => self.find_worst_fit(size),
418            AllocationStrategy::SegregatedList => self.find_segregated_block(size),
419            AllocationStrategy::BuddySystem => self.find_buddy_block(size),
420            _ => self.find_best_fit(size), // Fallback
421        }
422    }
423
424    /// ML-based allocation using learned patterns
425    pub fn find_ml_based_block(&mut self, size: usize) -> Option<*mut u8> {
426        if let Some(ml_config) = self.ml_config.clone() {
427            // Extract features for ML prediction
428            let features = self.extract_ml_features(size);
429
430            // Make prediction (simplified - would use actual ML model)
431            let prediction = self.predict_best_strategy(&features, &ml_config);
432
433            // Apply predicted strategy if confidence is high enough
434            if prediction.confidence >= ml_config.prediction_confidence_threshold as f64 {
435                match prediction.strategy {
436                    AllocationStrategy::FirstFit => self.find_first_fit(size),
437                    AllocationStrategy::BestFit => self.find_best_fit(size),
438                    AllocationStrategy::WorstFit => self.find_worst_fit(size),
439                    AllocationStrategy::BuddySystem => self.find_buddy_block(size),
440                    AllocationStrategy::SegregatedList => self.find_segregated_block(size),
441                    _ => self.apply_fallback_strategy(size, &ml_config.fallback_strategy),
442                }
443            } else {
444                // Fall back to configured fallback strategy
445                self.apply_fallback_strategy(size, &ml_config.fallback_strategy)
446            }
447        } else {
448            // No ML config, fall back to best fit
449            self.find_best_fit(size)
450        }
451    }
452
453    /// Hybrid allocation combining multiple strategies
454    pub fn find_hybrid_block(&mut self, size: usize) -> Option<*mut u8> {
455        // Evaluate current memory state
456        let fragmentation_level = self.calculate_fragmentation_level();
457        let utilization_level = self.calculate_utilization_level();
458
459        // Choose primary or secondary strategy based on thresholds
460        let chosen_strategy = if fragmentation_level
461            > self.hybrid_config.switch_threshold_fragmentation as f64
462            || utilization_level > self.hybrid_config.switch_threshold_utilization as f64
463        {
464            self.hybrid_config.secondary_strategy.clone()
465        } else {
466            self.hybrid_config.primary_strategy.clone()
467        };
468
469        // Apply chosen strategy
470        self.apply_fallback_strategy(size, &chosen_strategy)
471    }
472
473    fn apply_fallback_strategy(
474        &mut self,
475        size: usize,
476        strategy: &AllocationStrategy,
477    ) -> Option<*mut u8> {
478        match strategy {
479            AllocationStrategy::FirstFit => self.find_first_fit(size),
480            AllocationStrategy::BestFit => self.find_best_fit(size),
481            AllocationStrategy::WorstFit => self.find_worst_fit(size),
482            AllocationStrategy::BuddySystem => self.find_buddy_block(size),
483            AllocationStrategy::SegregatedList => self.find_segregated_block(size),
484            AllocationStrategy::Adaptive => self.find_adaptive_block(size),
485            _ => self.find_best_fit(size), // Ultimate fallback
486        }
487    }
488
489    /// Analyze allocation patterns from history
490    pub fn analyze_allocation_patterns(&self) -> AllocationPattern {
491        if self.allocation_history.len() < 10 {
492            return AllocationPattern::Unknown;
493        }
494
495        let recent_history: Vec<&AllocationEvent> =
496            self.allocation_history.iter().rev().take(50).collect();
497
498        // Analyze size distribution
499        let sizes: Vec<usize> = recent_history.iter().map(|e| e.size).collect();
500        let small_count = sizes
501            .iter()
502            .filter(|&&s| s < self.adaptive_config.small_allocation_threshold)
503            .count();
504        let large_count = sizes
505            .iter()
506            .filter(|&&s| s > self.adaptive_config.large_allocation_threshold)
507            .count();
508
509        // Analyze temporal patterns
510        let time_diffs: Vec<u128> = recent_history
511            .windows(2)
512            .map(|w| w[0].timestamp.duration_since(w[1].timestamp).as_millis())
513            .collect();
514        let avg_interval = if !time_diffs.is_empty() {
515            time_diffs.iter().sum::<u128>() / time_diffs.len() as u128
516        } else {
517            0
518        };
519
520        // Pattern classification
521        if small_count > recent_history.len() * 8 / 10 && avg_interval < 100 {
522            AllocationPattern::SmallFrequent
523        } else if large_count > recent_history.len() / 2 {
524            AllocationPattern::LargeInfrequent
525        } else if self.is_sequential_pattern(&sizes) {
526            AllocationPattern::Sequential
527        } else if self.is_random_pattern(&sizes) {
528            AllocationPattern::Random
529        } else {
530            AllocationPattern::Mixed
531        }
532    }
533
534    fn is_sequential_pattern(&self, sizes: &[usize]) -> bool {
535        if sizes.len() < 3 {
536            return false;
537        }
538
539        let mut increasing = 0;
540        let mut decreasing = 0;
541
542        for window in sizes.windows(2) {
543            if window[1] > window[0] {
544                increasing += 1;
545            } else if window[1] < window[0] {
546                decreasing += 1;
547            }
548        }
549
550        // Consider sequential if more than 70% follow a trend
551        let trend_ratio = (increasing.max(decreasing) as f64) / (sizes.len() - 1) as f64;
552        trend_ratio > 0.7
553    }
554
555    fn is_random_pattern(&self, sizes: &[usize]) -> bool {
556        if sizes.len() < 5 {
557            return false;
558        }
559
560        // Calculate coefficient of variation
561        let mean = sizes.iter().sum::<usize>() as f64 / sizes.len() as f64;
562        let variance = sizes
563            .iter()
564            .map(|&s| (s as f64 - mean).powi(2))
565            .sum::<f64>()
566            / sizes.len() as f64;
567        let std_dev = variance.sqrt();
568        let cv = std_dev / mean;
569
570        // High coefficient of variation indicates randomness
571        cv > 0.5
572    }
573
574    /// Extract features for ML-based allocation
575    fn extract_ml_features(&self, size: usize) -> MLFeatures {
576        let recent_history: Vec<&AllocationEvent> = self
577            .allocation_history
578            .iter()
579            .rev()
580            .take(
581                self.ml_config
582                    .as_ref()
583                    .map(|c| c.feature_window)
584                    .unwrap_or(20),
585            )
586            .collect();
587
588        let avg_size = if !recent_history.is_empty() {
589            recent_history.iter().map(|e| e.size).sum::<usize>() as f64
590                / recent_history.len() as f64
591        } else {
592            0.0
593        };
594
595        let avg_latency = if !recent_history.is_empty() {
596            recent_history.iter().map(|e| e.latency_us).sum::<u64>() as f64
597                / recent_history.len() as f64
598        } else {
599            0.0
600        };
601
602        MLFeatures {
603            requested_size: size as f64,
604            avg_recent_size: avg_size,
605            avg_recent_latency: avg_latency,
606            cache_hit_rate: self.stats.get_cache_hit_rate(),
607            fragmentation_level: self.calculate_fragmentation_level(),
608            utilization_level: self.calculate_utilization_level(),
609            allocation_frequency: recent_history.len() as f64,
610        }
611    }
612
613    /// Predict best allocation strategy using ML
614    fn predict_best_strategy(&self, features: &MLFeatures, ml_config: &MLConfig) -> MLPrediction {
615        // Simplified ML prediction - in real implementation would use trained model
616        let score_first_fit =
617            self.score_strategy_for_features(features, &AllocationStrategy::FirstFit);
618        let score_best_fit =
619            self.score_strategy_for_features(features, &AllocationStrategy::BestFit);
620        let score_worst_fit =
621            self.score_strategy_for_features(features, &AllocationStrategy::WorstFit);
622        let score_buddy =
623            self.score_strategy_for_features(features, &AllocationStrategy::BuddySystem);
624        let score_segregated =
625            self.score_strategy_for_features(features, &AllocationStrategy::SegregatedList);
626
627        let mut best_strategy = AllocationStrategy::BestFit;
628        let mut _best_score = score_best_fit;
629        let mut confidence = 0.5;
630
631        if score_first_fit > _best_score {
632            best_strategy = AllocationStrategy::FirstFit;
633            _best_score = score_first_fit;
634        }
635        if score_worst_fit > _best_score {
636            best_strategy = AllocationStrategy::WorstFit;
637            _best_score = score_worst_fit;
638        }
639        if score_buddy > _best_score {
640            best_strategy = AllocationStrategy::BuddySystem;
641            _best_score = score_buddy;
642        }
643        if score_segregated > _best_score {
644            best_strategy = AllocationStrategy::SegregatedList;
645            _best_score = score_segregated;
646        }
647
648        // Calculate confidence based on score difference
649        let mut scores = [
650            score_first_fit,
651            score_best_fit,
652            score_worst_fit,
653            score_buddy,
654            score_segregated,
655        ];
656        scores.sort_by(|a, b| b.total_cmp(a));
657        if scores.len() >= 2 {
658            confidence = (scores[0] - scores[1]).clamp(0.0, 1.0);
659        }
660
661        // A prediction this uncertain (top two strategies scored too close
662        // together) is not worth trusting over the caller's configured
663        // fallback.
664        let strategy = if (confidence as f32) < ml_config.prediction_confidence_threshold {
665            ml_config.fallback_strategy.clone()
666        } else {
667            best_strategy
668        };
669
670        MLPrediction {
671            strategy,
672            confidence,
673            predicted_latency: features.avg_recent_latency,
674        }
675    }
676
677    fn score_strategy_for_features(
678        &self,
679        features: &MLFeatures,
680        strategy: &AllocationStrategy,
681    ) -> f64 {
682        // Simplified scoring function - would be learned from data
683        match strategy {
684            AllocationStrategy::FirstFit => {
685                // First fit is good for high frequency, small allocations
686                let size_score = if features.requested_size < 4096.0 {
687                    0.8
688                } else {
689                    0.3
690                };
691                let freq_score = if features.allocation_frequency > 10.0 {
692                    0.9
693                } else {
694                    0.4
695                };
696                (size_score + freq_score) / 2.0
697            }
698            AllocationStrategy::BestFit => {
699                // Best fit is good for memory efficiency
700                let util_score = if features.utilization_level > 0.7 {
701                    0.9
702                } else {
703                    0.6
704                };
705                let frag_score = if features.fragmentation_level < 0.3 {
706                    0.8
707                } else {
708                    0.4
709                };
710                (util_score + frag_score) / 2.0
711            }
712            AllocationStrategy::WorstFit => {
713                // Worst fit is good for reducing fragmentation
714
715                if features.fragmentation_level > 0.4 {
716                    0.8
717                } else {
718                    0.3
719                }
720            }
721            AllocationStrategy::BuddySystem => {
722                // Buddy system is good for power-of-2 sizes
723
724                if features.requested_size.log2().fract() < 0.1 {
725                    0.9
726                } else {
727                    0.4
728                }
729            }
730            AllocationStrategy::SegregatedList
731                // Segregated lists are good for diverse sizes with patterns
732
733                if features.cache_hit_rate > 0.6 => {
734                    0.7
735                }
736            _ => 0.5, // Default score
737        }
738    }
739
740    fn calculate_fragmentation_level(&self) -> f64 {
741        // Simplified fragmentation calculation
742        if self.free_blocks.is_empty() {
743            return 0.0;
744        }
745
746        let total_free_space: usize = self
747            .free_blocks
748            .iter()
749            .map(|(size, blocks)| size * blocks.len())
750            .sum();
751
752        let free_block_count: usize = self.free_blocks.values().map(|blocks| blocks.len()).sum();
753
754        if total_free_space == 0 {
755            0.0
756        } else {
757            let avg_block_size = total_free_space as f64 / free_block_count as f64;
758            let fragmentation = 1.0 - (avg_block_size / total_free_space as f64);
759            fragmentation.clamp(0.0, 1.0)
760        }
761    }
762
763    fn calculate_utilization_level(&self) -> f64 {
764        // Simplified utilization calculation based on allocation stats
765        let total_capacity = self.stats.peak_allocated_bytes as f64;
766        let current_allocated = self.stats.total_allocated_bytes as f64;
767
768        if total_capacity == 0.0 {
769            0.0
770        } else {
771            (current_allocated / total_capacity).clamp(0.0, 1.0)
772        }
773    }
774
775    /// Get size class for segregated list allocation
776    pub fn get_size_class(&self, size: usize) -> usize {
777        match size {
778            0..=256 => 256,
779            257..=512 => 512,
780            513..=1024 => 1024,
781            1025..=2048 => 2048,
782            2049..=4096 => 4096,
783            4097..=8192 => 8192,
784            8193..=16384 => 16384,
785            16385..=32768 => 32768,
786            32769..=65536 => 65536,
787            65537..=131072 => 131072,
788            131073..=262144 => 262144,
789            262145..=524288 => 524288,
790            524289..=1048576 => 1048576,
791            _ => size.next_power_of_two(),
792        }
793    }
794
795    /// Add free block to the pool
796    pub fn add_free_block(&mut self, block: MemoryBlock) {
797        let size_class = self.get_size_class(block.size);
798        self.free_blocks
799            .entry(size_class)
800            .or_default()
801            .push_back(block);
802    }
803
804    /// Remove free block from the pool
805    pub fn remove_free_block(&mut self, size: usize, ptr: *mut u8) -> Option<MemoryBlock> {
806        let size_class = self.get_size_class(size);
807        if let Some(blocks) = self.free_blocks.get_mut(&size_class) {
808            if let Some(pos) = blocks.iter().position(|block| block.ptr == ptr) {
809                return blocks.remove(pos);
810            }
811        }
812        None
813    }
814
815    /// Get current allocation statistics
816    pub fn get_stats(&self) -> &AllocationStats {
817        &self.stats
818    }
819
820    /// Get current allocation strategy
821    pub fn get_strategy(&self) -> &AllocationStrategy {
822        &self.strategy
823    }
824
825    /// Set new allocation strategy
826    pub fn set_strategy(&mut self, strategy: AllocationStrategy) {
827        self.strategy = strategy;
828    }
829
830    /// Clear allocation history
831    pub fn clear_history(&mut self) {
832        self.allocation_history.clear();
833    }
834
835    /// Get allocation history
836    pub fn get_history(&self) -> &VecDeque<AllocationEvent> {
837        &self.allocation_history
838    }
839}
840
841/// Allocation pattern analysis results
842#[derive(Debug, Clone, PartialEq, Eq, Hash)]
843pub enum AllocationPattern {
844    SmallFrequent,
845    LargeInfrequent,
846    Mixed,
847    Sequential,
848    Random,
849    Unknown,
850}
851
852/// Features for ML-based allocation
853#[derive(Debug, Clone)]
854pub struct MLFeatures {
855    pub requested_size: f64,
856    pub avg_recent_size: f64,
857    pub avg_recent_latency: f64,
858    pub cache_hit_rate: f64,
859    pub fragmentation_level: f64,
860    pub utilization_level: f64,
861    pub allocation_frequency: f64,
862}
863
864/// ML prediction result
865#[derive(Debug, Clone)]
866pub struct MLPrediction {
867    pub strategy: AllocationStrategy,
868    pub confidence: f64,
869    pub predicted_latency: f64,
870}
871
872#[cfg(test)]
873mod tests {
874    use super::*;
875
876    #[test]
877    fn test_predict_best_strategy_falls_back_below_confidence_threshold() {
878        let manager = AllocationStrategyManager::new(AllocationStrategy::BestFit);
879        let features = MLFeatures {
880            requested_size: 8192.0,
881            avg_recent_size: 8192.0,
882            avg_recent_latency: 10.0,
883            cache_hit_rate: 0.5,
884            fragmentation_level: 0.2,
885            utilization_level: 0.5,
886            allocation_frequency: 10.0,
887        };
888
889        // A threshold above 1.0 (confidence is clamped to [0, 1]) can never
890        // be met, so the prediction must always report the configured
891        // fallback rather than a "best" pick it isn't actually confident in.
892        let unreachable_threshold = MLConfig {
893            model_type: MLModelType::LinearRegression,
894            feature_window: 10,
895            training_interval: 100,
896            prediction_confidence_threshold: 1.1,
897            fallback_strategy: AllocationStrategy::WorstFit,
898        };
899        let prediction = manager.predict_best_strategy(&features, &unreachable_threshold);
900        assert_eq!(prediction.strategy, AllocationStrategy::WorstFit);
901
902        // A threshold of 0.0 is always met, so the prediction must report
903        // whatever scored highest rather than the fallback.
904        let always_reachable = MLConfig {
905            prediction_confidence_threshold: 0.0,
906            fallback_strategy: AllocationStrategy::WorstFit,
907            ..unreachable_threshold
908        };
909        let prediction = manager.predict_best_strategy(&features, &always_reachable);
910        assert_ne!(prediction.strategy, AllocationStrategy::WorstFit);
911    }
912
913    #[test]
914    fn test_allocation_strategies() {
915        let mut manager = AllocationStrategyManager::new(AllocationStrategy::BestFit);
916
917        // Add some free blocks
918        for i in 0..5 {
919            let block = MemoryBlock::new((i * 1024) as *mut u8, 1024 * (i + 1));
920            manager.add_free_block(block);
921        }
922
923        // Test best fit allocation
924        let ptr = manager.find_free_block(1500);
925        assert!(ptr.is_some());
926
927        // Test statistics
928        let stats = manager.get_stats();
929        assert_eq!(stats.total_allocations, 1);
930    }
931
932    #[test]
933    fn test_size_classes() {
934        let manager = AllocationStrategyManager::new(AllocationStrategy::SegregatedList);
935
936        assert_eq!(manager.get_size_class(100), 256);
937        assert_eq!(manager.get_size_class(300), 512);
938        assert_eq!(manager.get_size_class(1000), 1024);
939        assert_eq!(manager.get_size_class(2000000), 2097152);
940    }
941
942    #[test]
943    fn test_adaptive_strategy() {
944        let mut manager = AllocationStrategyManager::new(AllocationStrategy::Adaptive);
945
946        // Simulate small frequent allocations
947        for _ in 0..20 {
948            let event = AllocationEvent::new(256, true, 10);
949            manager.allocation_history.push_back(event);
950        }
951
952        let pattern = manager.analyze_allocation_patterns();
953        assert_eq!(pattern, AllocationPattern::SmallFrequent);
954    }
955
956    #[test]
957    fn test_fragmentation_calculation() {
958        let mut manager = AllocationStrategyManager::new(AllocationStrategy::BestFit);
959
960        // Add blocks of different sizes
961        manager.add_free_block(MemoryBlock::new(0x1000 as *mut u8, 1024));
962        manager.add_free_block(MemoryBlock::new(0x2000 as *mut u8, 2048));
963        manager.add_free_block(MemoryBlock::new(0x3000 as *mut u8, 512));
964
965        let fragmentation = manager.calculate_fragmentation_level();
966        assert!((0.0..=1.0).contains(&fragmentation));
967    }
968}