Skip to main content

oxirs_shacl/optimization/
memory.rs

1//! Memory optimization for SHACL validation
2//!
3//! This module provides comprehensive memory optimization techniques including:
4//! - String interning for reducing memory usage of repeated strings
5//! - Compact data structures for efficient memory layout
6//! - Memory pool allocation for reducing allocation overhead
7//! - Memory monitoring and pressure detection
8//! - Garbage collection optimization hints
9
10#![allow(dead_code)]
11
12use std::collections::{HashMap, HashSet};
13use std::sync::{Arc, Mutex, RwLock};
14use std::time::{Duration, Instant};
15
16use serde::{Deserialize, Serialize};
17
18use crate::{Result, ShaclError};
19
20/// Memory optimization engine for SHACL validation
21#[derive(Debug)]
22pub struct MemoryOptimizer {
23    /// String interner for reducing string duplication
24    string_interner: Arc<RwLock<StringInterner>>,
25
26    /// Memory pool for allocations
27    memory_pool: Arc<Mutex<MemoryPool>>,
28
29    /// Memory monitoring system
30    memory_monitor: MemoryMonitor,
31
32    /// Optimization configuration
33    config: MemoryOptimizationConfig,
34
35    /// Performance statistics
36    stats: MemoryOptimizationStats,
37}
38
39/// Configuration for memory optimization
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct MemoryOptimizationConfig {
42    /// Enable string interning
43    pub enable_string_interning: bool,
44
45    /// Enable memory pooling
46    pub enable_memory_pooling: bool,
47
48    /// Enable memory monitoring
49    pub enable_monitoring: bool,
50
51    /// Maximum memory usage threshold (bytes)
52    pub max_memory_threshold: usize,
53
54    /// Memory pressure warning threshold (0.0-1.0)
55    pub pressure_warning_threshold: f64,
56
57    /// Memory pressure critical threshold (0.0-1.0)
58    pub pressure_critical_threshold: f64,
59
60    /// Enable garbage collection hints
61    pub enable_gc_hints: bool,
62
63    /// Memory compaction interval
64    pub compaction_interval: Duration,
65
66    /// Pool size limits
67    pub pool_limits: PoolLimits,
68}
69
70impl Default for MemoryOptimizationConfig {
71    fn default() -> Self {
72        Self {
73            enable_string_interning: true,
74            enable_memory_pooling: true,
75            enable_monitoring: true,
76            max_memory_threshold: 1024 * 1024 * 1024, // 1GB
77            pressure_warning_threshold: 0.7,
78            pressure_critical_threshold: 0.9,
79            enable_gc_hints: true,
80            compaction_interval: Duration::from_secs(300), // 5 minutes
81            pool_limits: PoolLimits::default(),
82        }
83    }
84}
85
86/// Memory pool size limits
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct PoolLimits {
89    /// Maximum number of small objects in pool
90    pub max_small_objects: usize,
91
92    /// Maximum number of medium objects in pool
93    pub max_medium_objects: usize,
94
95    /// Maximum number of large objects in pool
96    pub max_large_objects: usize,
97
98    /// Small object size threshold (bytes)
99    pub small_size_threshold: usize,
100
101    /// Medium object size threshold (bytes)
102    pub medium_size_threshold: usize,
103}
104
105impl Default for PoolLimits {
106    fn default() -> Self {
107        Self {
108            max_small_objects: 10000,
109            max_medium_objects: 1000,
110            max_large_objects: 100,
111            small_size_threshold: 256,
112            medium_size_threshold: 4096,
113        }
114    }
115}
116
117/// String interner for reducing memory usage of duplicate strings
118#[derive(Debug)]
119pub struct StringInterner {
120    /// Interned strings map
121    strings: HashMap<String, InternedString>,
122
123    /// Reverse lookup for debugging
124    reverse_map: HashMap<InternedString, String>,
125
126    /// Next string ID
127    next_id: u32,
128
129    /// Statistics
130    stats: InternerStats,
131}
132
133/// Interned string handle
134#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
135pub struct InternedString(u32);
136
137/// Statistics for string interning
138#[derive(Debug, Clone, Default, Serialize, Deserialize)]
139pub struct InternerStats {
140    /// Total strings interned
141    pub total_strings: usize,
142
143    /// Total memory saved (estimated)
144    pub memory_saved: usize,
145
146    /// Number of lookups
147    pub lookups: usize,
148
149    /// Number of cache hits
150    pub hits: usize,
151
152    /// Average string length
153    pub avg_string_length: f64,
154}
155
156impl StringInterner {
157    /// Create a new string interner
158    pub fn new() -> Self {
159        Self {
160            strings: HashMap::new(),
161            reverse_map: HashMap::new(),
162            next_id: 0,
163            stats: InternerStats::default(),
164        }
165    }
166
167    /// Intern a string, returning a handle
168    pub fn intern(&mut self, s: &str) -> InternedString {
169        self.stats.lookups += 1;
170
171        if let Some(&id) = self.strings.get(s) {
172            self.stats.hits += 1;
173            return id;
174        }
175
176        let id = InternedString(self.next_id);
177        self.next_id += 1;
178
179        let owned_string = s.to_string();
180        self.strings.insert(owned_string.clone(), id);
181        self.reverse_map.insert(id, owned_string);
182
183        // Update statistics
184        self.stats.total_strings += 1;
185        self.stats.memory_saved += s.len(); // Approximation
186        self.update_avg_length(s.len());
187
188        id
189    }
190
191    /// Get the string for an interned handle
192    pub fn get(&self, id: InternedString) -> Option<&String> {
193        self.reverse_map.get(&id)
194    }
195
196    /// Get interning statistics
197    pub fn stats(&self) -> &InternerStats {
198        &self.stats
199    }
200
201    /// Clear all interned strings
202    pub fn clear(&mut self) {
203        self.strings.clear();
204        self.reverse_map.clear();
205        self.next_id = 0;
206        self.stats = InternerStats::default();
207    }
208
209    /// Compact the interner by removing unused entries
210    pub fn compact(&mut self, used_ids: &HashSet<InternedString>) {
211        let old_size = self.strings.len();
212
213        // Remove unused strings
214        self.strings.retain(|_, id| used_ids.contains(id));
215        self.reverse_map.retain(|&id, _| used_ids.contains(&id));
216
217        let removed = old_size - self.strings.len();
218        self.stats.memory_saved += removed * 50; // Rough estimate
219    }
220
221    /// Update average string length
222    fn update_avg_length(&mut self, new_length: usize) {
223        let total_length = self.stats.avg_string_length * (self.stats.total_strings - 1) as f64
224            + new_length as f64;
225        self.stats.avg_string_length = total_length / self.stats.total_strings as f64;
226    }
227}
228
229impl Default for StringInterner {
230    fn default() -> Self {
231        Self::new()
232    }
233}
234
235/// Memory pool for efficient allocation of validation objects
236#[derive(Debug)]
237pub struct MemoryPool {
238    /// Small object pool
239    small_pool: Vec<PooledObject>,
240
241    /// Medium object pool
242    medium_pool: Vec<PooledObject>,
243
244    /// Large object pool
245    large_pool: Vec<PooledObject>,
246
247    /// Pool statistics
248    stats: PoolStats,
249
250    /// Configuration
251    limits: PoolLimits,
252}
253
254/// Pooled object wrapper
255#[derive(Debug)]
256struct PooledObject {
257    /// Object data
258    data: Vec<u8>,
259
260    /// Allocation timestamp
261    allocated_at: Instant,
262
263    /// Whether object is currently in use
264    in_use: bool,
265}
266
267/// Memory pool statistics
268#[derive(Debug, Clone, Default, Serialize, Deserialize)]
269pub struct PoolStats {
270    /// Total allocations
271    pub total_allocations: usize,
272
273    /// Pool hits
274    pub pool_hits: usize,
275
276    /// Pool misses
277    pub pool_misses: usize,
278
279    /// Currently allocated objects
280    pub active_objects: usize,
281
282    /// Peak object count
283    pub peak_objects: usize,
284
285    /// Total memory managed (bytes)
286    pub total_memory_managed: usize,
287}
288
289impl MemoryPool {
290    /// Create a new memory pool
291    pub fn new(limits: PoolLimits) -> Self {
292        Self {
293            small_pool: Vec::with_capacity(limits.max_small_objects),
294            medium_pool: Vec::with_capacity(limits.max_medium_objects),
295            large_pool: Vec::with_capacity(limits.max_large_objects),
296            stats: PoolStats::default(),
297            limits,
298        }
299    }
300
301    /// Allocate memory from the pool
302    pub fn allocate(&mut self, size: usize) -> Result<*mut u8> {
303        self.stats.total_allocations += 1;
304
305        // Determine pool type and capacity first
306        let pool_capacity = self.pool_capacity(size);
307
308        // Check if we can find an available object
309        let pool_index = if size <= self.limits.small_size_threshold {
310            0
311        } else if size <= self.limits.medium_size_threshold {
312            1
313        } else {
314            2
315        };
316
317        let pool = match pool_index {
318            0 => &mut self.small_pool,
319            1 => &mut self.medium_pool,
320            _ => &mut self.large_pool,
321        };
322
323        // Try to find an available object in the pool
324        for obj in pool.iter_mut() {
325            if !obj.in_use && obj.data.capacity() >= size {
326                obj.in_use = true;
327                obj.allocated_at = Instant::now();
328                self.stats.pool_hits += 1;
329                self.stats.active_objects += 1;
330
331                if self.stats.active_objects > self.stats.peak_objects {
332                    self.stats.peak_objects = self.stats.active_objects;
333                }
334
335                return Ok(obj.data.as_mut_ptr());
336            }
337        }
338
339        // No available object found, create a new one if pool not full
340        if pool.len() < pool_capacity {
341            let mut data = vec![0; size];
342
343            let ptr = data.as_mut_ptr();
344
345            pool.push(PooledObject {
346                data,
347                allocated_at: Instant::now(),
348                in_use: true,
349            });
350
351            self.stats.pool_misses += 1;
352            self.stats.active_objects += 1;
353            self.stats.total_memory_managed += size;
354
355            if self.stats.active_objects > self.stats.peak_objects {
356                self.stats.peak_objects = self.stats.active_objects;
357            }
358
359            Ok(ptr)
360        } else {
361            // Pool is full, fall back to regular allocation
362            self.stats.pool_misses += 1;
363            Err(ShaclError::MemoryPool("Pool capacity exceeded".to_string()))
364        }
365    }
366
367    /// Deallocate memory back to the pool
368    pub fn deallocate(&mut self, ptr: *mut u8) {
369        // Find the object in pools and mark as not in use
370        for pool in [
371            &mut self.small_pool,
372            &mut self.medium_pool,
373            &mut self.large_pool,
374        ] {
375            for obj in pool.iter_mut() {
376                if obj.data.as_mut_ptr() == ptr && obj.in_use {
377                    obj.in_use = false;
378                    self.stats.active_objects -= 1;
379                    return;
380                }
381            }
382        }
383    }
384
385    /// Compact the memory pool by removing old unused objects
386    pub fn compact(&mut self, max_age: Duration) {
387        let cutoff = Instant::now() - max_age;
388
389        let compact_pool = |pool: &mut Vec<PooledObject>| {
390            let old_len = pool.len();
391            pool.retain(|obj| obj.in_use || obj.allocated_at > cutoff);
392            old_len - pool.len()
393        };
394
395        let removed = compact_pool(&mut self.small_pool)
396            + compact_pool(&mut self.medium_pool)
397            + compact_pool(&mut self.large_pool);
398
399        if removed > 0 {
400            self.stats.total_memory_managed = self
401                .small_pool
402                .iter()
403                .chain(self.medium_pool.iter())
404                .chain(self.large_pool.iter())
405                .map(|obj| obj.data.capacity())
406                .sum();
407        }
408    }
409
410    /// Get pool statistics
411    pub fn stats(&self) -> &PoolStats {
412        &self.stats
413    }
414
415    /// Select appropriate pool based on size
416    fn select_pool(&mut self, size: usize) -> &mut Vec<PooledObject> {
417        if size <= self.limits.small_size_threshold {
418            &mut self.small_pool
419        } else if size <= self.limits.medium_size_threshold {
420            &mut self.medium_pool
421        } else {
422            &mut self.large_pool
423        }
424    }
425
426    /// Get pool capacity based on size
427    fn pool_capacity(&self, size: usize) -> usize {
428        if size <= self.limits.small_size_threshold {
429            self.limits.max_small_objects
430        } else if size <= self.limits.medium_size_threshold {
431            self.limits.max_medium_objects
432        } else {
433            self.limits.max_large_objects
434        }
435    }
436}
437
438/// Memory monitoring system
439#[derive(Debug)]
440pub struct MemoryMonitor {
441    /// Memory usage samples
442    samples: Vec<MemorySample>,
443
444    /// Current memory pressure level
445    pressure_level: MemoryPressureLevel,
446
447    /// Monitoring statistics
448    stats: MonitoringStats,
449
450    /// Last collection time
451    last_collection: Instant,
452}
453
454/// Memory usage sample
455#[derive(Debug, Clone, Serialize)]
456pub struct MemorySample {
457    /// Timestamp
458    #[serde(skip)]
459    pub timestamp: Instant,
460
461    /// Memory usage in bytes
462    pub memory_usage: usize,
463
464    /// Memory pressure (0.0-1.0)
465    pub pressure: f64,
466}
467
468impl Default for MemorySample {
469    fn default() -> Self {
470        Self {
471            timestamp: Instant::now(),
472            memory_usage: 0,
473            pressure: 0.0,
474        }
475    }
476}
477
478/// Memory pressure levels
479#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
480pub enum MemoryPressureLevel {
481    /// Normal memory usage
482    Normal,
483
484    /// Warning level - should start optimizing
485    Warning,
486
487    /// Critical level - must take immediate action
488    Critical,
489
490    /// Emergency level - risk of out-of-memory
491    Emergency,
492}
493
494/// Monitoring statistics
495#[derive(Debug, Clone, Default, Serialize, Deserialize)]
496pub struct MonitoringStats {
497    /// Total samples collected
498    pub total_samples: usize,
499
500    /// Average memory usage
501    pub avg_memory_usage: f64,
502
503    /// Peak memory usage
504    pub peak_memory_usage: usize,
505
506    /// Number of pressure warnings
507    pub pressure_warnings: usize,
508
509    /// Number of critical pressure events
510    pub critical_pressure_events: usize,
511
512    /// Time spent in high pressure
513    pub high_pressure_duration: Duration,
514}
515
516impl MemoryMonitor {
517    /// Create a new memory monitor
518    pub fn new() -> Self {
519        Self {
520            samples: Vec::new(),
521            pressure_level: MemoryPressureLevel::Normal,
522            stats: MonitoringStats::default(),
523            last_collection: Instant::now(),
524        }
525    }
526
527    /// Record a memory usage sample
528    pub fn record_sample(&mut self, memory_usage: usize, max_memory: usize) {
529        let pressure = memory_usage as f64 / max_memory as f64;
530        let timestamp = Instant::now();
531
532        self.samples.push(MemorySample {
533            timestamp,
534            memory_usage,
535            pressure,
536        });
537
538        // Keep only recent samples (last 1000)
539        if self.samples.len() > 1000 {
540            self.samples.remove(0);
541        }
542
543        // Update pressure level
544        let old_level = self.pressure_level.clone();
545        self.pressure_level = if pressure >= 0.95 {
546            MemoryPressureLevel::Emergency
547        } else if pressure >= 0.9 {
548            MemoryPressureLevel::Critical
549        } else if pressure >= 0.7 {
550            MemoryPressureLevel::Warning
551        } else {
552            MemoryPressureLevel::Normal
553        };
554
555        // Update statistics
556        self.stats.total_samples += 1;
557        self.stats.avg_memory_usage = (self.stats.avg_memory_usage
558            * (self.stats.total_samples - 1) as f64
559            + memory_usage as f64)
560            / self.stats.total_samples as f64;
561
562        if memory_usage > self.stats.peak_memory_usage {
563            self.stats.peak_memory_usage = memory_usage;
564        }
565
566        if matches!(self.pressure_level, MemoryPressureLevel::Warning)
567            && !matches!(old_level, MemoryPressureLevel::Warning)
568        {
569            self.stats.pressure_warnings += 1;
570        }
571
572        if matches!(
573            self.pressure_level,
574            MemoryPressureLevel::Critical | MemoryPressureLevel::Emergency
575        ) && !matches!(
576            old_level,
577            MemoryPressureLevel::Critical | MemoryPressureLevel::Emergency
578        ) {
579            self.stats.critical_pressure_events += 1;
580        }
581
582        // Update high pressure duration
583        if matches!(
584            self.pressure_level,
585            MemoryPressureLevel::Critical | MemoryPressureLevel::Emergency
586        ) {
587            self.stats.high_pressure_duration += timestamp.duration_since(self.last_collection);
588        }
589
590        self.last_collection = timestamp;
591    }
592
593    /// Get current memory pressure level
594    pub fn pressure_level(&self) -> &MemoryPressureLevel {
595        &self.pressure_level
596    }
597
598    /// Get monitoring statistics
599    pub fn stats(&self) -> &MonitoringStats {
600        &self.stats
601    }
602
603    /// Get recent memory usage trend
604    pub fn memory_trend(&self) -> MemoryTrend {
605        if self.samples.len() < 10 {
606            return MemoryTrend::Stable;
607        }
608
609        let recent_avg = self
610            .samples
611            .iter()
612            .rev()
613            .take(5)
614            .map(|s| s.memory_usage)
615            .sum::<usize>() as f64
616            / 5.0;
617
618        let older_avg = self
619            .samples
620            .iter()
621            .rev()
622            .skip(5)
623            .take(5)
624            .map(|s| s.memory_usage)
625            .sum::<usize>() as f64
626            / 5.0;
627
628        let change_ratio = (recent_avg - older_avg) / older_avg;
629
630        if change_ratio > 0.1 {
631            MemoryTrend::Increasing
632        } else if change_ratio < -0.1 {
633            MemoryTrend::Decreasing
634        } else {
635            MemoryTrend::Stable
636        }
637    }
638
639    /// Clear old samples
640    pub fn clear_old_samples(&mut self, max_age: Duration) {
641        let cutoff = Instant::now() - max_age;
642        self.samples.retain(|sample| sample.timestamp > cutoff);
643    }
644}
645
646impl Default for MemoryMonitor {
647    fn default() -> Self {
648        Self::new()
649    }
650}
651
652/// Memory usage trend
653#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
654pub enum MemoryTrend {
655    /// Memory usage is increasing
656    Increasing,
657
658    /// Memory usage is decreasing
659    Decreasing,
660
661    /// Memory usage is stable
662    Stable,
663}
664
665/// Memory optimization statistics
666#[derive(Debug, Clone, Default, Serialize, Deserialize)]
667pub struct MemoryOptimizationStats {
668    /// String interning statistics
669    pub interning_stats: InternerStats,
670
671    /// Memory pool statistics
672    pub pool_stats: PoolStats,
673
674    /// Memory monitoring statistics
675    pub monitoring_stats: MonitoringStats,
676
677    /// Total memory saved (estimated)
678    pub total_memory_saved: usize,
679
680    /// Number of optimization operations
681    pub optimization_operations: usize,
682
683    /// Time spent on optimization
684    pub optimization_time: Duration,
685}
686
687impl MemoryOptimizer {
688    /// Create a new memory optimizer
689    pub fn new(config: MemoryOptimizationConfig) -> Self {
690        Self {
691            string_interner: Arc::new(RwLock::new(StringInterner::new())),
692            memory_pool: Arc::new(Mutex::new(MemoryPool::new(config.pool_limits.clone()))),
693            memory_monitor: MemoryMonitor::new(),
694            config,
695            stats: MemoryOptimizationStats::default(),
696        }
697    }
698
699    /// Intern a string for memory optimization
700    pub fn intern_string(&mut self, s: &str) -> Result<InternedString> {
701        if !self.config.enable_string_interning {
702            return Err(ShaclError::MemoryOptimization(
703                "String interning is disabled".to_string(),
704            ));
705        }
706
707        let start = Instant::now();
708        let mut interner = self
709            .string_interner
710            .write()
711            .expect("rwlock should not be poisoned");
712        let result = interner.intern(s);
713
714        self.stats.optimization_time += start.elapsed();
715        self.stats.optimization_operations += 1;
716
717        Ok(result)
718    }
719
720    /// Get an interned string
721    pub fn get_interned_string(&self, id: InternedString) -> Option<String> {
722        let interner = self
723            .string_interner
724            .read()
725            .expect("rwlock should not be poisoned");
726        interner.get(id).cloned()
727    }
728
729    /// Allocate memory from pool
730    pub fn pool_allocate(&mut self, size: usize) -> Result<*mut u8> {
731        if !self.config.enable_memory_pooling {
732            return Err(ShaclError::MemoryOptimization(
733                "Memory pooling is disabled".to_string(),
734            ));
735        }
736
737        let start = Instant::now();
738        let mut pool = self
739            .memory_pool
740            .lock()
741            .expect("mutex lock should not be poisoned");
742        let result = pool.allocate(size);
743
744        self.stats.optimization_time += start.elapsed();
745        self.stats.optimization_operations += 1;
746
747        result
748    }
749
750    /// Deallocate memory back to pool
751    pub fn pool_deallocate(&mut self, ptr: *mut u8) {
752        if self.config.enable_memory_pooling {
753            let mut pool = self
754                .memory_pool
755                .lock()
756                .expect("mutex lock should not be poisoned");
757            pool.deallocate(ptr);
758        }
759    }
760
761    /// Record memory usage sample
762    pub fn record_memory_usage(&mut self, memory_usage: usize) {
763        if self.config.enable_monitoring {
764            self.memory_monitor
765                .record_sample(memory_usage, self.config.max_memory_threshold);
766        }
767    }
768
769    /// Get current memory pressure level
770    pub fn memory_pressure_level(&self) -> &MemoryPressureLevel {
771        self.memory_monitor.pressure_level()
772    }
773
774    /// Check if memory pressure is high
775    pub fn is_memory_pressure_high(&self) -> bool {
776        matches!(
777            self.memory_monitor.pressure_level(),
778            MemoryPressureLevel::Critical | MemoryPressureLevel::Emergency
779        )
780    }
781
782    /// Perform memory optimization operations
783    pub fn optimize(&mut self) -> Result<OptimizationResult> {
784        let start = Instant::now();
785        let mut result = OptimizationResult::default();
786
787        // Compact string interner if enabled
788        if self.config.enable_string_interning {
789            let used_strings = self.collect_used_strings()?;
790            let mut interner = self
791                .string_interner
792                .write()
793                .expect("rwlock should not be poisoned");
794            let old_size = interner.strings.len();
795            interner.compact(&used_strings);
796            let new_size = interner.strings.len();
797            result.strings_removed = old_size - new_size;
798        }
799
800        // Compact memory pool if enabled
801        if self.config.enable_memory_pooling {
802            let mut pool = self
803                .memory_pool
804                .lock()
805                .expect("mutex lock should not be poisoned");
806            pool.compact(self.config.compaction_interval);
807            result.pool_objects_freed =
808                pool.stats().total_allocations - pool.stats().active_objects;
809        }
810
811        // Trigger garbage collection hint if enabled
812        if self.config.enable_gc_hints && self.is_memory_pressure_high() {
813            #[cfg(not(target_family = "wasm"))]
814            {
815                // Force garbage collection (Rust doesn't have direct GC, but we can drop caches)
816                self.clear_caches();
817                result.gc_triggered = true;
818            }
819        }
820
821        // Update statistics
822        self.update_statistics();
823
824        result.optimization_duration = start.elapsed();
825        self.stats.optimization_time += result.optimization_duration;
826        self.stats.optimization_operations += 1;
827
828        Ok(result)
829    }
830
831    /// Clear all caches to free memory
832    pub fn clear_caches(&mut self) {
833        if self.config.enable_string_interning {
834            let mut interner = self
835                .string_interner
836                .write()
837                .expect("rwlock should not be poisoned");
838            interner.clear();
839        }
840    }
841
842    /// Get optimization statistics
843    pub fn get_statistics(&self) -> MemoryOptimizationStats {
844        let mut stats = self.stats.clone();
845
846        if self.config.enable_string_interning {
847            let interner = self
848                .string_interner
849                .read()
850                .expect("rwlock should not be poisoned");
851            stats.interning_stats = interner.stats().clone();
852        }
853
854        if self.config.enable_memory_pooling {
855            let pool = self
856                .memory_pool
857                .lock()
858                .expect("mutex lock should not be poisoned");
859            stats.pool_stats = pool.stats().clone();
860        }
861
862        if self.config.enable_monitoring {
863            stats.monitoring_stats = self.memory_monitor.stats().clone();
864        }
865
866        stats.total_memory_saved = stats.interning_stats.memory_saved;
867
868        stats
869    }
870
871    /// Collect currently used string IDs (placeholder implementation)
872    fn collect_used_strings(&self) -> Result<HashSet<InternedString>> {
873        // In a real implementation, this would traverse all active validation objects
874        // to find which interned strings are still in use
875        Ok(HashSet::new())
876    }
877
878    /// Update internal statistics
879    fn update_statistics(&mut self) {
880        // Update stats from component statistics
881        if self.config.enable_string_interning {
882            let interner = self
883                .string_interner
884                .read()
885                .expect("rwlock should not be poisoned");
886            self.stats.interning_stats = interner.stats().clone();
887        }
888
889        if self.config.enable_memory_pooling {
890            let pool = self
891                .memory_pool
892                .lock()
893                .expect("mutex lock should not be poisoned");
894            self.stats.pool_stats = pool.stats().clone();
895        }
896
897        if self.config.enable_monitoring {
898            self.stats.monitoring_stats = self.memory_monitor.stats().clone();
899        }
900    }
901}
902
903/// Result of memory optimization operation
904#[derive(Debug, Clone, Default, Serialize, Deserialize)]
905pub struct OptimizationResult {
906    /// Number of strings removed from interner
907    pub strings_removed: usize,
908
909    /// Number of pool objects freed
910    pub pool_objects_freed: usize,
911
912    /// Whether garbage collection was triggered
913    pub gc_triggered: bool,
914
915    /// Time spent on optimization
916    pub optimization_duration: Duration,
917
918    /// Estimated memory freed (bytes)
919    pub memory_freed: usize,
920}
921
922/// Compact data structures for memory efficiency
923pub mod compact {
924    use super::*;
925
926    /// Compact representation of validation violations
927    #[derive(Debug, Clone, Serialize, Deserialize)]
928    pub struct CompactViolation {
929        /// Focus node (interned)
930        pub focus_node: InternedString,
931
932        /// Shape ID (interned)
933        pub shape_id: InternedString,
934
935        /// Constraint component (interned)
936        pub constraint_component: InternedString,
937
938        /// Severity level (single byte)
939        pub severity: u8,
940
941        /// Message (interned if available)
942        pub message: Option<InternedString>,
943    }
944
945    /// Compact representation of shapes
946    #[derive(Debug, Clone, Serialize, Deserialize)]
947    pub struct CompactShape {
948        /// Shape ID (interned)
949        pub id: InternedString,
950
951        /// Shape type (single byte)
952        pub shape_type: u8,
953
954        /// Target definitions (compact)
955        pub targets: Vec<CompactTarget>,
956
957        /// Constraint definitions (compact)
958        pub constraints: Vec<CompactConstraint>,
959    }
960
961    /// Compact representation of targets
962    #[derive(Debug, Clone, Serialize, Deserialize)]
963    pub struct CompactTarget {
964        /// Target type (single byte)
965        pub target_type: u8,
966
967        /// Target value (interned)
968        pub value: InternedString,
969    }
970
971    /// Compact representation of constraints
972    #[derive(Debug, Clone, Serialize, Deserialize)]
973    pub struct CompactConstraint {
974        /// Constraint type (single byte)
975        pub constraint_type: u8,
976
977        /// Constraint parameters (binary encoded)
978        pub parameters: Vec<u8>,
979    }
980
981    /// Converter between regular and compact representations
982    #[derive(Debug)]
983    pub struct CompactConverter {
984        /// String interner for compact representations
985        interner: Arc<RwLock<StringInterner>>,
986    }
987
988    impl CompactConverter {
989        /// Create a new compact converter
990        pub fn new(interner: Arc<RwLock<StringInterner>>) -> Self {
991            Self { interner }
992        }
993
994        /// Convert violation to compact representation
995        pub fn compact_violation(
996            &self,
997            violation: &crate::validation::ValidationViolation,
998        ) -> Result<CompactViolation> {
999            let mut interner = self
1000                .interner
1001                .write()
1002                .expect("rwlock should not be poisoned");
1003
1004            let focus_node = interner.intern(&format!("{:?}", violation.focus_node));
1005            let shape_id = interner.intern(&violation.source_shape.to_string());
1006            let constraint_component =
1007                interner.intern(&violation.source_constraint_component.to_string());
1008
1009            let severity = match violation.result_severity {
1010                crate::Severity::Info => 0,
1011                crate::Severity::Warning => 1,
1012                crate::Severity::Violation => 2,
1013            };
1014
1015            let message = violation
1016                .result_message
1017                .as_ref()
1018                .map(|msg| interner.intern(msg));
1019
1020            Ok(CompactViolation {
1021                focus_node,
1022                shape_id,
1023                constraint_component,
1024                severity,
1025                message,
1026            })
1027        }
1028
1029        /// Convert compact violation back to regular representation
1030        pub fn expand_violation(
1031            &self,
1032            compact: &CompactViolation,
1033        ) -> Result<crate::validation::ValidationViolation> {
1034            let interner = self.interner.read().expect("rwlock should not be poisoned");
1035
1036            let _focus_node_str = interner.get(compact.focus_node).ok_or_else(|| {
1037                ShaclError::MemoryOptimization("Invalid focus node reference".to_string())
1038            })?;
1039
1040            let _shape_id_str = interner.get(compact.shape_id).ok_or_else(|| {
1041                ShaclError::MemoryOptimization("Invalid shape ID reference".to_string())
1042            })?;
1043
1044            let _constraint_component_str =
1045                interner.get(compact.constraint_component).ok_or_else(|| {
1046                    ShaclError::MemoryOptimization(
1047                        "Invalid constraint component reference".to_string(),
1048                    )
1049                })?;
1050
1051            // This is a simplified conversion - a real implementation would need
1052            // proper parsing of the string representations back to their original types
1053            Err(ShaclError::MemoryOptimization(
1054                "Compact violation expansion not fully implemented".to_string(),
1055            ))
1056        }
1057    }
1058}
1059
1060// Implement required error conversions
1061
1062#[cfg(test)]
1063mod tests {
1064    use super::*;
1065
1066    #[test]
1067    fn test_string_interner() {
1068        let mut interner = StringInterner::new();
1069
1070        let id1 = interner.intern("test_string");
1071        let id2 = interner.intern("test_string");
1072        let id3 = interner.intern("different_string");
1073
1074        assert_eq!(id1, id2); // Same string should return same ID
1075        assert_ne!(id1, id3); // Different strings should return different IDs
1076
1077        assert_eq!(interner.get(id1), Some(&"test_string".to_string()));
1078        assert_eq!(interner.get(id3), Some(&"different_string".to_string()));
1079
1080        let stats = interner.stats();
1081        assert_eq!(stats.total_strings, 2); // Only 2 unique strings
1082        assert_eq!(stats.lookups, 3); // 3 total lookups
1083        assert_eq!(stats.hits, 1); // 1 cache hit
1084    }
1085
1086    #[test]
1087    fn test_memory_monitor() {
1088        let mut monitor = MemoryMonitor::new();
1089
1090        // Test normal pressure
1091        monitor.record_sample(1000, 10000); // 10% usage
1092        assert_eq!(monitor.pressure_level(), &MemoryPressureLevel::Normal);
1093
1094        // Test warning pressure
1095        monitor.record_sample(7500, 10000); // 75% usage
1096        assert_eq!(monitor.pressure_level(), &MemoryPressureLevel::Warning);
1097
1098        // Test critical pressure
1099        monitor.record_sample(9500, 10000); // 95% usage
1100        assert_eq!(monitor.pressure_level(), &MemoryPressureLevel::Emergency);
1101
1102        let stats = monitor.stats();
1103        assert_eq!(stats.total_samples, 3);
1104        assert!(stats.peak_memory_usage == 9500);
1105    }
1106
1107    #[test]
1108    fn test_memory_optimizer_config() {
1109        let config = MemoryOptimizationConfig::default();
1110
1111        assert!(config.enable_string_interning);
1112        assert!(config.enable_memory_pooling);
1113        assert!(config.enable_monitoring);
1114        assert_eq!(config.max_memory_threshold, 1024 * 1024 * 1024);
1115        assert_eq!(config.pressure_warning_threshold, 0.7);
1116        assert_eq!(config.pressure_critical_threshold, 0.9);
1117    }
1118
1119    #[test]
1120    fn test_memory_optimizer_creation() {
1121        let config = MemoryOptimizationConfig::default();
1122        let optimizer = MemoryOptimizer::new(config);
1123
1124        assert_eq!(
1125            optimizer.memory_pressure_level(),
1126            &MemoryPressureLevel::Normal
1127        );
1128        assert!(!optimizer.is_memory_pressure_high());
1129    }
1130}