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,
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
21pub 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 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 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 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 pub fn return_string(&self, mut s: String) {
81 if s.capacity() > 4096 {
84 s = String::with_capacity(256);
86 } else {
87 s.clear();
88 }
89 let mut pool = self.string_pool.lock();
90 if pool.len() < pool.capacity() {
92 pool.push_back(s);
93 }
94 }
95
96 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 pub fn return_value(&self, v: Value) {
113 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 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 pub fn return_vec(&self, mut v: Vec<String>) {
147 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 if pool.len() < pool.capacity() {
156 pool.push_back(v);
157 }
158 }
159
160 pub fn get_stats(&self) -> MemoryPoolStats {
162 self.stats.lock().clone()
163 }
164
165 pub fn reset_stats(&self) {
167 *self.stats.lock() = MemoryPoolStats::default();
168 }
169
170 pub fn auto_tune(&self, config: &MemoryPoolConfig) -> MemoryPoolTuningRecommendation {
173 let stats = self.get_stats();
174
175 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 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 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 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
231fn calculate_size_recommendation(
233 hit_rate: f64,
234 utilization: f64,
235 current_size: usize,
236) -> SizeRecommendation {
237 if hit_rate < 0.3 {
238 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 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 if utilization > 0.85 {
256 SizeRecommendation::Increase(current_size.saturating_add(8))
257 } else {
258 SizeRecommendation::Maintain
259 }
260 }
261}
262
263#[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#[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 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 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 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
362static MEMORY_POOL: once_cell::sync::Lazy<Arc<MemoryPool>> =
364 once_cell::sync::Lazy::new(|| Arc::new(MemoryPool::new()));
365
366pub fn global_pool() -> Arc<MemoryPool> {
368 Arc::clone(&MEMORY_POOL)
369}