runmat_gc/
generations.rs

1//! Generational heap management
2//!
3//! Manages the heap layout and organization across multiple generations,
4//! handling object aging, promotion, and generation-specific optimizations.
5
6use crate::{GcConfig, GcError, Result};
7use std::collections::VecDeque;
8use std::sync::atomic::{AtomicUsize, Ordering};
9use std::time::{Duration, Instant};
10
11/// Represents a single generation in the generational heap
12#[derive(Debug)]
13pub struct Generation {
14    /// Generation number (0 = youngest)
15    pub number: usize,
16
17    /// Current size in bytes
18    current_size: AtomicUsize,
19
20    /// Maximum size in bytes
21    max_size: usize,
22
23    /// Number of collections this generation has experienced
24    collection_count: AtomicUsize,
25
26    /// Objects that have survived collections
27    survivor_count: AtomicUsize,
28
29    /// Time of last collection
30    last_collection: parking_lot::Mutex<Option<Instant>>,
31
32    /// Age threshold for promotion to next generation
33    promotion_threshold: usize,
34
35    /// Collection frequency (for adaptive scheduling)
36    collection_frequency: parking_lot::Mutex<VecDeque<Instant>>,
37}
38
39impl Generation {
40    pub fn new(number: usize, max_size: usize, promotion_threshold: usize) -> Self {
41        Self {
42            number,
43            current_size: AtomicUsize::new(0),
44            max_size,
45            collection_count: AtomicUsize::new(0),
46            survivor_count: AtomicUsize::new(0),
47            last_collection: parking_lot::Mutex::new(None),
48            promotion_threshold,
49            collection_frequency: parking_lot::Mutex::new(VecDeque::new()),
50        }
51    }
52
53    /// Get current size
54    pub fn current_size(&self) -> usize {
55        self.current_size.load(Ordering::Relaxed)
56    }
57
58    /// Get maximum size
59    pub fn max_size(&self) -> usize {
60        self.max_size
61    }
62
63    /// Get utilization as a percentage
64    pub fn utilization(&self) -> f64 {
65        self.current_size() as f64 / self.max_size as f64
66    }
67
68    /// Check if generation is full
69    pub fn is_full(&self, threshold: f64) -> bool {
70        self.utilization() >= threshold
71    }
72
73    /// Allocate bytes in this generation
74    pub fn allocate(&self, size: usize) -> Result<()> {
75        let current = self.current_size.load(Ordering::Relaxed);
76        if current + size > self.max_size {
77            return Err(GcError::OutOfMemory(format!(
78                "Generation {} cannot allocate {} bytes",
79                self.number, size
80            )));
81        }
82
83        self.current_size.fetch_add(size, Ordering::Relaxed);
84        Ok(())
85    }
86
87    /// Deallocate bytes from this generation
88    pub fn deallocate(&self, size: usize) {
89        self.current_size
90            .fetch_sub(size.min(self.current_size()), Ordering::Relaxed);
91    }
92
93    /// Record a collection in this generation
94    pub fn record_collection(&self, objects_collected: usize, survivors: usize) {
95        self.collection_count.fetch_add(1, Ordering::Relaxed);
96        self.survivor_count.store(survivors, Ordering::Relaxed);
97
98        let now = Instant::now();
99        *self.last_collection.lock() = Some(now);
100
101        // Track collection frequency
102        let mut frequency = self.collection_frequency.lock();
103        frequency.push_back(now);
104
105        // Keep only recent collections (last 60 seconds)
106        let cutoff = now - Duration::from_secs(60);
107        while frequency.front().is_some_and(|&t| t < cutoff) {
108            frequency.pop_front();
109        }
110
111        log::debug!(
112            "Generation {} collection: {} collected, {} survivors",
113            self.number,
114            objects_collected,
115            survivors
116        );
117    }
118
119    /// Get collection count
120    pub fn collection_count(&self) -> usize {
121        self.collection_count.load(Ordering::Relaxed)
122    }
123
124    /// Get survivor count from last collection
125    pub fn survivor_count(&self) -> usize {
126        self.survivor_count.load(Ordering::Relaxed)
127    }
128
129    /// Check if objects should be promoted based on survival count
130    pub fn should_promote(&self, object_age: usize) -> bool {
131        object_age >= self.promotion_threshold
132    }
133
134    /// Get time since last collection
135    pub fn time_since_last_collection(&self) -> Option<Duration> {
136        self.last_collection.lock().map(|time| time.elapsed())
137    }
138
139    /// Get collection frequency (collections per minute)
140    pub fn collection_frequency(&self) -> f64 {
141        let frequency = self.collection_frequency.lock();
142        if frequency.len() < 2 {
143            return 0.0;
144        }
145
146        let duration = frequency
147            .back()
148            .unwrap()
149            .duration_since(*frequency.front().unwrap());
150        if duration.as_secs_f64() == 0.0 {
151            return 0.0;
152        }
153
154        frequency.len() as f64 / (duration.as_secs_f64() / 60.0)
155    }
156
157    /// Resize the generation
158    pub fn resize(&mut self, new_max_size: usize) -> Result<()> {
159        if new_max_size < self.current_size() {
160            return Err(GcError::ConfigError(format!(
161                "Cannot shrink generation {} below current size",
162                self.number
163            )));
164        }
165
166        log::info!(
167            "Resizing generation {} from {} to {} bytes",
168            self.number,
169            self.max_size,
170            new_max_size
171        );
172        self.max_size = new_max_size;
173        Ok(())
174    }
175
176    /// Reset generation state (after major collection)
177    pub fn reset(&self) {
178        self.current_size.store(0, Ordering::Relaxed);
179        self.survivor_count.store(0, Ordering::Relaxed);
180        // Don't reset collection_count as it's cumulative
181    }
182
183    /// Get generation statistics
184    pub fn stats(&self) -> GenerationStats {
185        GenerationStats {
186            number: self.number,
187            current_size: self.current_size(),
188            max_size: self.max_size,
189            utilization: self.utilization(),
190            collection_count: self.collection_count(),
191            survivor_count: self.survivor_count(),
192            promotion_threshold: self.promotion_threshold,
193            time_since_last_collection: self.time_since_last_collection(),
194            collection_frequency: self.collection_frequency(),
195        }
196    }
197}
198
199/// Statistics for a generation
200#[derive(Debug, Clone)]
201pub struct GenerationStats {
202    pub number: usize,
203    pub current_size: usize,
204    pub max_size: usize,
205    pub utilization: f64,
206    pub collection_count: usize,
207    pub survivor_count: usize,
208    pub promotion_threshold: usize,
209    pub time_since_last_collection: Option<Duration>,
210    pub collection_frequency: f64,
211}
212
213/// Manages all generations in the heap
214pub struct GenerationalHeap {
215    /// All generations (ordered from young to old)
216    generations: Vec<Generation>,
217
218    /// Configuration
219    config: GcConfig,
220
221    /// Total heap size limit
222    total_size_limit: usize,
223
224    /// Adaptive sizing enabled
225    adaptive_sizing: bool,
226
227    /// Statistics
228    total_promotions: AtomicUsize,
229    total_demotions: AtomicUsize,
230}
231
232impl GenerationalHeap {
233    pub fn new(config: &GcConfig) -> Self {
234        let mut generations = Vec::new();
235
236        // Create generations with exponentially increasing sizes
237        let mut gen_size = config.young_generation_size;
238        for i in 0..config.num_generations {
239            generations.push(Generation::new(i, gen_size, config.promotion_threshold));
240            gen_size *= 2; // Each generation is twice as large as the previous
241        }
242
243        let total_size_limit = if config.max_heap_size > 0 {
244            config.max_heap_size
245        } else {
246            // Calculate total based on generation sizes
247            let mut total = config.young_generation_size;
248            let mut size = config.young_generation_size;
249            for _ in 1..config.num_generations {
250                size *= 2;
251                total += size;
252            }
253            total
254        };
255
256        Self {
257            generations,
258            config: config.clone(),
259            total_size_limit,
260            adaptive_sizing: true,
261            total_promotions: AtomicUsize::new(0),
262            total_demotions: AtomicUsize::new(0),
263        }
264    }
265
266    /// Get a specific generation
267    pub fn generation(&self, number: usize) -> Option<&Generation> {
268        self.generations.get(number)
269    }
270
271    /// Get the young generation (generation 0)
272    pub fn young_generation(&self) -> &Generation {
273        &self.generations[0]
274    }
275
276    /// Get all generations
277    pub fn generations(&self) -> &[Generation] {
278        &self.generations
279    }
280
281    /// Get number of generations
282    pub fn num_generations(&self) -> usize {
283        self.generations.len()
284    }
285
286    /// Check if minor GC should be triggered
287    pub fn should_collect_minor(&self) -> bool {
288        self.young_generation()
289            .is_full(self.config.minor_gc_threshold)
290    }
291
292    /// Check if major GC should be triggered
293    pub fn should_collect_major(&self) -> bool {
294        self.total_utilization() >= self.config.major_gc_threshold
295    }
296
297    /// Get total heap utilization
298    pub fn total_utilization(&self) -> f64 {
299        let total_used: usize = self.generations.iter().map(|g| g.current_size()).sum();
300
301        total_used as f64 / self.total_size_limit as f64
302    }
303
304    /// Get total current size
305    pub fn total_current_size(&self) -> usize {
306        self.generations.iter().map(|g| g.current_size()).sum()
307    }
308
309    /// Get total maximum size
310    pub fn total_max_size(&self) -> usize {
311        self.generations.iter().map(|g| g.max_size()).sum()
312    }
313
314    /// Record object promotion between generations
315    pub fn record_promotion(&self, _from_gen: usize, _to_gen: usize, _count: usize) {
316        self.total_promotions.fetch_add(_count, Ordering::Relaxed);
317
318        log::trace!("Promoted {_count} objects from generation {_from_gen} to {_to_gen}");
319    }
320
321    /// Record object demotion (rare, but possible in some algorithms)
322    pub fn record_demotion(&self, _from_gen: usize, _to_gen: usize, _count: usize) {
323        self.total_demotions.fetch_add(_count, Ordering::Relaxed);
324
325        log::trace!("Demoted {_count} objects from generation {_from_gen} to {_to_gen}");
326    }
327
328    /// Adapt generation sizes based on allocation patterns
329    pub fn adapt_generation_sizes(&mut self) -> Result<()> {
330        if !self.adaptive_sizing {
331            return Ok(());
332        }
333
334        log::debug!("Adapting generation sizes based on allocation patterns");
335
336        // Simple adaptive strategy:
337        // - If young generation is collecting too frequently, increase its size
338        // - If old generations are mostly empty, shrink them
339
340        let young_gen_freq = self.young_generation().collection_frequency();
341        let young_gen_util = self.young_generation().utilization();
342
343        // If collecting more than 10 times per minute and utilization > 80%
344        if young_gen_freq > 10.0 && young_gen_util > 0.8 {
345            let new_size = (self.generations[0].max_size() as f64 * 1.2) as usize;
346            if new_size <= self.total_size_limit / 4 {
347                // Don't let young gen be > 25% of total
348                log::info!("Increasing young generation size to {new_size} bytes");
349                self.generations[0].max_size = new_size;
350            }
351        }
352
353        // Check old generations for underutilization
354        for i in 1..self.generations.len() {
355            let gen = &self.generations[i];
356            if gen.utilization() < 0.2 && gen.max_size() > self.config.young_generation_size {
357                let new_size = (gen.max_size() as f64 * 0.9) as usize;
358                log::info!("Decreasing generation {i} size to {new_size} bytes");
359                self.generations[i].max_size = new_size.max(self.config.young_generation_size);
360            }
361        }
362
363        Ok(())
364    }
365
366    /// Reconfigure the heap
367    pub fn reconfigure(&mut self, config: &GcConfig) -> Result<()> {
368        // Validate that we can accommodate the new configuration
369        if config.num_generations != self.generations.len() {
370            return Err(GcError::ConfigError(
371                "Cannot change number of generations at runtime".to_string(),
372            ));
373        }
374
375        // Update promotion thresholds
376        for gen in self.generations.iter_mut() {
377            gen.promotion_threshold = config.promotion_threshold;
378        }
379
380        self.config = config.clone();
381
382        // Potentially resize young generation
383        if config.young_generation_size != self.generations[0].max_size() {
384            self.generations[0].resize(config.young_generation_size)?;
385        }
386
387        Ok(())
388    }
389
390    /// Get comprehensive heap statistics
391    pub fn stats(&self) -> GenerationalHeapStats {
392        GenerationalHeapStats {
393            generations: self.generations.iter().map(|g| g.stats()).collect(),
394            total_current_size: self.total_current_size(),
395            total_max_size: self.total_max_size(),
396            total_utilization: self.total_utilization(),
397            total_promotions: self.total_promotions.load(Ordering::Relaxed),
398            total_demotions: self.total_demotions.load(Ordering::Relaxed),
399            should_collect_minor: self.should_collect_minor(),
400            should_collect_major: self.should_collect_major(),
401        }
402    }
403}
404
405/// Comprehensive statistics for the generational heap
406#[derive(Debug, Clone)]
407pub struct GenerationalHeapStats {
408    pub generations: Vec<GenerationStats>,
409    pub total_current_size: usize,
410    pub total_max_size: usize,
411    pub total_utilization: f64,
412    pub total_promotions: usize,
413    pub total_demotions: usize,
414    pub should_collect_minor: bool,
415    pub should_collect_major: bool,
416}
417
418impl GenerationalHeapStats {
419    /// Generate a summary report
420    pub fn summary(&self) -> String {
421        let mut report = format!(
422            "Generational Heap Summary:\n\
423             Total Size: {} / {} bytes ({:.1}% utilized)\n\
424             Promotions: {}, Demotions: {}\n\
425             Collection Triggers: Minor={}, Major={}\n\n",
426            self.total_current_size,
427            self.total_max_size,
428            self.total_utilization * 100.0,
429            self.total_promotions,
430            self.total_demotions,
431            self.should_collect_minor,
432            self.should_collect_major
433        );
434
435        for gen in &self.generations {
436            report.push_str(&format!(
437                "Generation {}: {} / {} bytes ({:.1}% util), {} collections, freq={:.1}/min\n",
438                gen.number,
439                gen.current_size,
440                gen.max_size,
441                gen.utilization * 100.0,
442                gen.collection_count,
443                gen.collection_frequency
444            ));
445        }
446
447        report
448    }
449}
450
451#[cfg(test)]
452mod tests {
453    use super::*;
454    use crate::GcConfig;
455
456    #[test]
457    fn test_generation_basic() {
458        let gen = Generation::new(0, 1024, 2);
459
460        assert_eq!(gen.number, 0);
461        assert_eq!(gen.max_size(), 1024);
462        assert_eq!(gen.current_size(), 0);
463        assert_eq!(gen.utilization(), 0.0);
464        assert!(!gen.is_full(0.8));
465
466        // Test allocation
467        gen.allocate(512).expect("should allocate");
468        assert_eq!(gen.current_size(), 512);
469        assert_eq!(gen.utilization(), 0.5);
470
471        // Test deallocation
472        gen.deallocate(256);
473        assert_eq!(gen.current_size(), 256);
474    }
475
476    #[test]
477    fn test_generation_promotion() {
478        let gen = Generation::new(0, 1024, 2);
479
480        assert!(!gen.should_promote(1));
481        assert!(gen.should_promote(2));
482        assert!(gen.should_promote(3));
483    }
484
485    #[test]
486    fn test_generation_collection_tracking() {
487        let gen = Generation::new(0, 1024, 2);
488
489        assert_eq!(gen.collection_count(), 0);
490        assert_eq!(gen.survivor_count(), 0);
491
492        gen.record_collection(10, 5);
493        assert_eq!(gen.collection_count(), 1);
494        assert_eq!(gen.survivor_count(), 5);
495
496        gen.record_collection(8, 3);
497        assert_eq!(gen.collection_count(), 2);
498        assert_eq!(gen.survivor_count(), 3);
499    }
500
501    #[test]
502    fn test_generational_heap() {
503        let config = GcConfig::default();
504        let heap = GenerationalHeap::new(&config);
505
506        assert_eq!(heap.num_generations(), config.num_generations);
507        assert_eq!(heap.young_generation().number, 0);
508
509        // Test collection triggers
510        assert!(!heap.should_collect_minor()); // Empty heap
511        assert!(!heap.should_collect_major());
512
513        // Fill young generation partially
514        heap.young_generation()
515            .allocate((config.young_generation_size as f64 * 0.9) as usize)
516            .expect("should allocate");
517
518        assert!(heap.should_collect_minor()); // Should trigger minor GC
519    }
520
521    #[test]
522    fn test_heap_statistics() {
523        let config = GcConfig::default();
524        let heap = GenerationalHeap::new(&config);
525
526        let stats = heap.stats();
527        assert_eq!(stats.generations.len(), config.num_generations);
528        assert_eq!(stats.total_current_size, 0);
529        assert!(stats.total_max_size > 0);
530        assert_eq!(stats.total_utilization, 0.0);
531
532        // Allocate some memory
533        heap.young_generation()
534            .allocate(1000)
535            .expect("should allocate");
536
537        let stats = heap.stats();
538        assert_eq!(stats.total_current_size, 1000);
539        assert!(stats.total_utilization > 0.0);
540    }
541
542    #[test]
543    fn test_generation_resize() {
544        let mut gen = Generation::new(0, 1024, 2);
545
546        // Resize up
547        gen.resize(2048).expect("should resize");
548        assert_eq!(gen.max_size(), 2048);
549
550        // Allocate some memory
551        gen.allocate(1500).expect("should allocate");
552
553        // Can't resize below current usage
554        assert!(gen.resize(1000).is_err());
555
556        // Can resize to accommodate current usage
557        assert!(gen.resize(1500).is_ok());
558    }
559
560    #[test]
561    fn test_heap_total_utilization() {
562        let config = GcConfig::default();
563        let heap = GenerationalHeap::new(&config);
564
565        // Allocate in multiple generations
566        heap.generation(0)
567            .unwrap()
568            .allocate(1000)
569            .expect("should allocate");
570        heap.generation(1)
571            .unwrap()
572            .allocate(2000)
573            .expect("should allocate");
574
575        let utilization = heap.total_utilization();
576        assert!(utilization > 0.0);
577        assert!(utilization < 1.0);
578
579        let total_used = heap.total_current_size();
580        assert_eq!(total_used, 3000);
581    }
582}