mnemosyne_local/
fast_path_cache.rs1use mnemosyne_core::NUM_SIZE_CLASSES;
9
10pub struct FastPathCacheConfig {
12 pub enabled: bool,
14 pub max_blocks_per_class: usize,
16 pub cached_size_classes: &'static [usize],
18}
19
20impl Default for FastPathCacheConfig {
21 fn default() -> Self {
22 Self {
23 enabled: true,
24 max_blocks_per_class: 256,
25 cached_size_classes: &[0, 1, 3, 7, 11],
27 }
28 }
29}
30
31#[derive(Clone, Copy, Debug)]
33pub struct CacheBlock {
34 pub ptr: *mut u8,
36 pub generation: u32,
38}
39
40impl CacheBlock {
41 #[inline]
43 pub fn new(ptr: *mut u8, generation: u32) -> Self {
44 Self { ptr, generation }
45 }
46
47 #[inline]
49 pub fn is_valid(&self) -> bool {
50 !self.ptr.is_null()
51 }
52}
53
54#[derive(Clone, Copy, Debug)]
56pub struct SizeClassCache {
57 pub bump: usize,
59 pub capacity: usize,
61 pub generation: u32,
63 pub hits: usize,
65 pub misses: usize,
67}
68
69impl SizeClassCache {
70 #[inline]
72 pub const fn new(capacity: usize) -> Self {
73 Self {
74 bump: 0,
75 capacity,
76 generation: 0,
77 hits: 0,
78 misses: 0,
79 }
80 }
81
82 #[inline]
84 pub fn has_space(&self) -> bool {
85 self.bump < self.capacity
86 }
87
88 #[inline]
91 pub fn allocate(&mut self) -> Option<usize> {
92 if self.has_space() {
93 let idx = self.bump;
94 self.bump += 1;
95 self.hits += 1;
96 Some(idx)
97 } else {
98 self.misses += 1;
99 None
100 }
101 }
102
103 #[inline]
106 pub fn reset(&mut self) {
107 self.bump = 0;
108 self.generation = self.generation.wrapping_add(1);
109 }
110
111 #[inline]
113 pub fn hit_ratio(&self) -> u8 {
114 let total = self.hits.saturating_add(self.misses);
115 if total == 0 {
116 0
117 } else {
118 ((self.hits as u128 * 100) / total as u128) as u8
119 }
120 }
121}
122
123pub struct FastPathCacheManager {
129 pub caches: [SizeClassCache; NUM_SIZE_CLASSES],
131 pub config: FastPathCacheConfig,
133 pub fast_path_allocations: usize,
135 pub fast_path_deallocations: usize,
137 pub slow_path_allocations: usize,
139}
140
141impl FastPathCacheManager {
142 pub fn new() -> Self {
144 Self::with_config(FastPathCacheConfig::default())
145 }
146
147 pub fn with_config(config: FastPathCacheConfig) -> Self {
149 let mut caches: [SizeClassCache; NUM_SIZE_CLASSES] =
150 [SizeClassCache::new(0); NUM_SIZE_CLASSES];
151
152 if config.enabled {
153 for &size_class in config.cached_size_classes {
154 if size_class < NUM_SIZE_CLASSES {
155 caches[size_class] = SizeClassCache::new(config.max_blocks_per_class);
156 }
157 }
158 }
159
160 Self {
161 caches,
162 config,
163 fast_path_allocations: 0,
164 fast_path_deallocations: 0,
165 slow_path_allocations: 0,
166 }
167 }
168
169 #[inline]
172 pub fn try_allocate(&mut self, size_class: usize) -> Option<usize> {
173 if !self.config.enabled || size_class >= NUM_SIZE_CLASSES {
174 return None;
175 }
176
177 if self.caches[size_class].capacity == 0 {
179 return None;
180 }
181
182 match self.caches[size_class].allocate() {
183 Some(idx) => {
184 self.fast_path_allocations += 1;
185 Some(idx)
186 }
187 None => {
188 self.slow_path_allocations += 1;
189 None
190 }
191 }
192 }
193
194 #[inline]
196 pub fn record_deallocation(&mut self, size_class: usize) {
197 if size_class < NUM_SIZE_CLASSES {
198 self.fast_path_deallocations += 1;
199 }
200 }
201
202 #[inline]
204 pub fn reset_class_cache(&mut self, size_class: usize) {
205 if size_class < NUM_SIZE_CLASSES {
206 self.caches[size_class].reset();
207 }
208 }
209
210 pub fn reset_all(&mut self) {
212 for cache in &mut self.caches {
213 if cache.capacity > 0 {
214 cache.reset();
215 }
216 }
217 }
218
219 #[inline]
221 pub fn class_stats(&self, size_class: usize) -> Option<(usize, u8)> {
222 if size_class < NUM_SIZE_CLASSES {
223 let cache = self.caches[size_class];
224 Some((cache.hits.saturating_add(cache.misses), cache.hit_ratio()))
225 } else {
226 None
227 }
228 }
229
230 pub fn efficiency_metrics(&self) -> FastPathEfficiencyMetrics {
232 let total_requests = self
233 .fast_path_allocations
234 .saturating_add(self.slow_path_allocations);
235 let fast_path_ratio = if total_requests > 0 {
236 ((self.fast_path_allocations as u128 * 100) / total_requests as u128) as u8
237 } else {
238 0
239 };
240
241 let mut avg_cache_hit_ratio = 0u32;
242 let mut active_classes = 0usize;
243 for cache in &self.caches {
244 if cache.capacity > 0 {
245 avg_cache_hit_ratio += cache.hit_ratio() as u32;
246 active_classes += 1;
247 }
248 }
249 let avg_hit_ratio = if active_classes > 0 {
250 (avg_cache_hit_ratio / active_classes as u32) as u8
251 } else {
252 0
253 };
254
255 FastPathEfficiencyMetrics {
256 fast_path_allocations: self.fast_path_allocations,
257 slow_path_allocations: self.slow_path_allocations,
258 fast_path_ratio,
259 avg_cache_hit_ratio: avg_hit_ratio,
260 total_cache_accesses: total_requests,
261 }
262 }
263}
264
265impl Default for FastPathCacheManager {
266 fn default() -> Self {
267 Self::new()
268 }
269}
270
271#[derive(Clone, Copy, Debug)]
273pub struct FastPathEfficiencyMetrics {
274 pub fast_path_allocations: usize,
276 pub slow_path_allocations: usize,
278 pub fast_path_ratio: u8,
280 pub avg_cache_hit_ratio: u8,
282 pub total_cache_accesses: usize,
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn test_cache_block_validity() {
292 let ptr = 0x1000 as *mut u8;
293 let block = CacheBlock::new(ptr, 0);
294 assert!(block.is_valid());
295
296 let null_block = CacheBlock::new(core::ptr::null_mut(), 0);
297 assert!(!null_block.is_valid());
298 }
299
300 #[test]
301 fn test_size_class_cache_allocation() {
302 let mut cache = SizeClassCache::new(10);
303 assert!(cache.has_space());
304 assert_eq!(cache.allocate(), Some(0));
305 assert_eq!(cache.allocate(), Some(1));
306 assert_eq!(cache.hits, 2);
307 }
308
309 #[test]
310 fn test_size_class_cache_full() {
311 let mut cache = SizeClassCache::new(2);
312 assert_eq!(cache.allocate(), Some(0));
313 assert_eq!(cache.allocate(), Some(1));
314 assert_eq!(cache.allocate(), None);
315 assert_eq!(cache.misses, 1);
316 }
317
318 #[test]
319 fn test_size_class_cache_hit_ratio() {
320 let mut cache = SizeClassCache::new(100);
321 for _ in 0..80 {
322 let _ = cache.allocate();
323 }
324 cache.misses = 20;
325 assert_eq!(cache.hit_ratio(), 80);
326 }
327
328 #[test]
329 fn test_fast_path_cache_manager() {
330 let mut manager = FastPathCacheManager::new();
331
332 assert!(manager.try_allocate(0).is_some());
334 assert_eq!(manager.fast_path_allocations, 1);
335
336 assert!(manager.try_allocate(2).is_none());
338 }
339
340 #[test]
341 fn test_efficiency_metrics() {
342 let mut manager = FastPathCacheManager::new();
343
344 let _ = manager.try_allocate(0);
345 let _ = manager.try_allocate(0);
346
347 let metrics = manager.efficiency_metrics();
348 assert_eq!(metrics.fast_path_allocations, 2);
349 assert_eq!(metrics.total_cache_accesses, 2);
350 }
351}