rust_logic_graph/memory/
pool.rs

1//! Memory pooling for reducing allocations
2
3use parking_lot::Mutex;
4use std::collections::HashMap;
5use std::sync::Arc;
6
7use crate::core::Context;
8
9/// Configuration for memory pool
10#[derive(Debug, Clone)]
11pub struct PoolConfig {
12    /// Maximum number of contexts to keep in pool
13    pub max_pooled: usize,
14    /// Initial capacity for context data hashmap
15    pub initial_capacity: usize,
16    /// Enable pool statistics tracking
17    pub track_stats: bool,
18}
19
20impl Default for PoolConfig {
21    fn default() -> Self {
22        Self {
23            max_pooled: 100,
24            initial_capacity: 16,
25            track_stats: true,
26        }
27    }
28}
29
30/// Statistics for pool usage
31#[derive(Debug, Default, Clone)]
32pub struct PoolStats {
33    /// Total number of contexts acquired from pool
34    pub total_acquired: u64,
35    /// Number of times a pooled context was reused
36    pub reused: u64,
37    /// Number of times a new context was created
38    pub created: u64,
39    /// Current pool size
40    pub current_pool_size: usize,
41    /// Peak pool size
42    pub peak_pool_size: usize,
43}
44
45/// Memory pool for Context objects to reduce allocations
46pub struct ContextPool {
47    pool: Arc<Mutex<Vec<Context>>>,
48    config: PoolConfig,
49    stats: Arc<Mutex<PoolStats>>,
50}
51
52impl ContextPool {
53    /// Create a new context pool with default configuration
54    pub fn new() -> Self {
55        Self::with_config(PoolConfig::default())
56    }
57
58    /// Create a new context pool with custom configuration
59    pub fn with_config(config: PoolConfig) -> Self {
60        Self {
61            pool: Arc::new(Mutex::new(Vec::with_capacity(config.max_pooled))),
62            config,
63            stats: Arc::new(Mutex::new(PoolStats::default())),
64        }
65    }
66
67    /// Acquire a context from the pool, or create a new one
68    pub fn acquire(&self) -> Context {
69        let mut pool = self.pool.lock();
70
71        if let Some(mut ctx) = pool.pop() {
72            // Reuse pooled context
73            ctx.data.clear();
74
75            if self.config.track_stats {
76                let mut stats = self.stats.lock();
77                stats.total_acquired += 1;
78                stats.reused += 1;
79                stats.current_pool_size = pool.len();
80            }
81
82            ctx
83        } else {
84            // Create new context
85            if self.config.track_stats {
86                let mut stats = self.stats.lock();
87                stats.total_acquired += 1;
88                stats.created += 1;
89            }
90
91            Context {
92                data: HashMap::with_capacity(self.config.initial_capacity),
93            }
94        }
95    }
96
97    /// Return a context to the pool
98    pub fn release(&self, ctx: Context) {
99        let mut pool = self.pool.lock();
100
101        if pool.len() < self.config.max_pooled {
102            pool.push(ctx);
103
104            if self.config.track_stats {
105                let mut stats = self.stats.lock();
106                stats.current_pool_size = pool.len();
107                if pool.len() > stats.peak_pool_size {
108                    stats.peak_pool_size = pool.len();
109                }
110            }
111        }
112        // If pool is full, context is dropped
113    }
114
115    /// Get pool statistics
116    pub fn stats(&self) -> PoolStats {
117        self.stats.lock().clone()
118    }
119
120    /// Clear all pooled contexts
121    pub fn clear(&self) {
122        let mut pool = self.pool.lock();
123        pool.clear();
124
125        if self.config.track_stats {
126            let mut stats = self.stats.lock();
127            stats.current_pool_size = 0;
128        }
129    }
130
131    /// Get current pool size
132    pub fn size(&self) -> usize {
133        self.pool.lock().len()
134    }
135
136    /// Get reuse rate (percentage of acquired contexts that were reused)
137    pub fn reuse_rate(&self) -> f64 {
138        let stats = self.stats.lock();
139        if stats.total_acquired == 0 {
140            0.0
141        } else {
142            (stats.reused as f64 / stats.total_acquired as f64) * 100.0
143        }
144    }
145}
146
147impl Default for ContextPool {
148    fn default() -> Self {
149        Self::new()
150    }
151}
152
153/// RAII guard for pooled contexts
154pub struct PooledContext {
155    context: Option<Context>,
156    pool: Arc<Mutex<Vec<Context>>>,
157    max_pooled: usize,
158}
159
160impl PooledContext {
161    /// Create a new pooled context guard
162    pub fn new(context: Context, pool: Arc<Mutex<Vec<Context>>>, max_pooled: usize) -> Self {
163        Self {
164            context: Some(context),
165            pool,
166            max_pooled,
167        }
168    }
169
170    /// Get mutable reference to the context
171    pub fn get_mut(&mut self) -> &mut Context {
172        self.context.as_mut().unwrap()
173    }
174
175    /// Get immutable reference to the context
176    pub fn get(&self) -> &Context {
177        self.context.as_ref().unwrap()
178    }
179}
180
181impl Drop for PooledContext {
182    fn drop(&mut self) {
183        if let Some(ctx) = self.context.take() {
184            let mut pool = self.pool.lock();
185            if pool.len() < self.max_pooled {
186                pool.push(ctx);
187            }
188        }
189    }
190}
191
192impl std::ops::Deref for PooledContext {
193    type Target = Context;
194
195    fn deref(&self) -> &Self::Target {
196        self.context.as_ref().unwrap()
197    }
198}
199
200impl std::ops::DerefMut for PooledContext {
201    fn deref_mut(&mut self) -> &mut Self::Target {
202        self.context.as_mut().unwrap()
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    #[test]
211    fn test_pool_acquire_and_release() {
212        let pool = ContextPool::new();
213
214        let ctx1 = pool.acquire();
215        assert_eq!(pool.size(), 0);
216
217        pool.release(ctx1);
218        assert_eq!(pool.size(), 1);
219
220        let ctx2 = pool.acquire();
221        assert_eq!(pool.size(), 0);
222
223        pool.release(ctx2);
224        assert_eq!(pool.size(), 1);
225    }
226
227    #[test]
228    fn test_pool_max_size() {
229        let config = PoolConfig {
230            max_pooled: 2,
231            initial_capacity: 16,
232            track_stats: true,
233        };
234        let pool = ContextPool::with_config(config);
235
236        // Create 3 separate contexts and release them
237        let ctx1 = Context::new();
238        let ctx2 = Context::new();
239        let ctx3 = Context::new();
240
241        pool.release(ctx1);
242        pool.release(ctx2);
243        pool.release(ctx3);
244
245        // Pool should only keep 2
246        assert_eq!(pool.size(), 2);
247    }
248
249    #[test]
250    fn test_pool_stats() {
251        let pool = ContextPool::new();
252
253        let _ctx1 = pool.acquire();
254        let _ctx2 = pool.acquire();
255
256        let stats = pool.stats();
257        assert_eq!(stats.total_acquired, 2);
258        assert_eq!(stats.created, 2);
259        assert_eq!(stats.reused, 0);
260
261        pool.release(_ctx1);
262        pool.release(_ctx2);
263
264        let _ctx3 = pool.acquire();
265        let stats = pool.stats();
266        assert_eq!(stats.total_acquired, 3);
267        assert_eq!(stats.reused, 1);
268    }
269
270    #[test]
271    fn test_reuse_rate() {
272        let pool = ContextPool::new();
273
274        // Create 2 contexts
275        let ctx1 = pool.acquire();
276        let ctx2 = pool.acquire();
277
278        // Return them
279        pool.release(ctx1);
280        pool.release(ctx2);
281
282        // Acquire 2 (reused)
283        let _ctx3 = pool.acquire();
284        let _ctx4 = pool.acquire();
285
286        // Reuse rate should be 50% (2 reused out of 4 total)
287        let rate = pool.reuse_rate();
288        assert_eq!(rate, 50.0);
289    }
290
291    #[test]
292    fn test_pooled_context_guard() {
293        let pool_vec = Arc::new(Mutex::new(Vec::new()));
294        let ctx = Context::new();
295
296        {
297            let _guard = PooledContext::new(ctx, pool_vec.clone(), 10);
298            assert_eq!(pool_vec.lock().len(), 0);
299        }
300
301        // After drop, context should be in pool
302        assert_eq!(pool_vec.lock().len(), 1);
303    }
304}