#![allow(missing_docs)]
pub mod compact;
pub use compact::{BitPackedGraph, CSRGraph, CompressedAdjacencyList, HybridGraph, MemmapGraph};
use crate::{DiGraph, Graph};
use std::collections::HashMap;
use std::mem;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use sysinfo::System;
#[derive(Debug, Clone)]
pub struct MemoryStats {
pub total_bytes: usize,
pub node_bytes: usize,
pub edge_bytes: usize,
pub adjacency_bytes: usize,
pub overhead_bytes: usize,
pub efficiency: f64,
}
pub struct MemoryProfiler;
impl MemoryProfiler {
pub fn profile_graph<N, E, Ix>(graph: &Graph<N, E, Ix>) -> MemoryStats
where
N: crate::base::Node + std::fmt::Debug,
E: crate::base::EdgeWeight,
Ix: petgraph::graph::IndexType,
{
let node_count = graph.node_count();
let edge_count = graph.edge_count();
let node_bytes = node_count
* (mem::size_of::<N>() + mem::size_of::<petgraph::graph::NodeIndex<Ix>>())
+ mem::size_of::<std::collections::HashMap<N, petgraph::graph::NodeIndex<Ix>>>();
let mut adjacency_bytes = 0;
for node in graph.nodes() {
if let Ok(neighbors) = graph.neighbors(node) {
let neighbor_count = neighbors.len();
adjacency_bytes += neighbor_count * mem::size_of::<E>() + mem::size_of::<Vec<E>>(); }
}
let edge_bytes = edge_count * (mem::size_of::<N>() * 2 + mem::size_of::<E>());
let allocation_count = node_count + 1; let overhead_bytes = allocation_count * 16;
let total_bytes = node_bytes + adjacency_bytes + edge_bytes + overhead_bytes;
let useful_bytes = node_bytes + adjacency_bytes;
let efficiency = if total_bytes > 0 {
useful_bytes as f64 / total_bytes as f64
} else {
1.0
};
MemoryStats {
total_bytes,
node_bytes,
edge_bytes,
adjacency_bytes,
overhead_bytes,
efficiency,
}
}
pub fn profile_digraph<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> MemoryStats
where
N: crate::base::Node + std::fmt::Debug,
E: crate::base::EdgeWeight,
Ix: petgraph::graph::IndexType,
{
let node_count = graph.node_count();
let edge_count = graph.edge_count();
let node_bytes = node_count
* (mem::size_of::<N>() + mem::size_of::<petgraph::graph::NodeIndex<Ix>>())
+ mem::size_of::<std::collections::HashMap<N, petgraph::graph::NodeIndex<Ix>>>();
let mut adjacency_bytes = 0;
for node in graph.nodes() {
if let Ok(successors) = graph.successors(node) {
adjacency_bytes +=
successors.len() * mem::size_of::<E>() + mem::size_of::<Vec<E>>();
}
if let Ok(predecessors) = graph.predecessors(node) {
adjacency_bytes +=
predecessors.len() * mem::size_of::<E>() + mem::size_of::<Vec<E>>();
}
}
let edge_bytes = edge_count * (mem::size_of::<N>() * 2 + mem::size_of::<E>());
let allocation_count = node_count * 2 + 1; let overhead_bytes = allocation_count * 16;
let total_bytes = node_bytes + adjacency_bytes + edge_bytes + overhead_bytes;
let useful_bytes = node_bytes + adjacency_bytes;
let efficiency = if total_bytes > 0 {
useful_bytes as f64 / total_bytes as f64
} else {
1.0
};
MemoryStats {
total_bytes,
node_bytes,
edge_bytes,
adjacency_bytes,
overhead_bytes,
efficiency,
}
}
pub fn estimate_memory(nodes: usize, edges: usize, directed: bool) -> usize {
let _avg_degree = if nodes > 0 {
edges as f64 / nodes as f64
} else {
0.0
};
let node_bytes = nodes * mem::size_of::<usize>();
let edge_entry_size = mem::size_of::<(usize, f64)>();
let adjacency_multiplier = if directed { 2.0 } else { 1.0 };
let adjacency_bytes =
(edges as f64 * adjacency_multiplier * edge_entry_size as f64) as usize;
let vec_overhead =
nodes * mem::size_of::<Vec<(usize, f64)>>() * if directed { 2 } else { 1 };
let overhead = (nodes + edges / 100) * 16;
node_bytes + adjacency_bytes + vec_overhead + overhead
}
pub fn analyze_fragmentation<N, E, Ix>(graph: &Graph<N, E, Ix>) -> FragmentationReport
where
N: crate::base::Node + std::fmt::Debug,
E: crate::base::EdgeWeight,
Ix: petgraph::graph::IndexType,
{
let mut degree_distribution = HashMap::new();
let mut total_capacity = 0;
let mut total_used = 0;
for node in graph.nodes() {
let degree = graph.degree(node);
*degree_distribution.entry(degree).or_insert(0) += 1;
let capacity = degree.next_power_of_two().max(4);
total_capacity += capacity;
total_used += degree;
}
let fragmentation = if total_capacity > 0 {
1.0 - (total_used as f64 / total_capacity as f64)
} else {
0.0
};
FragmentationReport {
degree_distribution,
total_capacity,
total_used,
fragmentation_ratio: fragmentation,
wasted_bytes: (total_capacity - total_used) * mem::size_of::<(N, E)>(),
}
}
}
#[derive(Debug)]
pub struct FragmentationReport {
pub degree_distribution: HashMap<usize, usize>,
pub total_capacity: usize,
pub total_used: usize,
pub fragmentation_ratio: f64,
pub wasted_bytes: usize,
}
pub struct OptimizedGraphBuilder {
nodes: Vec<usize>,
edges: Vec<(usize, usize, f64)>,
estimated_edges_per_node: Option<usize>,
}
impl Default for OptimizedGraphBuilder {
fn default() -> Self {
Self::new()
}
}
impl OptimizedGraphBuilder {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
edges: Vec::new(),
estimated_edges_per_node: None,
}
}
pub fn with_estimated_edges_per_node(mut self, edges_pernode: usize) -> Self {
self.estimated_edges_per_node = Some(edges_pernode);
self
}
pub fn reserve_nodes(mut self, capacity: usize) -> Self {
self.nodes.reserve(capacity);
self
}
pub fn reserve_edges(mut self, capacity: usize) -> Self {
self.edges.reserve(capacity);
self
}
pub fn add_node(&mut self, node: usize) {
self.nodes.push(node);
}
pub fn add_edge(&mut self, from: usize, to: usize, weight: f64) {
self.edges.push((from, to, weight));
}
pub fn build(self) -> Result<Graph<usize, f64>, String> {
let mut graph = Graph::new();
if let Some(_epn) = self.estimated_edges_per_node {
for &node in &self.nodes {
let _ = graph.add_node(node);
}
} else {
for &node in &self.nodes {
let _ = graph.add_node(node);
}
}
for (from, to, weight) in self.edges {
graph
.add_edge(from, to, weight)
.map_err(|e| format!("Failed to add edge: {e:?}"))?;
}
Ok(graph)
}
}
#[derive(Debug)]
pub struct OptimizationSuggestions {
pub suggestions: Vec<String>,
pub potential_savings: usize,
}
#[allow(dead_code)]
pub fn suggest_optimizations(
stats: &MemoryStats,
fragmentation: &FragmentationReport,
) -> OptimizationSuggestions {
let mut suggestions = Vec::new();
let mut potential_savings = 0;
if stats.efficiency < 0.7 {
suggestions.push(format!(
"Low memory efficiency ({:.1}%). Consider using a more compact representation.",
stats.efficiency * 100.0
));
}
if fragmentation.fragmentation_ratio > 0.3 {
suggestions.push(format!(
"High fragmentation ({:.1}%). Pre-allocate adjacency lists based on expected degree.",
fragmentation.fragmentation_ratio * 100.0
));
potential_savings += fragmentation.wasted_bytes;
}
let max_degree = fragmentation
.degree_distribution
.keys()
.max()
.copied()
.unwrap_or(0);
let avg_degree =
if fragmentation.total_used > 0 && !fragmentation.degree_distribution.is_empty() {
fragmentation.total_used as f64 / fragmentation.degree_distribution.len() as f64
} else {
0.0
};
if max_degree > avg_degree as usize * 10 {
suggestions.push(
"Highly skewed degree distribution. Consider using a hybrid representation \
with different storage for high-degree nodes."
.to_string(),
);
}
if avg_degree < 5.0 {
suggestions.push(
"Very sparse graph. Consider using a sparse matrix representation \
or compressed adjacency lists."
.to_string(),
);
}
OptimizationSuggestions {
suggestions,
potential_savings,
}
}
#[derive(Debug)]
pub struct RealTimeMemoryProfiler {
pid: u32,
system: Arc<Mutex<System>>,
is_monitoring: Arc<Mutex<bool>>,
samples: Arc<Mutex<Vec<MemorySample>>>,
monitor_thread: Option<thread::JoinHandle<()>>,
}
#[derive(Debug, Clone)]
pub struct MemorySample {
pub timestamp: Instant,
pub physical_memory: u64,
pub virtual_memory: u64,
pub growth_rate: i64,
}
#[derive(Debug, Clone)]
pub struct MemoryMetrics {
pub peak_memory: u64,
pub average_memory: u64,
pub growth_rate: f64,
pub duration: Duration,
pub sample_count: usize,
pub memory_variance: f64,
}
impl Default for RealTimeMemoryProfiler {
fn default() -> Self {
Self::new()
}
}
impl RealTimeMemoryProfiler {
pub fn new() -> Self {
let mut system = System::new_all();
system.refresh_all();
let pid = std::process::id();
Self {
pid,
system: Arc::new(Mutex::new(system)),
is_monitoring: Arc::new(Mutex::new(false)),
samples: Arc::new(Mutex::new(Vec::new())),
monitor_thread: None,
}
}
pub fn start_monitoring(&mut self, sampleinterval: Duration) {
let mut is_monitoring = self.is_monitoring.lock().expect("Operation failed");
if *is_monitoring {
return; }
*is_monitoring = true;
drop(is_monitoring);
self.samples.lock().expect("Operation failed").clear();
let pid = self.pid;
let system = Arc::clone(&self.system);
let is_monitoring = Arc::clone(&self.is_monitoring);
let samples = Arc::clone(&self.samples);
let handle = thread::spawn(move || {
let mut last_memory = 0u64;
let _start_time = Instant::now();
while *is_monitoring.lock().expect("Operation failed") {
{
let mut sys = system.lock().expect("Operation failed");
sys.refresh_processes(
sysinfo::ProcessesToUpdate::Some(&[(pid as usize).into()]),
false,
);
if let Some(process) = sys.process((pid as usize).into()) {
let physical_memory = process.memory() * 1024; let virtual_memory = process.virtual_memory() * 1024;
let growth_rate = physical_memory as i64 - last_memory as i64;
let sample = MemorySample {
timestamp: Instant::now(),
physical_memory,
virtual_memory,
growth_rate,
};
samples.lock().expect("Operation failed").push(sample);
last_memory = physical_memory;
}
}
thread::sleep(sampleinterval);
}
});
self.monitor_thread = Some(handle);
}
pub fn stop_monitoring(&mut self) -> MemoryMetrics {
{
let mut is_monitoring = self.is_monitoring.lock().expect("Operation failed");
*is_monitoring = false;
}
if let Some(handle) = self.monitor_thread.take() {
let _ = handle.join();
}
let samples = self.samples.lock().expect("Operation failed");
self.calculate_metrics(&samples)
}
pub fn get_current_metrics(&self) -> MemoryMetrics {
let samples = self.samples.lock().expect("Operation failed");
self.calculate_metrics(&samples)
}
fn calculate_metrics(&self, samples: &[MemorySample]) -> MemoryMetrics {
if samples.is_empty() {
return MemoryMetrics {
peak_memory: 0,
average_memory: 0,
growth_rate: 0.0,
duration: Duration::new(0, 0),
sample_count: 0,
memory_variance: 0.0,
};
}
let peak_memory = samples.iter().map(|s| s.physical_memory).max().unwrap_or(0);
let total_memory: u64 = samples.iter().map(|s| s.physical_memory).sum();
let average_memory = total_memory / samples.len() as u64;
let duration = if samples.len() > 1 {
samples
.last()
.expect("Operation failed")
.timestamp
.duration_since(samples[0].timestamp)
} else {
Duration::new(0, 0)
};
let growth_rate = if duration.as_secs_f64() > 0.0 && samples.len() > 1 {
let total_growth = samples.last().expect("Operation failed").physical_memory as i64
- samples[0].physical_memory as i64;
total_growth as f64 / duration.as_secs_f64()
} else {
0.0
};
let variance = if samples.len() > 1 {
let mean = average_memory as f64;
let sum_sq_diff: f64 = samples
.iter()
.map(|s| {
let diff = s.physical_memory as f64 - mean;
diff * diff
})
.sum();
sum_sq_diff / samples.len() as f64
} else {
0.0
};
MemoryMetrics {
peak_memory,
average_memory,
growth_rate,
duration,
sample_count: samples.len(),
memory_variance: variance,
}
}
pub fn detect_memory_leaks(&self, threshold_bytes_persec: f64) -> bool {
let metrics = self.get_current_metrics();
metrics.growth_rate > threshold_bytes_persec && metrics.sample_count > 10
}
pub fn generate_report(&self) -> String {
let metrics = self.get_current_metrics();
let _samples = self.samples.lock().expect("Operation failed");
let mut report = String::new();
report.push_str("=== Memory Usage Report ===\n");
report.push_str(&format!(
"Peak Memory: {:.2} MB\n",
metrics.peak_memory as f64 / 1_048_576.0
));
report.push_str(&format!(
"Average Memory: {:.2} MB\n",
metrics.average_memory as f64 / 1_048_576.0
));
report.push_str(&format!(
"Growth Rate: {:.2} KB/s\n",
metrics.growth_rate / 1024.0
));
report.push_str(&format!(
"Duration: {:.2} seconds\n",
metrics.duration.as_secs_f64()
));
report.push_str(&format!("Samples: {}\n", metrics.sample_count));
report.push_str(&format!(
"Memory Variance: {:.2} MB²\n",
metrics.memory_variance / 1_048_576.0_f64.powi(2)
));
if metrics.growth_rate > 1024.0 * 1024.0 {
report.push_str("\n⚠️ WARNING: High memory growth rate detected!\n");
}
if metrics.memory_variance > (100.0 * 1_048_576.0_f64).powi(2) {
report.push_str("⚠️ WARNING: High memory usage variance!\n");
}
report
}
}
pub struct AdvancedMemoryAnalyzer;
impl AdvancedMemoryAnalyzer {
pub fn analyze_operation_memory<F, R>(
operation_name: &str,
operation: F,
sample_interval: Duration,
) -> (R, MemoryMetrics)
where
F: FnOnce() -> R,
{
let mut profiler = RealTimeMemoryProfiler::new();
profiler.start_monitoring(sample_interval);
let result = operation();
let metrics = profiler.stop_monitoring();
println!(
"Memory analysis for '{}': Peak={:.2}MB, Growth={:.2}KB/s",
operation_name,
metrics.peak_memory as f64 / 1_048_576.0,
metrics.growth_rate / 1024.0
);
(result, metrics)
}
pub fn compare_implementations<F1, F2, R>(
impl1: F1,
impl2: F2,
impl1_name: &str,
impl2_name: &str,
) -> (MemoryMetrics, MemoryMetrics)
where
F1: FnOnce() -> R,
F2: FnOnce() -> R,
{
let sample_interval = Duration::from_millis(10);
let (_, metrics1) = Self::analyze_operation_memory(impl1_name, impl1, sample_interval);
let (_, metrics2) = Self::analyze_operation_memory(impl2_name, impl2, sample_interval);
println!("\n=== Implementation Comparison ===");
println!(
"{} - Peak: {:.2}MB, Growth: {:.2}KB/s",
impl1_name,
metrics1.peak_memory as f64 / 1_048_576.0,
metrics1.growth_rate / 1024.0
);
println!(
"{} - Peak: {:.2}MB, Growth: {:.2}KB/s",
impl2_name,
metrics2.peak_memory as f64 / 1_048_576.0,
metrics2.growth_rate / 1024.0
);
let memory_improvement = if metrics2.peak_memory > 0 {
((metrics1.peak_memory as f64 - metrics2.peak_memory as f64)
/ metrics2.peak_memory as f64)
* 100.0
} else {
0.0
};
println!("Memory improvement: {memory_improvement:.1}%");
(metrics1, metrics2)
}
pub fn scale_analysis<F>(
algorithm_factory: F,
scales: Vec<usize>,
algorithm_name: &str,
) -> Vec<(usize, MemoryMetrics)>
where
F: Fn(usize) -> Box<dyn FnOnce()>,
{
let mut results = Vec::new();
println!("\n=== Scaling Analysis for {algorithm_name} ===");
for scale in scales {
let operation = algorithm_factory(scale);
let (_, metrics) = Self::analyze_operation_memory(
&format!("{algorithm_name} (n={scale})"),
operation,
Duration::from_millis(5),
);
results.push((scale, metrics));
}
println!("\nScaling Summary:");
for (scale, metrics) in &results {
println!(
" n={}: {:.2}MB peak",
scale,
metrics.peak_memory as f64 / 1_048_576.0
);
}
results
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::generators;
#[test]
fn test_memory_profiling() {
let graph = generators::complete_graph(100).expect("Operation failed");
let stats = MemoryProfiler::profile_graph(&graph);
assert!(stats.total_bytes > 0);
assert!(stats.efficiency > 0.0 && stats.efficiency <= 1.0);
assert_eq!(graph.node_count(), 100);
assert_eq!(graph.edge_count(), 100 * 99 / 2); }
#[test]
fn test_memory_estimation() {
let estimated = MemoryProfiler::estimate_memory(1000, 5000, false);
assert!(estimated > 0);
let estimated_directed = MemoryProfiler::estimate_memory(1000, 5000, true);
assert!(estimated_directed > estimated); }
#[test]
fn test_fragmentation_analysis() {
let mut graph: Graph<i32, f64> = Graph::new();
for i in 0..100 {
graph.add_node(i);
}
for i in 0..10 {
for j in 10..20 {
if i != j {
graph.add_edge(i, j, 1.0).expect("Operation failed");
}
}
}
let report = MemoryProfiler::analyze_fragmentation(&graph);
assert!(report.fragmentation_ratio >= 0.0 && report.fragmentation_ratio <= 1.0);
}
#[test]
fn test_optimized_builder() {
let mut builder = OptimizedGraphBuilder::new()
.reserve_nodes(100)
.reserve_edges(200)
.with_estimated_edges_per_node(4);
for i in 0..100 {
builder.add_node(i);
}
for i in 0..99 {
builder.add_edge(i, i + 1, 1.0);
}
let graph = builder.build().expect("Operation failed");
assert_eq!(graph.node_count(), 100);
assert_eq!(graph.edge_count(), 99);
}
#[test]
fn test_real_time_memory_profiler() {
use std::time::Duration;
let mut profiler = RealTimeMemoryProfiler::new();
profiler.start_monitoring(Duration::from_millis(10));
std::thread::sleep(Duration::from_millis(50));
let metrics = profiler.stop_monitoring();
assert!(metrics.sample_count > 0);
assert!(metrics.peak_memory > 0);
}
#[test]
fn test_advanced_memory_analyzer() {
let (result, metrics) = AdvancedMemoryAnalyzer::analyze_operation_memory(
"test_operation",
|| {
let mut v = Vec::new();
for i in 0..100_000 {
v.push(i);
}
std::thread::sleep(Duration::from_millis(100));
v.len()
},
Duration::from_millis(10),
);
assert_eq!(result, 100_000);
assert!(metrics.peak_memory < u64::MAX);
}
#[test]
fn test_memory_leak_detection() {
let profiler = RealTimeMemoryProfiler::new();
let has_leak = profiler.detect_memory_leaks(1024.0 * 1024.0); assert!(!has_leak); }
#[test]
fn test_optimization_suggestions() {
let fragmentation = FragmentationReport {
degree_distribution: std::collections::HashMap::new(),
total_capacity: 1000,
total_used: 500,
fragmentation_ratio: 0.5, wasted_bytes: 500 * mem::size_of::<(usize, f64)>(),
};
let stats = MemoryStats {
total_bytes: 10000,
node_bytes: 2000,
edge_bytes: 3000,
adjacency_bytes: 4000,
overhead_bytes: 1000,
efficiency: 0.5, };
let suggestions = suggest_optimizations(&stats, &fragmentation);
assert!(!suggestions.suggestions.is_empty());
assert!(suggestions.potential_savings > 0);
}
}