vtcode_core/core/
memory_pool.rs1use parking_lot::Mutex;
4use serde_json::Value;
5use std::collections::VecDeque;
6use std::sync::Arc;
7use vtcode_config::MemoryPoolConfig;
8
9#[derive(Debug, Clone, Default)]
11pub struct MemoryPoolStats {
12 pub string_hits: usize,
14 pub string_misses: usize,
16 pub value_hits: usize,
18 pub value_misses: usize,
20 pub vec_hits: usize,
22 pub vec_misses: usize,
24 pub allocations_avoided: usize,
26}
27
28pub struct MemoryPool {
30 string_pool: Mutex<VecDeque<String>>,
31 value_pool: Mutex<VecDeque<Value>>,
32 vec_pool: Mutex<VecDeque<Vec<String>>>,
33 stats: Mutex<MemoryPoolStats>,
34}
35
36impl MemoryPool {
37 pub fn new() -> Self {
39 Self {
40 string_pool: Mutex::new(VecDeque::with_capacity(64)),
41 value_pool: Mutex::new(VecDeque::with_capacity(32)),
42 vec_pool: Mutex::new(VecDeque::with_capacity(16)),
43 stats: Mutex::new(MemoryPoolStats::default()),
44 }
45 }
46
47 pub fn with_capacities(string_capacity: usize, value_capacity: usize, vec_capacity: usize) -> Self {
49 Self {
50 string_pool: Mutex::new(VecDeque::with_capacity(string_capacity)),
51 value_pool: Mutex::new(VecDeque::with_capacity(value_capacity)),
52 vec_pool: Mutex::new(VecDeque::with_capacity(vec_capacity)),
53 stats: Mutex::new(MemoryPoolStats::default()),
54 }
55 }
56
57 pub fn from_config(config: &MemoryPoolConfig) -> Self {
59 Self {
60 string_pool: Mutex::new(VecDeque::with_capacity(config.max_string_pool_size)),
61 value_pool: Mutex::new(VecDeque::with_capacity(config.max_value_pool_size)),
62 vec_pool: Mutex::new(VecDeque::with_capacity(config.max_vec_pool_size)),
63 stats: Mutex::new(MemoryPoolStats::default()),
64 }
65 }
66
67 pub fn get_string(&self) -> String {
69 let mut stats = self.stats.lock();
70 let result = self.string_pool.lock().pop_front();
71 if let Some(mut s) = result {
72 stats.string_hits += 1;
73 stats.allocations_avoided += 1;
74 s.clear();
75 s
76 } else {
77 stats.string_misses += 1;
78 String::new()
79 }
80 }
81
82 pub fn return_string(&self, mut s: String) {
85 if s.capacity() > 4096 {
87 s.shrink_to(256);
88 }
89 s.clear();
90 let mut pool = self.string_pool.lock();
91 if pool.len() < pool.capacity() {
93 pool.push_back(s);
94 }
95 }
96
97 pub fn get_value(&self) -> Value {
99 let mut stats = self.stats.lock();
100 let result = self.value_pool.lock().pop_front();
101 if let Some(v) = result {
102 stats.value_hits += 1;
103 stats.allocations_avoided += 1;
104 v
105 } else {
106 stats.value_misses += 1;
107 Value::Null
108 }
109 }
110
111 pub fn return_value(&self, v: Value) {
114 let should_pool = match &v {
116 Value::Null | Value::Bool(_) | Value::Number(_) => true,
117 Value::String(s) => s.len() < 1024,
118 Value::Array(arr) => arr.is_empty(),
119 Value::Object(obj) => obj.is_empty(),
120 };
121
122 if should_pool {
123 let mut pool = self.value_pool.lock();
124 if pool.len() < pool.capacity() {
125 pool.push_back(v);
126 }
127 }
128 }
129
130 pub fn get_vec(&self) -> Vec<String> {
132 let mut stats = self.stats.lock();
133 let result = self.vec_pool.lock().pop_front();
134 if let Some(mut v) = result {
135 stats.vec_hits += 1;
136 stats.allocations_avoided += 1;
137 v.clear();
138 v
139 } else {
140 stats.vec_misses += 1;
141 Vec::new()
142 }
143 }
144
145 pub fn return_vec(&self, mut v: Vec<String>) {
148 if v.capacity() > 128 {
150 v = Vec::with_capacity(32);
151 } else {
152 v.clear();
153 }
154 let mut pool = self.vec_pool.lock();
155 if pool.len() < pool.capacity() {
157 pool.push_back(v);
158 }
159 }
160
161 pub fn get_stats(&self) -> MemoryPoolStats {
163 self.stats.lock().clone()
164 }
165
166 pub fn reset_stats(&self) {
168 *self.stats.lock() = MemoryPoolStats::default();
169 }
170
171 pub fn auto_tune(&self, config: &MemoryPoolConfig) -> MemoryPoolTuningRecommendation {
174 let stats = self.get_stats();
175
176 let string_hit_rate = if stats.string_hits + stats.string_misses > 0 {
178 stats.string_hits as f64 / (stats.string_hits + stats.string_misses) as f64
179 } else {
180 0.0
181 };
182
183 let value_hit_rate = if stats.value_hits + stats.value_misses > 0 {
184 stats.value_hits as f64 / (stats.value_hits + stats.value_misses) as f64
185 } else {
186 0.0
187 };
188
189 let vec_hit_rate = if stats.vec_hits + stats.vec_misses > 0 {
190 stats.vec_hits as f64 / (stats.vec_hits + stats.vec_misses) as f64
191 } else {
192 0.0
193 };
194
195 let string_utilization = self.string_pool.lock().len() as f64 / config.max_string_pool_size as f64;
197 let value_utilization = self.value_pool.lock().len() as f64 / config.max_value_pool_size as f64;
198 let vec_utilization = self.vec_pool.lock().len() as f64 / config.max_vec_pool_size as f64;
199
200 MemoryPoolTuningRecommendation {
202 string_hit_rate,
203 value_hit_rate,
204 vec_hit_rate,
205 string_utilization,
206 value_utilization,
207 vec_utilization,
208 total_allocations_avoided: stats.allocations_avoided,
209
210 string_size_recommendation: calculate_size_recommendation(
212 string_hit_rate,
213 string_utilization,
214 config.max_string_pool_size,
215 ),
216 value_size_recommendation: calculate_size_recommendation(
217 value_hit_rate,
218 value_utilization,
219 config.max_value_pool_size,
220 ),
221 vec_size_recommendation: calculate_size_recommendation(
222 vec_hit_rate,
223 vec_utilization,
224 config.max_vec_pool_size,
225 ),
226 }
227 }
228}
229
230fn calculate_size_recommendation(hit_rate: f64, utilization: f64, current_size: usize) -> SizeRecommendation {
232 if hit_rate < 0.3 {
233 if utilization > 0.8 {
235 SizeRecommendation::Increase(current_size.saturating_mul(2))
236 } else {
237 SizeRecommendation::Maintain
238 }
239 } else if hit_rate > 0.7 {
240 if utilization > 0.9 {
242 SizeRecommendation::Increase(current_size.saturating_add(16))
243 } else if utilization < 0.5 {
244 SizeRecommendation::Decrease(current_size.saturating_sub(8).max(16))
245 } else {
246 SizeRecommendation::Maintain
247 }
248 } else {
249 if utilization > 0.85 {
251 SizeRecommendation::Increase(current_size.saturating_add(8))
252 } else {
253 SizeRecommendation::Maintain
254 }
255 }
256}
257
258#[derive(Debug, Clone)]
260pub struct MemoryPoolTuningRecommendation {
261 pub string_hit_rate: f64,
263 pub value_hit_rate: f64,
265 pub vec_hit_rate: f64,
267 pub string_utilization: f64,
269 pub value_utilization: f64,
271 pub vec_utilization: f64,
273 pub total_allocations_avoided: usize,
275 pub string_size_recommendation: SizeRecommendation,
277 pub value_size_recommendation: SizeRecommendation,
279 pub vec_size_recommendation: SizeRecommendation,
281}
282
283#[derive(Debug, Clone, Copy)]
285pub enum SizeRecommendation {
286 Maintain,
288 Increase(usize),
290 Decrease(usize),
292}
293
294impl From<MemoryPoolStats> for crate::telemetry::MemoryPoolTelemetry {
295 fn from(stats: MemoryPoolStats) -> Self {
296 Self {
297 string_hit_rate: if stats.string_hits + stats.string_misses > 0 {
298 stats.string_hits as f64 / (stats.string_hits + stats.string_misses) as f64
299 } else {
300 0.0
301 },
302 value_hit_rate: if stats.value_hits + stats.value_misses > 0 {
303 stats.value_hits as f64 / (stats.value_hits + stats.value_misses) as f64
304 } else {
305 0.0
306 },
307 vec_hit_rate: if stats.vec_hits + stats.vec_misses > 0 {
308 stats.vec_hits as f64 / (stats.vec_hits + stats.vec_misses) as f64
309 } else {
310 0.0
311 },
312 total_allocations_avoided: stats.allocations_avoided,
313 }
314 }
315}
316
317impl Default for MemoryPool {
318 fn default() -> Self {
319 Self::new()
320 }
321}
322
323impl MemoryPool {
324 pub fn pre_warm(&self, string_count: usize, value_count: usize, vec_count: usize) {
327 {
328 let mut pool = self.string_pool.lock();
329 let to_add = string_count.min(pool.capacity().saturating_sub(pool.len()));
330 for _ in 0..to_add {
331 pool.push_back(String::with_capacity(256));
332 }
333 }
334 {
335 let mut pool = self.value_pool.lock();
336 let to_add = value_count.min(pool.capacity().saturating_sub(pool.len()));
337 for _ in 0..to_add {
338 pool.push_back(Value::Null);
339 }
340 }
341 {
342 let mut pool = self.vec_pool.lock();
343 let to_add = vec_count.min(pool.capacity().saturating_sub(pool.len()));
344 for _ in 0..to_add {
345 pool.push_back(Vec::with_capacity(16));
346 }
347 }
348 }
349
350 pub fn get_string_with_capacity(&self, capacity: usize) -> String {
352 let mut stats = self.stats.lock();
353 let result = self.string_pool.lock().pop_front();
354 if let Some(mut s) = result {
355 stats.string_hits += 1;
356 stats.allocations_avoided += 1;
357 s.clear();
358 if s.capacity() < capacity {
360 s.reserve(capacity - s.capacity());
361 }
362 s
363 } else {
364 stats.string_misses += 1;
365 String::with_capacity(capacity)
366 }
367 }
368}
369
370static MEMORY_POOL: once_cell::sync::Lazy<Arc<MemoryPool>> = once_cell::sync::Lazy::new(|| Arc::new(MemoryPool::new()));
372
373pub fn global_pool() -> Arc<MemoryPool> {
375 Arc::clone(&MEMORY_POOL)
376}