Skip to main content

vtcode_core/core/
memory_pool.rs

1//! Memory pool for reducing allocations in hot paths
2
3use parking_lot::Mutex;
4use serde_json::Value;
5use std::collections::VecDeque;
6use std::sync::Arc;
7use vtcode_config::MemoryPoolConfig;
8
9/// Memory pool statistics for auto-tuning
10#[derive(Debug, Clone, Default)]
11pub struct MemoryPoolStats {
12    pub string_hits: usize,
13    pub string_misses: usize,
14    pub value_hits: usize,
15    pub value_misses: usize,
16    pub vec_hits: usize,
17    pub vec_misses: usize,
18    pub allocations_avoided: usize,
19}
20
21/// Pre-allocated memory pools for common data structures
22pub struct MemoryPool {
23    string_pool: Mutex<VecDeque<String>>,
24    value_pool: Mutex<VecDeque<Value>>,
25    vec_pool: Mutex<VecDeque<Vec<String>>>,
26    stats: Mutex<MemoryPoolStats>,
27}
28
29impl MemoryPool {
30    pub fn new() -> Self {
31        Self {
32            string_pool: Mutex::new(VecDeque::with_capacity(64)),
33            value_pool: Mutex::new(VecDeque::with_capacity(32)),
34            vec_pool: Mutex::new(VecDeque::with_capacity(16)),
35            stats: Mutex::new(MemoryPoolStats::default()),
36        }
37    }
38
39    /// Create a new memory pool with custom sizes
40    pub fn with_capacities(
41        string_capacity: usize,
42        value_capacity: usize,
43        vec_capacity: usize,
44    ) -> Self {
45        Self {
46            string_pool: Mutex::new(VecDeque::with_capacity(string_capacity)),
47            value_pool: Mutex::new(VecDeque::with_capacity(value_capacity)),
48            vec_pool: Mutex::new(VecDeque::with_capacity(vec_capacity)),
49            stats: Mutex::new(MemoryPoolStats::default()),
50        }
51    }
52
53    /// Create a memory pool from configuration
54    pub fn from_config(config: &MemoryPoolConfig) -> Self {
55        Self {
56            string_pool: Mutex::new(VecDeque::with_capacity(config.max_string_pool_size)),
57            value_pool: Mutex::new(VecDeque::with_capacity(config.max_value_pool_size)),
58            vec_pool: Mutex::new(VecDeque::with_capacity(config.max_vec_pool_size)),
59            stats: Mutex::new(MemoryPoolStats::default()),
60        }
61    }
62
63    /// Get a reusable string, clearing it first
64    pub fn get_string(&self) -> String {
65        let mut stats = self.stats.lock();
66        let result = self.string_pool.lock().pop_front();
67        if let Some(mut s) = result {
68            stats.string_hits += 1;
69            stats.allocations_avoided += 1;
70            s.clear();
71            s
72        } else {
73            stats.string_misses += 1;
74            String::new()
75        }
76    }
77
78    /// Return a string to the pool after clearing it
79    /// Optimization: Only clear if string has content, preserve capacity for reuse
80    pub fn return_string(&self, mut s: String) {
81        // Optimization: Preserve capacity for strings with reasonable size
82        // This avoids reallocations when the string is reused
83        if s.capacity() > 4096 {
84            // Large strings: shrink to avoid memory bloat
85            s = String::with_capacity(256);
86        } else {
87            s.clear();
88        }
89        let mut pool = self.string_pool.lock();
90        // Use capacity as the limit to respect configuration
91        if pool.len() < pool.capacity() {
92            pool.push_back(s);
93        }
94    }
95
96    /// Get a reusable Value
97    pub fn get_value(&self) -> Value {
98        let mut stats = self.stats.lock();
99        let result = self.value_pool.lock().pop_front();
100        if let Some(v) = result {
101            stats.value_hits += 1;
102            stats.allocations_avoided += 1;
103            v
104        } else {
105            stats.value_misses += 1;
106            Value::Null
107        }
108    }
109
110    /// Return a Value to the pool
111    /// Optimization: Only pool simple values to avoid memory bloat from large objects
112    pub fn return_value(&self, v: Value) {
113        // Optimization: Only pool null/simple values, skip large objects/arrays
114        let should_pool = match &v {
115            Value::Null | Value::Bool(_) | Value::Number(_) => true,
116            Value::String(s) => s.len() < 1024,
117            Value::Array(arr) => arr.is_empty(),
118            Value::Object(obj) => obj.is_empty(),
119        };
120
121        if should_pool {
122            let mut pool = self.value_pool.lock();
123            if pool.len() < pool.capacity() {
124                pool.push_back(v);
125            }
126        }
127    }
128
129    /// Get a reusable `Vec<String>`
130    pub fn get_vec(&self) -> Vec<String> {
131        let mut stats = self.stats.lock();
132        let result = self.vec_pool.lock().pop_front();
133        if let Some(mut v) = result {
134            stats.vec_hits += 1;
135            stats.allocations_avoided += 1;
136            v.clear();
137            v
138        } else {
139            stats.vec_misses += 1;
140            Vec::new()
141        }
142    }
143
144    /// Return a `Vec<String>` to the pool after clearing it
145    /// Optimization: Preserve capacity for vectors with reasonable size
146    pub fn return_vec(&self, mut v: Vec<String>) {
147        // Optimization: Shrink large vectors to avoid memory bloat
148        if v.capacity() > 128 {
149            v = Vec::with_capacity(32);
150        } else {
151            v.clear();
152        }
153        let mut pool = self.vec_pool.lock();
154        // Use capacity as the limit to respect configuration
155        if pool.len() < pool.capacity() {
156            pool.push_back(v);
157        }
158    }
159
160    /// Get memory pool statistics
161    pub fn get_stats(&self) -> MemoryPoolStats {
162        self.stats.lock().clone()
163    }
164
165    /// Reset statistics
166    pub fn reset_stats(&self) {
167        *self.stats.lock() = MemoryPoolStats::default();
168    }
169
170    /// Auto-tune pool sizes based on usage patterns
171    /// Returns recommendations for configuration adjustments
172    pub fn auto_tune(&self, config: &MemoryPoolConfig) -> MemoryPoolTuningRecommendation {
173        let stats = self.get_stats();
174
175        // Calculate hit rates
176        let string_hit_rate = if stats.string_hits + stats.string_misses > 0 {
177            stats.string_hits as f64 / (stats.string_hits + stats.string_misses) as f64
178        } else {
179            0.0
180        };
181
182        let value_hit_rate = if stats.value_hits + stats.value_misses > 0 {
183            stats.value_hits as f64 / (stats.value_hits + stats.value_misses) as f64
184        } else {
185            0.0
186        };
187
188        let vec_hit_rate = if stats.vec_hits + stats.vec_misses > 0 {
189            stats.vec_hits as f64 / (stats.vec_hits + stats.vec_misses) as f64
190        } else {
191            0.0
192        };
193
194        // Calculate current pool utilization
195        let string_utilization =
196            self.string_pool.lock().len() as f64 / config.max_string_pool_size as f64;
197        let value_utilization =
198            self.value_pool.lock().len() as f64 / config.max_value_pool_size as f64;
199        let vec_utilization = self.vec_pool.lock().len() as f64 / config.max_vec_pool_size as f64;
200
201        // Generate tuning recommendations
202        MemoryPoolTuningRecommendation {
203            string_hit_rate,
204            value_hit_rate,
205            vec_hit_rate,
206            string_utilization,
207            value_utilization,
208            vec_utilization,
209            total_allocations_avoided: stats.allocations_avoided,
210
211            // Recommendations based on usage patterns
212            string_size_recommendation: calculate_size_recommendation(
213                string_hit_rate,
214                string_utilization,
215                config.max_string_pool_size,
216            ),
217            value_size_recommendation: calculate_size_recommendation(
218                value_hit_rate,
219                value_utilization,
220                config.max_value_pool_size,
221            ),
222            vec_size_recommendation: calculate_size_recommendation(
223                vec_hit_rate,
224                vec_utilization,
225                config.max_vec_pool_size,
226            ),
227        }
228    }
229}
230
231/// Calculate size recommendation based on hit rate and utilization
232fn calculate_size_recommendation(
233    hit_rate: f64,
234    utilization: f64,
235    current_size: usize,
236) -> SizeRecommendation {
237    if hit_rate < 0.3 {
238        // Low hit rate - pool might be too small or not used effectively
239        if utilization > 0.8 {
240            SizeRecommendation::Increase(current_size.saturating_mul(2))
241        } else {
242            SizeRecommendation::Maintain
243        }
244    } else if hit_rate > 0.7 {
245        // High hit rate - pool is working well
246        if utilization > 0.9 {
247            SizeRecommendation::Increase(current_size.saturating_add(16))
248        } else if utilization < 0.5 {
249            SizeRecommendation::Decrease(current_size.saturating_sub(8).max(16))
250        } else {
251            SizeRecommendation::Maintain
252        }
253    } else {
254        // Medium hit rate
255        if utilization > 0.85 {
256            SizeRecommendation::Increase(current_size.saturating_add(8))
257        } else {
258            SizeRecommendation::Maintain
259        }
260    }
261}
262
263/// Memory pool tuning recommendation
264#[derive(Debug, Clone)]
265pub struct MemoryPoolTuningRecommendation {
266    pub string_hit_rate: f64,
267    pub value_hit_rate: f64,
268    pub vec_hit_rate: f64,
269    pub string_utilization: f64,
270    pub value_utilization: f64,
271    pub vec_utilization: f64,
272    pub total_allocations_avoided: usize,
273    pub string_size_recommendation: SizeRecommendation,
274    pub value_size_recommendation: SizeRecommendation,
275    pub vec_size_recommendation: SizeRecommendation,
276}
277
278/// Size recommendation enum
279#[derive(Debug, Clone, Copy)]
280pub enum SizeRecommendation {
281    Maintain,
282    Increase(usize),
283    Decrease(usize),
284}
285
286impl From<MemoryPoolStats> for crate::telemetry::MemoryPoolTelemetry {
287    fn from(stats: MemoryPoolStats) -> Self {
288        Self {
289            string_hit_rate: if stats.string_hits + stats.string_misses > 0 {
290                stats.string_hits as f64 / (stats.string_hits + stats.string_misses) as f64
291            } else {
292                0.0
293            },
294            value_hit_rate: if stats.value_hits + stats.value_misses > 0 {
295                stats.value_hits as f64 / (stats.value_hits + stats.value_misses) as f64
296            } else {
297                0.0
298            },
299            vec_hit_rate: if stats.vec_hits + stats.vec_misses > 0 {
300                stats.vec_hits as f64 / (stats.vec_hits + stats.vec_misses) as f64
301            } else {
302                0.0
303            },
304            total_allocations_avoided: stats.allocations_avoided,
305        }
306    }
307}
308
309impl Default for MemoryPool {
310    fn default() -> Self {
311        Self::new()
312    }
313}
314
315impl MemoryPool {
316    /// Pre-warm the pool with initial allocations
317    /// Call this during startup to avoid allocation latency during hot paths
318    pub fn pre_warm(&self, string_count: usize, value_count: usize, vec_count: usize) {
319        {
320            let mut pool = self.string_pool.lock();
321            let to_add = string_count.min(pool.capacity().saturating_sub(pool.len()));
322            for _ in 0..to_add {
323                pool.push_back(String::with_capacity(256));
324            }
325        }
326        {
327            let mut pool = self.value_pool.lock();
328            let to_add = value_count.min(pool.capacity().saturating_sub(pool.len()));
329            for _ in 0..to_add {
330                pool.push_back(Value::Null);
331            }
332        }
333        {
334            let mut pool = self.vec_pool.lock();
335            let to_add = vec_count.min(pool.capacity().saturating_sub(pool.len()));
336            for _ in 0..to_add {
337                pool.push_back(Vec::with_capacity(16));
338            }
339        }
340    }
341
342    /// Get a string with pre-allocated capacity for better performance
343    pub fn get_string_with_capacity(&self, capacity: usize) -> String {
344        let mut stats = self.stats.lock();
345        let result = self.string_pool.lock().pop_front();
346        if let Some(mut s) = result {
347            stats.string_hits += 1;
348            stats.allocations_avoided += 1;
349            s.clear();
350            // Reserve additional capacity if needed
351            if s.capacity() < capacity {
352                s.reserve(capacity - s.capacity());
353            }
354            s
355        } else {
356            stats.string_misses += 1;
357            String::with_capacity(capacity)
358        }
359    }
360}
361
362/// Global memory pool instance
363static MEMORY_POOL: once_cell::sync::Lazy<Arc<MemoryPool>> =
364    once_cell::sync::Lazy::new(|| Arc::new(MemoryPool::new()));
365
366/// Get the global memory pool
367pub fn global_pool() -> Arc<MemoryPool> {
368    Arc::clone(&MEMORY_POOL)
369}