use mnemosyne_core::NUM_SIZE_CLASSES;
pub struct FastPathCacheConfig {
pub enabled: bool,
pub max_blocks_per_class: usize,
pub cached_size_classes: &'static [usize],
}
impl Default for FastPathCacheConfig {
fn default() -> Self {
Self {
enabled: true,
max_blocks_per_class: 256,
cached_size_classes: &[0, 1, 3, 7, 11],
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct CacheBlock {
pub ptr: *mut u8,
pub generation: u32,
}
impl CacheBlock {
#[inline]
pub fn new(ptr: *mut u8, generation: u32) -> Self {
Self { ptr, generation }
}
#[inline]
pub fn is_valid(&self) -> bool {
!self.ptr.is_null()
}
}
#[derive(Clone, Copy, Debug)]
pub struct SizeClassCache {
pub bump: usize,
pub capacity: usize,
pub generation: u32,
pub hits: usize,
pub misses: usize,
}
impl SizeClassCache {
#[inline]
pub const fn new(capacity: usize) -> Self {
Self {
bump: 0,
capacity,
generation: 0,
hits: 0,
misses: 0,
}
}
#[inline]
pub fn has_space(&self) -> bool {
self.bump < self.capacity
}
#[inline]
pub fn allocate(&mut self) -> Option<usize> {
if self.has_space() {
let idx = self.bump;
self.bump += 1;
self.hits += 1;
Some(idx)
} else {
self.misses += 1;
None
}
}
#[inline]
pub fn reset(&mut self) {
self.bump = 0;
self.generation = self.generation.wrapping_add(1);
}
#[inline]
pub fn hit_ratio(&self) -> u8 {
let total = self.hits.saturating_add(self.misses);
if total == 0 {
0
} else {
((self.hits as u128 * 100) / total as u128) as u8
}
}
}
pub struct FastPathCacheManager {
pub caches: [SizeClassCache; NUM_SIZE_CLASSES],
pub config: FastPathCacheConfig,
pub fast_path_allocations: usize,
pub fast_path_deallocations: usize,
pub slow_path_allocations: usize,
}
impl FastPathCacheManager {
pub fn new() -> Self {
Self::with_config(FastPathCacheConfig::default())
}
pub fn with_config(config: FastPathCacheConfig) -> Self {
let mut caches: [SizeClassCache; NUM_SIZE_CLASSES] =
[SizeClassCache::new(0); NUM_SIZE_CLASSES];
if config.enabled {
for &size_class in config.cached_size_classes {
if size_class < NUM_SIZE_CLASSES {
caches[size_class] = SizeClassCache::new(config.max_blocks_per_class);
}
}
}
Self {
caches,
config,
fast_path_allocations: 0,
fast_path_deallocations: 0,
slow_path_allocations: 0,
}
}
#[inline]
pub fn try_allocate(&mut self, size_class: usize) -> Option<usize> {
if !self.config.enabled || size_class >= NUM_SIZE_CLASSES {
return None;
}
if self.caches[size_class].capacity == 0 {
return None;
}
match self.caches[size_class].allocate() {
Some(idx) => {
self.fast_path_allocations += 1;
Some(idx)
}
None => {
self.slow_path_allocations += 1;
None
}
}
}
#[inline]
pub fn record_deallocation(&mut self, size_class: usize) {
if size_class < NUM_SIZE_CLASSES {
self.fast_path_deallocations += 1;
}
}
#[inline]
pub fn reset_class_cache(&mut self, size_class: usize) {
if size_class < NUM_SIZE_CLASSES {
self.caches[size_class].reset();
}
}
pub fn reset_all(&mut self) {
for cache in &mut self.caches {
if cache.capacity > 0 {
cache.reset();
}
}
}
#[inline]
pub fn class_stats(&self, size_class: usize) -> Option<(usize, u8)> {
if size_class < NUM_SIZE_CLASSES {
let cache = self.caches[size_class];
Some((cache.hits.saturating_add(cache.misses), cache.hit_ratio()))
} else {
None
}
}
pub fn efficiency_metrics(&self) -> FastPathEfficiencyMetrics {
let total_requests = self
.fast_path_allocations
.saturating_add(self.slow_path_allocations);
let fast_path_ratio = if total_requests > 0 {
((self.fast_path_allocations as u128 * 100) / total_requests as u128) as u8
} else {
0
};
let mut avg_cache_hit_ratio = 0u32;
let mut active_classes = 0usize;
for cache in &self.caches {
if cache.capacity > 0 {
avg_cache_hit_ratio += cache.hit_ratio() as u32;
active_classes += 1;
}
}
let avg_hit_ratio = if active_classes > 0 {
(avg_cache_hit_ratio / active_classes as u32) as u8
} else {
0
};
FastPathEfficiencyMetrics {
fast_path_allocations: self.fast_path_allocations,
slow_path_allocations: self.slow_path_allocations,
fast_path_ratio,
avg_cache_hit_ratio: avg_hit_ratio,
total_cache_accesses: total_requests,
}
}
}
impl Default for FastPathCacheManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug)]
pub struct FastPathEfficiencyMetrics {
pub fast_path_allocations: usize,
pub slow_path_allocations: usize,
pub fast_path_ratio: u8,
pub avg_cache_hit_ratio: u8,
pub total_cache_accesses: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_block_validity() {
let ptr = 0x1000 as *mut u8;
let block = CacheBlock::new(ptr, 0);
assert!(block.is_valid());
let null_block = CacheBlock::new(core::ptr::null_mut(), 0);
assert!(!null_block.is_valid());
}
#[test]
fn test_size_class_cache_allocation() {
let mut cache = SizeClassCache::new(10);
assert!(cache.has_space());
assert_eq!(cache.allocate(), Some(0));
assert_eq!(cache.allocate(), Some(1));
assert_eq!(cache.hits, 2);
}
#[test]
fn test_size_class_cache_full() {
let mut cache = SizeClassCache::new(2);
assert_eq!(cache.allocate(), Some(0));
assert_eq!(cache.allocate(), Some(1));
assert_eq!(cache.allocate(), None);
assert_eq!(cache.misses, 1);
}
#[test]
fn test_size_class_cache_hit_ratio() {
let mut cache = SizeClassCache::new(100);
for _ in 0..80 {
let _ = cache.allocate();
}
cache.misses = 20;
assert_eq!(cache.hit_ratio(), 80);
}
#[test]
fn test_fast_path_cache_manager() {
let mut manager = FastPathCacheManager::new();
assert!(manager.try_allocate(0).is_some());
assert_eq!(manager.fast_path_allocations, 1);
assert!(manager.try_allocate(2).is_none());
}
#[test]
fn test_efficiency_metrics() {
let mut manager = FastPathCacheManager::new();
let _ = manager.try_allocate(0);
let _ = manager.try_allocate(0);
let metrics = manager.efficiency_metrics();
assert_eq!(metrics.fast_path_allocations, 2);
assert_eq!(metrics.total_cache_accesses, 2);
}
}