Skip to main content

oxirs_arq/
buffer_management.rs

1//! Advanced Buffer Pool Management
2//!
3//! This module provides specialized buffer pools for frequently allocated objects
4//! to reduce memory pressure and improve performance by minimizing allocations.
5
6use crate::algebra::{Binding, Solution, Term, Variable};
7use std::collections::{HashMap, VecDeque};
8use std::sync::{Arc, Mutex};
9
10/// Manager for multiple specialized buffer pools
11pub struct BufferPoolManager {
12    solution_pool: Arc<Mutex<BufferPool<Solution>>>,
13    binding_pool: Arc<Mutex<BufferPool<Binding>>>,
14    hashmap_pool: Arc<Mutex<BufferPool<HashMap<Variable, Term>>>>,
15    vector_pool: Arc<Mutex<BufferPool<Vec<Binding>>>>,
16    stats: Arc<Mutex<BufferPoolStats>>,
17}
18
19impl BufferPoolManager {
20    /// Create a new buffer pool manager with default configurations
21    pub fn new() -> Self {
22        Self::with_capacities(1000, 2000, 500, 1000)
23    }
24
25    /// Create with custom pool capacities
26    pub fn with_capacities(
27        solution_capacity: usize,
28        binding_capacity: usize,
29        hashmap_capacity: usize,
30        vector_capacity: usize,
31    ) -> Self {
32        Self {
33            solution_pool: Arc::new(Mutex::new(BufferPool::new_for_solutions(solution_capacity))),
34            binding_pool: Arc::new(Mutex::new(BufferPool::new_for_bindings(binding_capacity))),
35            hashmap_pool: Arc::new(Mutex::new(BufferPool::new_for_hashmaps(hashmap_capacity))),
36            vector_pool: Arc::new(Mutex::new(BufferPool::new_for_vectors(vector_capacity))),
37            stats: Arc::new(Mutex::new(BufferPoolStats::default())),
38        }
39    }
40
41    /// Get a solution from the pool (or create new one)
42    pub fn acquire_solution(&self) -> PooledSolution<'_> {
43        let solution = self.solution_pool.lock().expect("lock poisoned").acquire();
44        self.update_stats("solution", true);
45
46        PooledSolution {
47            solution: Some(solution),
48            pool: Arc::clone(&self.solution_pool),
49            manager: Some(self),
50        }
51    }
52
53    /// Get a binding from the pool (or create new one)
54    pub fn acquire_binding(&self) -> PooledBinding<'_> {
55        let binding = self.binding_pool.lock().expect("lock poisoned").acquire();
56        self.update_stats("binding", true);
57
58        PooledBinding {
59            binding: Some(binding),
60            pool: Arc::clone(&self.binding_pool),
61            manager: Some(self),
62        }
63    }
64
65    /// Get a hashmap from the pool (or create new one)
66    pub fn acquire_hashmap(&self) -> PooledHashMap<'_> {
67        let hashmap = self.hashmap_pool.lock().expect("lock poisoned").acquire();
68        self.update_stats("hashmap", true);
69
70        PooledHashMap {
71            hashmap: Some(hashmap),
72            pool: Arc::clone(&self.hashmap_pool),
73            manager: Some(self),
74        }
75    }
76
77    /// Get a vector from the pool (or create new one)
78    pub fn acquire_vector(&self) -> PooledVector<'_> {
79        let vector = self.vector_pool.lock().expect("lock poisoned").acquire();
80        self.update_stats("vector", true);
81
82        PooledVector {
83            vector: Some(vector),
84            pool: Arc::clone(&self.vector_pool),
85            manager: Some(self),
86        }
87    }
88
89    /// Update pool statistics
90    fn update_stats(&self, pool_type: &str, is_acquire: bool) {
91        let mut stats = self.stats.lock().expect("lock poisoned");
92
93        let pool_stats = match pool_type {
94            "solution" => &mut stats.solution_stats,
95            "binding" => &mut stats.binding_stats,
96            "hashmap" => &mut stats.hashmap_stats,
97            "vector" => &mut stats.vector_stats,
98            _ => return,
99        };
100
101        if is_acquire {
102            pool_stats.total_acquires += 1;
103        } else {
104            pool_stats.total_returns += 1;
105        }
106    }
107
108    /// Get comprehensive statistics for all pools
109    pub fn get_stats(&self) -> BufferPoolStats {
110        self.stats.lock().expect("lock poisoned").clone()
111    }
112
113    /// Clear all pools (useful for memory cleanup)
114    pub fn clear_all_pools(&self) {
115        self.solution_pool.lock().expect("lock poisoned").clear();
116        self.binding_pool.lock().expect("lock poisoned").clear();
117        self.hashmap_pool.lock().expect("lock poisoned").clear();
118        self.vector_pool.lock().expect("lock poisoned").clear();
119    }
120
121    /// Get total memory usage estimate for all pools
122    pub fn estimate_total_memory_usage(&self) -> usize {
123        let solution_mem = self
124            .solution_pool
125            .lock()
126            .expect("lock poisoned")
127            .estimate_memory_usage()
128            * std::mem::size_of::<Solution>();
129        let binding_mem = self
130            .binding_pool
131            .lock()
132            .expect("lock poisoned")
133            .estimate_memory_usage()
134            * std::mem::size_of::<Binding>();
135        let hashmap_mem = self
136            .hashmap_pool
137            .lock()
138            .expect("lock poisoned")
139            .estimate_memory_usage()
140            * std::mem::size_of::<HashMap<Variable, Term>>();
141        let vector_mem = self
142            .vector_pool
143            .lock()
144            .expect("lock poisoned")
145            .estimate_memory_usage()
146            * std::mem::size_of::<Vec<Binding>>();
147
148        solution_mem + binding_mem + hashmap_mem + vector_mem
149    }
150}
151
152impl Default for BufferPoolManager {
153    fn default() -> Self {
154        Self::new()
155    }
156}
157
158/// Generic buffer pool for reusing objects
159struct BufferPool<T> {
160    available: VecDeque<T>,
161    max_capacity: usize,
162    factory: fn() -> T,
163}
164
165impl<T> BufferPool<T> {
166    #[allow(dead_code)]
167    fn new(max_capacity: usize) -> Self
168    where
169        T: Default,
170    {
171        Self {
172            available: VecDeque::new(),
173            max_capacity,
174            factory: T::default,
175        }
176    }
177
178    fn acquire(&mut self) -> T {
179        self.available
180            .pop_front()
181            .unwrap_or_else(|| (self.factory)())
182    }
183
184    #[allow(dead_code)]
185    fn return_object(&mut self, mut obj: T) {
186        if self.available.len() < self.max_capacity {
187            // Reset object to clean state before returning to pool
188            self.reset_object(&mut obj);
189            self.available.push_back(obj);
190        }
191        // If pool is full, just drop the object
192    }
193
194    fn clear(&mut self) {
195        self.available.clear();
196    }
197
198    fn estimate_memory_usage(&self) -> usize {
199        self.available.len()
200    }
201
202    #[allow(dead_code)]
203    fn reset_object(&self, _obj: &mut T) {
204        // Default implementation does nothing
205        // Specialized pools will override this behavior
206    }
207}
208
209// Specialized buffer pool implementations with proper reset behavior
210impl BufferPool<Solution> {
211    fn new_for_solutions(max_capacity: usize) -> Self {
212        Self {
213            available: VecDeque::new(),
214            max_capacity,
215            factory: Vec::new,
216        }
217    }
218
219    fn return_solution(&mut self, mut solution: Solution) {
220        if self.available.len() < self.max_capacity {
221            solution.clear(); // Reset to clean state
222            self.available.push_back(solution);
223        }
224    }
225}
226
227impl BufferPool<Binding> {
228    fn new_for_bindings(max_capacity: usize) -> Self {
229        Self {
230            available: VecDeque::new(),
231            max_capacity,
232            factory: HashMap::new,
233        }
234    }
235
236    fn return_binding(&mut self, mut binding: Binding) {
237        if self.available.len() < self.max_capacity {
238            binding.clear(); // Reset to clean state
239            self.available.push_back(binding);
240        }
241    }
242}
243
244impl BufferPool<HashMap<Variable, Term>> {
245    fn new_for_hashmaps(max_capacity: usize) -> Self {
246        Self {
247            available: VecDeque::new(),
248            max_capacity,
249            factory: HashMap::new,
250        }
251    }
252
253    fn return_hashmap(&mut self, mut hashmap: HashMap<Variable, Term>) {
254        if self.available.len() < self.max_capacity {
255            hashmap.clear(); // Reset to clean state
256            self.available.push_back(hashmap);
257        }
258    }
259}
260
261impl BufferPool<Vec<Binding>> {
262    fn new_for_vectors(max_capacity: usize) -> Self {
263        Self {
264            available: VecDeque::new(),
265            max_capacity,
266            factory: Vec::new,
267        }
268    }
269
270    fn return_vector(&mut self, mut vector: Vec<Binding>) {
271        if self.available.len() < self.max_capacity {
272            vector.clear(); // Reset to clean state
273            self.available.push_back(vector);
274        }
275    }
276}
277
278/// RAII wrapper for pooled solutions
279pub struct PooledSolution<'a> {
280    solution: Option<Solution>,
281    pool: Arc<Mutex<BufferPool<Solution>>>,
282    manager: Option<&'a BufferPoolManager>,
283}
284
285impl<'a> PooledSolution<'a> {
286    pub fn get_mut(&mut self) -> &mut Solution {
287        self.solution
288            .as_mut()
289            .expect("pooled solution should exist until drop")
290    }
291
292    pub fn get(&self) -> &Solution {
293        self.solution
294            .as_ref()
295            .expect("pooled solution should exist until drop")
296    }
297}
298
299impl<'a> Drop for PooledSolution<'a> {
300    fn drop(&mut self) {
301        if let Some(solution) = self.solution.take() {
302            self.pool
303                .lock()
304                .expect("lock poisoned")
305                .return_solution(solution);
306            if let Some(manager) = self.manager {
307                manager.update_stats("solution", false);
308            }
309        }
310    }
311}
312
313/// RAII wrapper for pooled bindings
314pub struct PooledBinding<'a> {
315    binding: Option<Binding>,
316    pool: Arc<Mutex<BufferPool<Binding>>>,
317    manager: Option<&'a BufferPoolManager>,
318}
319
320impl<'a> PooledBinding<'a> {
321    pub fn get_mut(&mut self) -> &mut Binding {
322        self.binding
323            .as_mut()
324            .expect("pooled binding should exist until drop")
325    }
326
327    pub fn get(&self) -> &Binding {
328        self.binding
329            .as_ref()
330            .expect("pooled binding should exist until drop")
331    }
332}
333
334impl<'a> Drop for PooledBinding<'a> {
335    fn drop(&mut self) {
336        if let Some(binding) = self.binding.take() {
337            self.pool
338                .lock()
339                .expect("lock poisoned")
340                .return_binding(binding);
341            if let Some(manager) = self.manager {
342                manager.update_stats("binding", false);
343            }
344        }
345    }
346}
347
348/// RAII wrapper for pooled hashmaps
349pub struct PooledHashMap<'a> {
350    hashmap: Option<HashMap<Variable, Term>>,
351    pool: Arc<Mutex<BufferPool<HashMap<Variable, Term>>>>,
352    manager: Option<&'a BufferPoolManager>,
353}
354
355impl<'a> PooledHashMap<'a> {
356    pub fn get_mut(&mut self) -> &mut HashMap<Variable, Term> {
357        self.hashmap
358            .as_mut()
359            .expect("pooled hashmap should exist until drop")
360    }
361
362    pub fn get(&self) -> &HashMap<Variable, Term> {
363        self.hashmap
364            .as_ref()
365            .expect("pooled hashmap should exist until drop")
366    }
367}
368
369impl<'a> Drop for PooledHashMap<'a> {
370    fn drop(&mut self) {
371        if let Some(hashmap) = self.hashmap.take() {
372            self.pool
373                .lock()
374                .expect("lock poisoned")
375                .return_hashmap(hashmap);
376            if let Some(manager) = self.manager {
377                manager.update_stats("hashmap", false);
378            }
379        }
380    }
381}
382
383/// RAII wrapper for pooled vectors
384pub struct PooledVector<'a> {
385    vector: Option<Vec<Binding>>,
386    pool: Arc<Mutex<BufferPool<Vec<Binding>>>>,
387    manager: Option<&'a BufferPoolManager>,
388}
389
390impl<'a> PooledVector<'a> {
391    pub fn get_mut(&mut self) -> &mut Vec<Binding> {
392        self.vector
393            .as_mut()
394            .expect("pooled vector should exist until drop")
395    }
396
397    pub fn get(&self) -> &Vec<Binding> {
398        self.vector
399            .as_ref()
400            .expect("pooled vector should exist until drop")
401    }
402}
403
404impl<'a> Drop for PooledVector<'a> {
405    fn drop(&mut self) {
406        if let Some(vector) = self.vector.take() {
407            self.pool
408                .lock()
409                .expect("lock poisoned")
410                .return_vector(vector);
411            if let Some(manager) = self.manager {
412                manager.update_stats("vector", false);
413            }
414        }
415    }
416}
417
418/// Statistics for buffer pool usage
419#[derive(Debug, Clone, Default)]
420pub struct BufferPoolStats {
421    pub solution_stats: PoolStats,
422    pub binding_stats: PoolStats,
423    pub hashmap_stats: PoolStats,
424    pub vector_stats: PoolStats,
425}
426
427#[derive(Debug, Clone, Default)]
428pub struct PoolStats {
429    pub total_acquires: usize,
430    pub total_returns: usize,
431    pub current_size: usize,
432}
433
434impl BufferPoolStats {
435    /// Get total number of acquisitions across all pools
436    pub fn total_acquisitions(&self) -> usize {
437        self.solution_stats.total_acquires
438            + self.binding_stats.total_acquires
439            + self.hashmap_stats.total_acquires
440            + self.vector_stats.total_acquires
441    }
442
443    /// Get total number of returns across all pools
444    pub fn total_returns(&self) -> usize {
445        self.solution_stats.total_returns
446            + self.binding_stats.total_returns
447            + self.hashmap_stats.total_returns
448            + self.vector_stats.total_returns
449    }
450
451    /// Calculate hit rate (objects returned from pool vs newly created)
452    pub fn hit_rate(&self) -> f64 {
453        let total_acquires = self.total_acquisitions();
454        if total_acquires == 0 {
455            return 0.0;
456        }
457
458        let total_returns = self.total_returns();
459        total_returns as f64 / total_acquires as f64
460    }
461
462    /// Get performance summary
463    pub fn performance_summary(&self) -> String {
464        format!(
465            "Buffer Pool Stats: {} acquisitions, {} returns, {:.2}% hit rate",
466            self.total_acquisitions(),
467            self.total_returns(),
468            self.hit_rate() * 100.0
469        )
470    }
471}
472
473#[cfg(test)]
474mod tests {
475    use super::*;
476
477    #[test]
478    fn test_buffer_pool_manager() {
479        let manager = BufferPoolManager::new();
480
481        // Test solution pooling
482        {
483            let mut pooled_solution = manager.acquire_solution();
484            let solution = pooled_solution.get_mut();
485
486            // Use the solution
487            let mut binding = Binding::new();
488            binding.insert(
489                Variable::new("x").unwrap(),
490                Term::Iri(oxirs_core::model::NamedNode::new_unchecked(
491                    "http://example.org/test",
492                )),
493            );
494            solution.push(binding);
495
496            assert_eq!(solution.len(), 1);
497        } // Solution automatically returned to pool here
498
499        // Test binding pooling
500        {
501            let mut pooled_binding = manager.acquire_binding();
502            let binding = pooled_binding.get_mut();
503
504            binding.insert(
505                Variable::new("y").unwrap(),
506                Term::Iri(oxirs_core::model::NamedNode::new_unchecked(
507                    "http://example.org/test2",
508                )),
509            );
510
511            assert_eq!(binding.len(), 1);
512        } // Binding automatically returned to pool here
513
514        // Check statistics
515        let stats = manager.get_stats();
516        assert!(stats.total_acquisitions() > 0);
517
518        println!("Buffer pool stats: {}", stats.performance_summary());
519    }
520
521    #[test]
522    fn test_buffer_pool_memory_management() {
523        let manager = BufferPoolManager::with_capacities(10, 10, 10, 10);
524
525        // First acquire and then release objects to populate the pools
526        {
527            let _solutions: Vec<_> = (0..5).map(|_| manager.acquire_solution()).collect();
528            // Objects will be returned to pool when this scope ends
529        }
530
531        // Now check that objects were returned to pools
532        let estimated_memory = manager.estimate_total_memory_usage();
533        assert!(
534            estimated_memory > 0,
535            "Expected memory usage > 0, got {estimated_memory}"
536        );
537
538        // Clear pools to free memory
539        manager.clear_all_pools();
540        let memory_after_clear = manager.estimate_total_memory_usage();
541        assert_eq!(memory_after_clear, 0);
542    }
543}