use memmap2::{Mmap, MmapOptions};
use std::collections::HashMap;
use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OptimizationStrategy {
MinimizeMemory,
MaximizePerformance,
Balanced,
Custom,
}
#[derive(Debug, Clone)]
pub struct LayoutConfig {
pub alignment: usize,
pub enable_packing: bool,
pub cache_line_size: usize,
pub numa_aware: bool,
}
impl Default for LayoutConfig {
fn default() -> Self {
Self {
alignment: 64, enable_packing: true,
cache_line_size: 64,
numa_aware: false,
}
}
}
pub struct MemoryLayout {
config: LayoutConfig,
optimization_stats: Arc<RwLock<OptimizationStats>>,
}
#[derive(Debug, Clone, Default)]
pub struct OptimizationStats {
pub optimizations_performed: u64,
pub memory_saved: u64,
pub performance_improvement: f64,
pub cache_hit_improvement: f64,
}
impl MemoryLayout {
pub fn new(config: LayoutConfig) -> Self {
Self {
config,
optimization_stats: Arc::new(RwLock::new(OptimizationStats::default())),
}
}
pub fn optimize_struct_layout<T>(
&self,
data: &mut [T],
access_pattern: AccessPattern,
) -> usize {
let _original_size = std::mem::size_of_val(data);
match access_pattern {
AccessPattern::Sequential => {
0
}
AccessPattern::Random => {
self.optimize_for_random_access(data)
}
AccessPattern::Strided => {
self.optimize_for_strided_access(data)
}
}
}
pub fn calculate_optimal_alignment(&self, size: usize) -> usize {
let mut alignment = 1;
while alignment <= self.config.cache_line_size
&& alignment <= size
&& size.is_multiple_of(alignment)
{
alignment *= 2;
}
alignment / 2
}
pub fn analyze_access_pattern(&self, accesses: &[MemoryAccess]) -> AccessAnalysis {
let mut sequential_count = 0;
let mut random_count = 0;
let mut stride_patterns = HashMap::new();
for window in accesses.windows(2) {
let addr_diff = window[1].address.saturating_sub(window[0].address);
if addr_diff <= 64 {
sequential_count += 1;
} else if addr_diff > 1024 {
random_count += 1;
} else {
*stride_patterns.entry(addr_diff).or_insert(0) += 1;
}
}
let total = accesses.len() - 1;
let sequential_ratio = if total > 0 {
sequential_count as f64 / total as f64
} else {
0.0
};
let random_ratio = if total > 0 {
random_count as f64 / total as f64
} else {
0.0
};
let dominant_pattern = if sequential_ratio > 0.7 {
AccessPattern::Sequential
} else if random_ratio > 0.7 {
AccessPattern::Random
} else {
AccessPattern::Strided
};
AccessAnalysis {
dominant_pattern,
sequential_ratio,
random_ratio,
stride_patterns,
cache_line_utilization: self.calculate_cache_utilization(accesses),
}
}
pub fn get_stats(&self) -> OptimizationStats {
self.optimization_stats
.read()
.map(|stats| stats.clone())
.unwrap_or_default()
}
fn optimize_for_random_access<T>(&self, _data: &mut [T]) -> usize {
0
}
fn optimize_for_strided_access<T>(&self, _data: &mut [T]) -> usize {
0
}
fn calculate_cache_utilization(&self, accesses: &[MemoryAccess]) -> f64 {
if accesses.is_empty() {
return 0.0;
}
let mut cache_lines_accessed = std::collections::HashSet::new();
for access in accesses {
let cache_line = access.address / self.config.cache_line_size;
cache_lines_accessed.insert(cache_line);
}
let total_bytes = accesses.iter().map(|a| a.size).sum::<usize>();
let cache_lines_needed = total_bytes.div_ceil(self.config.cache_line_size);
if cache_lines_needed > 0 {
cache_lines_accessed.len() as f64 / cache_lines_needed as f64
} else {
0.0
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccessPattern {
Sequential,
Random,
Strided,
}
#[derive(Debug, Clone)]
pub struct MemoryAccess {
pub address: usize,
pub size: usize,
pub timestamp: std::time::Instant,
pub access_type: AccessType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccessType {
Read,
Write,
ReadWrite,
}
#[derive(Debug, Clone)]
pub struct AccessAnalysis {
pub dominant_pattern: AccessPattern,
pub sequential_ratio: f64,
pub random_ratio: f64,
pub stride_patterns: HashMap<usize, usize>,
pub cache_line_utilization: f64,
}
pub struct MappedFile {
mmap: Mmap,
#[allow(dead_code)]
path: PathBuf,
size: usize,
}
impl MappedFile {
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
let file = File::open(&path)?;
let metadata = file.metadata()?;
let size = metadata.len() as usize;
let mmap = unsafe { MmapOptions::new().map(&file)? };
Ok(Self {
mmap,
path: path.as_ref().to_path_buf(),
size,
})
}
pub fn get_slice(&self, offset: usize, length: usize) -> Option<&[u8]> {
if offset + length <= self.size {
Some(&self.mmap[offset..offset + length])
} else {
None
}
}
pub fn as_slice(&self) -> &[u8] {
&self.mmap
}
pub fn len(&self) -> usize {
self.size
}
pub fn is_empty(&self) -> bool {
self.size == 0
}
pub fn prefetch(&self, offset: usize, length: usize) -> io::Result<()> {
if offset + length <= self.size {
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Range out of bounds",
))
}
}
}
pub struct LazyData<T> {
data: Arc<RwLock<Option<T>>>,
loader: Box<dyn Fn() -> Result<T, Box<dyn std::error::Error + Send + Sync>> + Send + Sync>,
load_count: Arc<std::sync::atomic::AtomicU64>,
}
impl<T> LazyData<T>
where
T: Clone + Send + Sync + 'static,
{
pub fn new<F>(loader: F) -> Self
where
F: Fn() -> Result<T, Box<dyn std::error::Error + Send + Sync>> + Send + Sync + 'static,
{
Self {
data: Arc::new(RwLock::new(None)),
loader: Box::new(loader),
load_count: Arc::new(std::sync::atomic::AtomicU64::new(0)),
}
}
pub fn get(&self) -> Result<T, Box<dyn std::error::Error + Send + Sync>> {
if let Ok(data_guard) = self.data.read() {
if let Some(ref data) = *data_guard {
return Ok(data.clone());
}
}
let loaded_data = (self.loader)()?;
self.load_count
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if let Ok(mut data_guard) = self.data.write() {
*data_guard = Some(loaded_data.clone());
}
Ok(loaded_data)
}
pub fn is_loaded(&self) -> bool {
if let Ok(data_guard) = self.data.read() {
data_guard.is_some()
} else {
false
}
}
pub fn unload(&self) {
if let Ok(mut data_guard) = self.data.write() {
*data_guard = None;
}
}
pub fn load_count(&self) -> u64 {
self.load_count.load(std::sync::atomic::Ordering::Relaxed)
}
}
pub struct MemoryOptimizer {
layout: MemoryLayout,
mapped_files: Arc<RwLock<HashMap<PathBuf, Arc<MappedFile>>>>,
strategy: OptimizationStrategy,
config: OptimizerConfig,
}
#[derive(Debug, Clone)]
pub struct OptimizerConfig {
pub aggressive_optimization: bool,
pub memory_pressure_threshold: f64,
pub enable_copy_elimination: bool,
pub max_mapped_cache_size: usize,
}
impl Default for OptimizerConfig {
fn default() -> Self {
Self {
aggressive_optimization: false,
memory_pressure_threshold: 0.8,
enable_copy_elimination: true,
max_mapped_cache_size: 1024 * 1024 * 1024, }
}
}
impl MemoryOptimizer {
pub fn new(strategy: OptimizationStrategy, config: OptimizerConfig) -> Self {
let layout_config = match strategy {
OptimizationStrategy::MinimizeMemory => LayoutConfig {
alignment: 8,
enable_packing: true,
cache_line_size: 64,
numa_aware: false,
},
OptimizationStrategy::MaximizePerformance => LayoutConfig {
alignment: 64,
enable_packing: false,
cache_line_size: 64,
numa_aware: true,
},
OptimizationStrategy::Balanced => LayoutConfig::default(),
OptimizationStrategy::Custom => LayoutConfig::default(),
};
Self {
layout: MemoryLayout::new(layout_config),
mapped_files: Arc::new(RwLock::new(HashMap::new())),
strategy,
config,
}
}
pub fn get_mapped_file<P: AsRef<Path>>(&self, path: P) -> io::Result<Arc<MappedFile>> {
let path_buf = path.as_ref().to_path_buf();
if let Ok(cache) = self.mapped_files.read() {
if let Some(mapped_file) = cache.get(&path_buf) {
return Ok(Arc::clone(mapped_file));
}
}
let mapped_file = Arc::new(MappedFile::open(&path_buf)?);
if let Ok(mut cache) = self.mapped_files.write() {
let current_size: usize = cache.values().map(|f| f.len()).sum();
if current_size + mapped_file.len() > self.config.max_mapped_cache_size {
let paths_to_remove: Vec<_> = cache.keys().cloned().collect();
for old_path in paths_to_remove {
cache.remove(&old_path);
let new_size: usize = cache.values().map(|f| f.len()).sum();
if new_size + mapped_file.len() <= self.config.max_mapped_cache_size {
break;
}
}
}
cache.insert(path_buf, Arc::clone(&mapped_file));
}
Ok(mapped_file)
}
pub fn optimize_layout<T>(
&self,
data: &mut [T],
accesses: &[MemoryAccess],
) -> OptimizationResult {
let analysis = self.layout.analyze_access_pattern(accesses);
let memory_saved = self
.layout
.optimize_struct_layout(data, analysis.dominant_pattern);
OptimizationResult {
memory_saved,
performance_improvement: self.estimate_performance_improvement(&analysis),
access_analysis: analysis,
}
}
pub fn create_zero_copy_view<'a, T>(&self, data: &'a [T]) -> ZeroCopyView<'a, T> {
ZeroCopyView::new(data)
}
pub fn get_optimization_report(&self) -> OptimizationReport {
let layout_stats = self.layout.get_stats();
let mapped_files_count = self
.mapped_files
.read()
.map(|files| files.len())
.unwrap_or(0);
OptimizationReport {
strategy: self.strategy,
layout_stats,
mapped_files_count,
total_mapped_size: self.get_total_mapped_size(),
}
}
fn estimate_performance_improvement(&self, analysis: &AccessAnalysis) -> f64 {
let baseline_performance = 1.0;
let cache_efficiency_bonus = analysis.cache_line_utilization * 0.3; baseline_performance + cache_efficiency_bonus
}
fn get_total_mapped_size(&self) -> usize {
if let Ok(cache) = self.mapped_files.read() {
cache.values().map(|f| f.len()).sum()
} else {
0
}
}
}
pub struct ZeroCopyView<'a, T> {
data: &'a [T],
}
impl<'a, T> ZeroCopyView<'a, T> {
fn new(data: &'a [T]) -> Self {
Self { data }
}
pub fn as_slice(&self) -> &[T] {
self.data
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct OptimizationResult {
pub memory_saved: usize,
pub performance_improvement: f64,
pub access_analysis: AccessAnalysis,
}
#[derive(Debug, Clone)]
pub struct OptimizationReport {
pub strategy: OptimizationStrategy,
pub layout_stats: OptimizationStats,
pub mapped_files_count: usize,
pub total_mapped_size: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Instant;
#[test]
fn test_memory_layout_optimization() {
let config = LayoutConfig::default();
let layout = MemoryLayout::new(config);
let mut data = vec![1u32, 2, 3, 4, 5];
let access_pattern = AccessPattern::Sequential;
let saved = layout.optimize_struct_layout(&mut data, access_pattern);
assert_eq!(saved, 0); }
#[test]
fn test_access_pattern_analysis() {
let layout = MemoryLayout::new(LayoutConfig::default());
let accesses = vec![
MemoryAccess {
address: 0x1000,
size: 4,
timestamp: Instant::now(),
access_type: AccessType::Read,
},
MemoryAccess {
address: 0x1004,
size: 4,
timestamp: Instant::now(),
access_type: AccessType::Read,
},
MemoryAccess {
address: 0x1008,
size: 4,
timestamp: Instant::now(),
access_type: AccessType::Read,
},
];
let analysis = layout.analyze_access_pattern(&accesses);
assert_eq!(analysis.dominant_pattern, AccessPattern::Sequential);
assert!(analysis.sequential_ratio > 0.5);
}
#[test]
fn test_lazy_data() {
let lazy = LazyData::new(|| Ok(String::from("Hello, World!")));
assert!(!lazy.is_loaded());
assert_eq!(lazy.load_count(), 0);
let data = lazy.get().unwrap();
assert_eq!(data, "Hello, World!");
assert!(lazy.is_loaded());
assert_eq!(lazy.load_count(), 1);
let data2 = lazy.get().unwrap();
assert_eq!(data2, "Hello, World!");
assert_eq!(lazy.load_count(), 1);
}
#[test]
fn test_zero_copy_view() {
let data = vec![1, 2, 3, 4, 5];
let optimizer =
MemoryOptimizer::new(OptimizationStrategy::Balanced, OptimizerConfig::default());
let view = optimizer.create_zero_copy_view(&data);
assert_eq!(view.len(), 5);
assert_eq!(view.as_slice(), &[1, 2, 3, 4, 5]);
}
#[test]
fn test_optimization_alignment() {
let layout = MemoryLayout::new(LayoutConfig::default());
assert_eq!(layout.calculate_optimal_alignment(64), 64);
assert_eq!(layout.calculate_optimal_alignment(32), 32);
assert_eq!(layout.calculate_optimal_alignment(17), 1);
assert_eq!(layout.calculate_optimal_alignment(128), 64); }
}