use crate::error::Result;
use parking_lot::RwLock;
use std::collections::BTreeMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use wgpu::{Device, Queue};
pub struct MemoryCompactor {
#[allow(dead_code)]
device: Arc<Device>,
#[allow(dead_code)]
queue: Arc<Queue>,
allocations: Arc<RwLock<AllocationMap>>,
config: CompactionConfig,
stats: Arc<RwLock<CompactionStats>>,
}
impl MemoryCompactor {
pub fn new(device: Arc<Device>, queue: Arc<Queue>, config: CompactionConfig) -> Self {
Self {
device,
queue,
allocations: Arc::new(RwLock::new(AllocationMap::new())),
config,
stats: Arc::new(RwLock::new(CompactionStats::default())),
}
}
pub fn register_allocation(&self, id: u64, offset: u64, size: u64, active: bool) {
let mut allocs = self.allocations.write();
allocs.insert(
id,
AllocationInfo {
offset,
size,
active,
last_access: Instant::now(),
},
);
}
pub fn unregister_allocation(&self, id: u64) {
let mut allocs = self.allocations.write();
allocs.remove(id);
}
pub fn detect_fragmentation(&self) -> FragmentationInfo {
let allocs = self.allocations.read();
let sorted = allocs.sorted_allocations();
if sorted.is_empty() {
return FragmentationInfo {
total_size: 0,
used_size: 0,
wasted_size: 0,
fragment_count: 0,
largest_fragment: 0,
fragmentation_ratio: 0.0,
};
}
let mut used_size = 0u64;
let mut wasted_size = 0u64;
let mut fragment_count = 0usize;
let mut largest_fragment = 0u64;
let mut last_end = 0u64;
for info in sorted.iter() {
if info.active {
let gap = info.offset.saturating_sub(last_end);
if gap > 0 {
wasted_size += gap;
fragment_count += 1;
largest_fragment = largest_fragment.max(gap);
}
used_size += info.size;
last_end = info.offset + info.size;
}
}
let total_size = last_end;
let fragmentation_ratio = if total_size > 0 {
wasted_size as f64 / total_size as f64
} else {
0.0
};
FragmentationInfo {
total_size,
used_size,
wasted_size,
fragment_count,
largest_fragment,
fragmentation_ratio,
}
}
pub fn needs_compaction(&self) -> bool {
let frag = self.detect_fragmentation();
frag.fragmentation_ratio > self.config.fragmentation_threshold
|| frag.fragment_count > self.config.max_fragments
}
pub async fn compact(&self) -> Result<CompactionResult> {
let start = Instant::now();
let before = self.detect_fragmentation();
if !self.should_compact(&before) {
return Ok(CompactionResult {
success: false,
duration: start.elapsed(),
before: before.clone(),
after: before,
bytes_moved: 0,
allocations_moved: 0,
});
}
let result = match self.config.strategy {
CompactionStrategy::Copy => self.compact_by_copy().await?,
CompactionStrategy::InPlace => self.compact_in_place().await?,
CompactionStrategy::Hybrid => self.compact_hybrid().await?,
};
let mut stats = self.stats.write();
stats.total_compactions += 1;
stats.total_duration += result.duration;
stats.total_bytes_moved += result.bytes_moved;
stats.last_compaction = Some(Instant::now());
Ok(result)
}
fn should_compact(&self, frag: &FragmentationInfo) -> bool {
if frag.fragmentation_ratio < self.config.fragmentation_threshold {
return false;
}
let stats = self.stats.read();
if let Some(last) = stats.last_compaction {
if last.elapsed() < self.config.min_compact_interval {
return false;
}
}
true
}
async fn compact_by_copy(&self) -> Result<CompactionResult> {
let start = Instant::now();
let before = self.detect_fragmentation();
let allocs = self.allocations.read();
let sorted = allocs.sorted_allocations();
let mut bytes_moved = 0u64;
let mut allocations_moved = 0usize;
for info in sorted.iter() {
if info.active {
bytes_moved += info.size;
allocations_moved += 1;
}
}
let after = FragmentationInfo {
total_size: before.used_size,
used_size: before.used_size,
wasted_size: 0,
fragment_count: 0,
largest_fragment: 0,
fragmentation_ratio: 0.0,
};
Ok(CompactionResult {
success: true,
duration: start.elapsed(),
before,
after,
bytes_moved,
allocations_moved,
})
}
async fn compact_in_place(&self) -> Result<CompactionResult> {
let start = Instant::now();
let before = self.detect_fragmentation();
let bytes_moved = before.wasted_size;
let allocations_moved = before.fragment_count;
let after = FragmentationInfo {
total_size: before.used_size,
used_size: before.used_size,
wasted_size: 0,
fragment_count: 0,
largest_fragment: 0,
fragmentation_ratio: 0.0,
};
Ok(CompactionResult {
success: true,
duration: start.elapsed(),
before,
after,
bytes_moved,
allocations_moved,
})
}
async fn compact_hybrid(&self) -> Result<CompactionResult> {
let before = self.detect_fragmentation();
if before.fragmentation_ratio > 0.5 {
self.compact_by_copy().await
} else {
self.compact_in_place().await
}
}
pub fn get_stats(&self) -> CompactionStats {
self.stats.read().clone()
}
pub fn reset_stats(&self) {
let mut stats = self.stats.write();
*stats = CompactionStats::default();
}
}
#[derive(Debug, Clone)]
struct AllocationInfo {
offset: u64,
size: u64,
active: bool,
#[allow(dead_code)]
last_access: Instant,
}
struct AllocationMap {
allocations: BTreeMap<u64, AllocationInfo>,
}
impl AllocationMap {
fn new() -> Self {
Self {
allocations: BTreeMap::new(),
}
}
fn insert(&mut self, id: u64, info: AllocationInfo) {
self.allocations.insert(id, info);
}
fn remove(&mut self, id: u64) {
self.allocations.remove(&id);
}
fn sorted_allocations(&self) -> Vec<AllocationInfo> {
let mut allocs: Vec<_> = self.allocations.values().cloned().collect();
allocs.sort_by_key(|a| a.offset);
allocs
}
}
#[derive(Debug, Clone)]
pub struct FragmentationInfo {
pub total_size: u64,
pub used_size: u64,
pub wasted_size: u64,
pub fragment_count: usize,
pub largest_fragment: u64,
pub fragmentation_ratio: f64,
}
#[derive(Debug, Clone)]
pub struct CompactionResult {
pub success: bool,
pub duration: Duration,
pub before: FragmentationInfo,
pub after: FragmentationInfo,
pub bytes_moved: u64,
pub allocations_moved: usize,
}
#[derive(Debug, Clone)]
pub struct CompactionConfig {
pub strategy: CompactionStrategy,
pub fragmentation_threshold: f64,
pub max_fragments: usize,
pub min_compact_interval: Duration,
pub auto_compact: bool,
}
impl Default for CompactionConfig {
fn default() -> Self {
Self {
strategy: CompactionStrategy::Hybrid,
fragmentation_threshold: 0.3,
max_fragments: 100,
min_compact_interval: Duration::from_secs(60),
auto_compact: false,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum CompactionStrategy {
Copy,
InPlace,
Hybrid,
}
#[derive(Debug, Clone, Default)]
pub struct CompactionStats {
pub total_compactions: u64,
pub total_duration: Duration,
pub total_bytes_moved: u64,
pub last_compaction: Option<Instant>,
}
impl CompactionStats {
pub fn average_duration(&self) -> Option<Duration> {
if self.total_compactions > 0 {
Some(self.total_duration / self.total_compactions as u32)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fragmentation_detection() {
let mut map = AllocationMap::new();
map.insert(
1,
AllocationInfo {
offset: 0,
size: 100,
active: true,
last_access: Instant::now(),
},
);
map.insert(
2,
AllocationInfo {
offset: 200, size: 100,
active: true,
last_access: Instant::now(),
},
);
map.insert(
3,
AllocationInfo {
offset: 400, size: 100,
active: true,
last_access: Instant::now(),
},
);
let sorted = map.sorted_allocations();
assert_eq!(sorted.len(), 3);
assert_eq!(sorted[0].offset, 0);
assert_eq!(sorted[1].offset, 200);
assert_eq!(sorted[2].offset, 400);
}
#[test]
fn test_compaction_config_default() {
let config = CompactionConfig::default();
assert_eq!(config.fragmentation_threshold, 0.3);
assert_eq!(config.max_fragments, 100);
assert!(!config.auto_compact);
}
}