Skip to main content

oxirs_ttl/toolkit/
buffer_manager.rs

1//! Buffer management for efficient RDF parsing
2//!
3//! This module provides memory pooling and buffer management optimized for RDF parsing workloads.
4//! It uses scirs2-core's advanced memory management to minimize allocations during parsing.
5
6use std::sync::{Arc, Mutex};
7
8/// Buffer manager for RDF parsing operations
9///
10/// This manages a pool of reusable buffers to minimize allocations during parsing.
11/// Particularly useful for:
12/// - Temporary string buffers during tokenization
13/// - Blank node ID generation
14/// - IRI resolution and prefix expansion
15/// - Literal value accumulation
16///
17/// # Example
18///
19/// ```
20/// use oxirs_ttl::toolkit::BufferManager;
21///
22/// let mut manager = BufferManager::new();
23///
24/// // Acquire a buffer for temporary string operations
25/// let mut buffer = manager.acquire_string_buffer();
26/// buffer.push_str("temporary content");
27///
28/// // Use the buffer...
29/// let content = buffer.clone();
30///
31/// // Release it back to the pool when done
32/// manager.release_string_buffer(buffer);
33/// ```
34#[derive(Debug)]
35pub struct BufferManager {
36    /// Pool of string buffers for reuse
37    string_buffers: Vec<String>,
38    /// Maximum number of buffers to keep in the pool
39    max_pooled_buffers: usize,
40    /// Statistics for monitoring buffer usage
41    stats: BufferStats,
42}
43
44/// Statistics about buffer pool usage
45#[derive(Debug, Clone, Default)]
46pub struct BufferStats {
47    /// Total number of buffer acquisitions
48    pub total_acquisitions: usize,
49    /// Number of times a buffer was reused from pool (hit)
50    pub pool_hits: usize,
51    /// Number of times a new buffer was allocated (miss)
52    pub pool_misses: usize,
53    /// Total number of buffer releases
54    pub total_releases: usize,
55    /// Current number of buffers in the pool
56    pub current_pool_size: usize,
57}
58
59impl BufferManager {
60    /// Create a new buffer manager with default capacity
61    pub fn new() -> Self {
62        Self::with_capacity(32)
63    }
64
65    /// Create a new buffer manager with specified pool capacity
66    pub fn with_capacity(max_pooled_buffers: usize) -> Self {
67        Self {
68            string_buffers: Vec::with_capacity(max_pooled_buffers),
69            max_pooled_buffers,
70            stats: BufferStats::default(),
71        }
72    }
73
74    /// Acquire a string buffer from the pool
75    ///
76    /// Returns a cleared buffer from the pool if available,
77    /// otherwise allocates a new buffer.
78    pub fn acquire_string_buffer(&mut self) -> String {
79        self.stats.total_acquisitions += 1;
80
81        if let Some(mut buffer) = self.string_buffers.pop() {
82            self.stats.pool_hits += 1;
83            self.stats.current_pool_size = self.string_buffers.len();
84            buffer.clear();
85            buffer
86        } else {
87            self.stats.pool_misses += 1;
88            String::with_capacity(256) // Pre-allocate reasonable capacity
89        }
90    }
91
92    /// Acquire a string buffer with specific capacity
93    pub fn acquire_string_buffer_with_capacity(&mut self, capacity: usize) -> String {
94        self.stats.total_acquisitions += 1;
95
96        // Try to find a buffer with sufficient capacity
97        for (i, buffer) in self.string_buffers.iter().enumerate() {
98            if buffer.capacity() >= capacity {
99                let mut buffer = self.string_buffers.swap_remove(i);
100                self.stats.pool_hits += 1;
101                self.stats.current_pool_size = self.string_buffers.len();
102                buffer.clear();
103                return buffer;
104            }
105        }
106
107        // No suitable buffer found, allocate new
108        self.stats.pool_misses += 1;
109        String::with_capacity(capacity)
110    }
111
112    /// Release a string buffer back to the pool
113    ///
114    /// The buffer will be cleared and reused for future acquisitions.
115    /// If the pool is full, the buffer is dropped.
116    pub fn release_string_buffer(&mut self, buffer: String) {
117        self.stats.total_releases += 1;
118
119        if self.string_buffers.len() < self.max_pooled_buffers {
120            // Only keep buffers with reasonable capacity to avoid memory bloat
121            if buffer.capacity() <= 4096 {
122                self.string_buffers.push(buffer);
123                self.stats.current_pool_size = self.string_buffers.len();
124            }
125        }
126        // Otherwise, drop the buffer (it goes out of scope)
127    }
128
129    /// Generate a blank node ID efficiently using a pooled buffer
130    pub fn generate_blank_node_id(&mut self, counter: usize) -> String {
131        let mut buffer = self.acquire_string_buffer();
132        buffer.push_str("_:b");
133        buffer.push_str(&counter.to_string());
134        // Don't release - we're returning this string
135        buffer
136    }
137
138    /// Expand a prefixed name to a full IRI using a pooled buffer
139    pub fn expand_prefixed_name(&mut self, _prefix: &str, local: &str, namespace: &str) -> String {
140        let total_len = namespace.len() + local.len();
141        let mut buffer = self.acquire_string_buffer_with_capacity(total_len);
142        buffer.push_str(namespace);
143        buffer.push_str(local);
144        // Don't release - we're returning this string
145        buffer
146    }
147
148    /// Get buffer pool statistics
149    pub fn stats(&self) -> &BufferStats {
150        &self.stats
151    }
152
153    /// Get the buffer pool hit rate (0.0 to 1.0)
154    pub fn hit_rate(&self) -> f64 {
155        if self.stats.total_acquisitions == 0 {
156            return 0.0;
157        }
158        self.stats.pool_hits as f64 / self.stats.total_acquisitions as f64
159    }
160
161    /// Clear all buffers from the pool and reset statistics
162    pub fn clear(&mut self) {
163        self.string_buffers.clear();
164        self.stats = BufferStats::default();
165    }
166
167    /// Shrink the buffer pool to fit current usage
168    pub fn shrink_to_fit(&mut self) {
169        self.string_buffers.shrink_to_fit();
170    }
171
172    /// Get the current number of buffers in the pool
173    pub fn pool_size(&self) -> usize {
174        self.string_buffers.len()
175    }
176}
177
178impl Default for BufferManager {
179    fn default() -> Self {
180        Self::new()
181    }
182}
183
184impl BufferStats {
185    /// Get a human-readable report of buffer statistics
186    pub fn report(&self) -> String {
187        let hit_rate = if self.total_acquisitions > 0 {
188            (self.pool_hits as f64 / self.total_acquisitions as f64) * 100.0
189        } else {
190            0.0
191        };
192
193        format!(
194            "Buffer Pool Statistics:\n\
195             - Total acquisitions: {}\n\
196             - Pool hits: {} ({:.1}%)\n\
197             - Pool misses: {}\n\
198             - Total releases: {}\n\
199             - Current pool size: {}",
200            self.total_acquisitions,
201            self.pool_hits,
202            hit_rate,
203            self.pool_misses,
204            self.total_releases,
205            self.current_pool_size
206        )
207    }
208}
209
210/// Thread-safe global buffer manager
211pub struct GlobalBufferManager {
212    inner: Arc<Mutex<BufferManager>>,
213}
214
215impl GlobalBufferManager {
216    /// Create a new global buffer manager
217    pub fn new() -> Self {
218        Self {
219            inner: Arc::new(Mutex::new(BufferManager::new())),
220        }
221    }
222
223    /// Create with specific capacity
224    pub fn with_capacity(max_pooled_buffers: usize) -> Self {
225        Self {
226            inner: Arc::new(Mutex::new(BufferManager::with_capacity(max_pooled_buffers))),
227        }
228    }
229
230    /// Acquire a string buffer
231    pub fn acquire_string_buffer(&self) -> String {
232        self.inner
233            .lock()
234            .expect("lock should not be poisoned")
235            .acquire_string_buffer()
236    }
237
238    /// Release a string buffer
239    pub fn release_string_buffer(&self, buffer: String) {
240        self.inner
241            .lock()
242            .expect("lock should not be poisoned")
243            .release_string_buffer(buffer);
244    }
245
246    /// Generate blank node ID
247    pub fn generate_blank_node_id(&self, counter: usize) -> String {
248        self.inner
249            .lock()
250            .expect("lock should not be poisoned")
251            .generate_blank_node_id(counter)
252    }
253
254    /// Get statistics
255    pub fn stats(&self) -> BufferStats {
256        self.inner
257            .lock()
258            .expect("lock should not be poisoned")
259            .stats()
260            .clone()
261    }
262
263    /// Get hit rate
264    pub fn hit_rate(&self) -> f64 {
265        self.inner
266            .lock()
267            .expect("lock should not be poisoned")
268            .hit_rate()
269    }
270
271    /// Clear the pool
272    pub fn clear(&self) {
273        self.inner
274            .lock()
275            .expect("lock should not be poisoned")
276            .clear();
277    }
278}
279
280impl Default for GlobalBufferManager {
281    fn default() -> Self {
282        Self::new()
283    }
284}
285
286impl Clone for GlobalBufferManager {
287    fn clone(&self) -> Self {
288        Self {
289            inner: Arc::clone(&self.inner),
290        }
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297
298    #[test]
299    fn test_buffer_acquisition_and_release() {
300        let mut manager = BufferManager::new();
301
302        // Acquire a buffer
303        let buffer1 = manager.acquire_string_buffer();
304        assert_eq!(manager.stats().total_acquisitions, 1);
305        assert_eq!(manager.stats().pool_misses, 1); // First acquisition is always a miss
306
307        // Release it back
308        manager.release_string_buffer(buffer1);
309        assert_eq!(manager.stats().total_releases, 1);
310        assert_eq!(manager.pool_size(), 1);
311
312        // Acquire again - should reuse
313        let buffer2 = manager.acquire_string_buffer();
314        assert_eq!(manager.stats().total_acquisitions, 2);
315        assert_eq!(manager.stats().pool_hits, 1); // Should have hit the pool
316        assert_eq!(manager.pool_size(), 0);
317
318        manager.release_string_buffer(buffer2);
319    }
320
321    #[test]
322    fn test_buffer_pool_limit() {
323        let mut manager = BufferManager::with_capacity(2);
324
325        // Fill the pool
326        let buf1 = manager.acquire_string_buffer();
327        let buf2 = manager.acquire_string_buffer();
328        let buf3 = manager.acquire_string_buffer();
329
330        manager.release_string_buffer(buf1);
331        manager.release_string_buffer(buf2);
332        manager.release_string_buffer(buf3);
333
334        // Pool should be limited to 2
335        assert_eq!(manager.pool_size(), 2);
336    }
337
338    #[test]
339    fn test_buffer_cleared_on_reuse() {
340        let mut manager = BufferManager::new();
341
342        let mut buffer = manager.acquire_string_buffer();
343        buffer.push_str("old content");
344        manager.release_string_buffer(buffer);
345
346        let reused_buffer = manager.acquire_string_buffer();
347        assert_eq!(reused_buffer.len(), 0); // Should be cleared
348        assert_eq!(reused_buffer, "");
349
350        manager.release_string_buffer(reused_buffer);
351    }
352
353    #[test]
354    fn test_hit_rate() {
355        let mut manager = BufferManager::new();
356
357        let buf1 = manager.acquire_string_buffer();
358        manager.release_string_buffer(buf1);
359
360        let buf2 = manager.acquire_string_buffer(); // Hit
361        manager.release_string_buffer(buf2);
362
363        let buf3 = manager.acquire_string_buffer(); // Hit
364        manager.release_string_buffer(buf3);
365
366        // 3 acquisitions: 1 miss, 2 hits = 66.7% hit rate
367        let hit_rate = manager.hit_rate();
368        assert!((hit_rate - 0.666).abs() < 0.01);
369    }
370
371    #[test]
372    fn test_blank_node_id_generation() {
373        let mut manager = BufferManager::new();
374
375        let id1 = manager.generate_blank_node_id(0);
376        assert_eq!(id1, "_:b0");
377
378        let id2 = manager.generate_blank_node_id(42);
379        assert_eq!(id2, "_:b42");
380
381        let id3 = manager.generate_blank_node_id(999);
382        assert_eq!(id3, "_:b999");
383    }
384
385    #[test]
386    fn test_prefixed_name_expansion() {
387        let mut manager = BufferManager::new();
388
389        let iri = manager.expand_prefixed_name("ex", "Person", "http://example.org/");
390        assert_eq!(iri, "http://example.org/Person");
391
392        let iri2 = manager.expand_prefixed_name(
393            "rdf",
394            "type",
395            "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
396        );
397        assert_eq!(iri2, "http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
398    }
399
400    #[test]
401    fn test_buffer_capacity_hint() {
402        let mut manager = BufferManager::new();
403
404        let buffer = manager.acquire_string_buffer_with_capacity(1024);
405        assert!(buffer.capacity() >= 1024);
406
407        manager.release_string_buffer(buffer);
408
409        // Acquire again - should get the same high-capacity buffer
410        let buffer2 = manager.acquire_string_buffer_with_capacity(512);
411        assert!(buffer2.capacity() >= 512); // Should reuse the 1024-capacity buffer
412
413        manager.release_string_buffer(buffer2);
414    }
415
416    #[test]
417    fn test_stats_report() {
418        let mut manager = BufferManager::new();
419
420        let buf = manager.acquire_string_buffer();
421        manager.release_string_buffer(buf);
422
423        let report = manager.stats().report();
424        assert!(report.contains("Total acquisitions: 1"));
425        assert!(report.contains("Pool hits: 0"));
426        assert!(report.contains("Pool misses: 1"));
427        assert!(report.contains("Total releases: 1"));
428    }
429
430    #[test]
431    fn test_clear() {
432        let mut manager = BufferManager::new();
433
434        let buf = manager.acquire_string_buffer();
435        manager.release_string_buffer(buf);
436
437        assert_eq!(manager.pool_size(), 1);
438        assert_eq!(manager.stats().total_acquisitions, 1);
439
440        manager.clear();
441
442        assert_eq!(manager.pool_size(), 0);
443        assert_eq!(manager.stats().total_acquisitions, 0);
444    }
445
446    #[test]
447    fn test_global_buffer_manager() {
448        let manager = GlobalBufferManager::new();
449
450        let buf1 = manager.acquire_string_buffer();
451        manager.release_string_buffer(buf1);
452
453        let buf2 = manager.acquire_string_buffer();
454        manager.release_string_buffer(buf2);
455
456        // Should have hit the pool
457        assert!(manager.hit_rate() > 0.0);
458    }
459
460    #[test]
461    fn test_global_buffer_manager_clone() {
462        let manager1 = GlobalBufferManager::new();
463        let manager2 = manager1.clone();
464
465        let buf = manager1.acquire_string_buffer();
466        manager2.release_string_buffer(buf);
467
468        // Both should share the same pool
469        assert_eq!(manager1.stats().total_releases, 1);
470        assert_eq!(manager2.stats().total_releases, 1);
471    }
472}