scirs2-graph 0.4.2

Graph processing module for SciRS2 (scirs2-graph)
Documentation
//! Memory profiling validation example
//!
//! This example demonstrates the memory profiling and optimization capabilities
//! of scirs2-graph by creating graphs of different sizes and analyzing their
//! memory usage patterns.

use scirs2_graph::{
    generators,
    memory::{
        suggest_optimizations, BitPackedGraph, CSRGraph, CompressedAdjacencyList, HybridGraph,
        MemoryProfiler, OptimizedGraphBuilder, RealTimeMemoryProfiler,
    },
    Graph,
};
use std::collections::HashMap;
use std::thread;
use std::time::Duration;

#[allow(dead_code)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("🔍 SciRS2-Graph Memory Profiling Validation");
    println!("=".repeat(50));

    // Test 1: Basic Memory Profiling
    println!("\n📊 Test 1: Basic Memory Profiling");
    basic_memory_profiling()?;

    // Test 2: Representation Comparison
    println!("\n📈 Test 2: Memory Representation Comparison");
    representation_comparison()?;

    // Test 3: Fragmentation Analysis
    println!("\n🗂️  Test 3: Memory Fragmentation Analysis");
    fragmentation_analysis()?;

    // Test 4: Optimization Suggestions
    println!("\n💡 Test 4: Memory Optimization Suggestions");
    optimization_suggestions()?;

    // Test 5: Real-time Monitoring
    println!("\n⏱️  Test 5: Real-time Memory Monitoring");
    real_time_monitoring()?;

    // Test 6: Optimized Graph Builder
    println!("\n🏗️  Test 6: Optimized Graph Builder");
    optimized_builder_test()?;

    println!("\n✅ All memory profiling tests completed successfully!");
    Ok(())
}

#[allow(dead_code)]
fn basic_memory_profiling() -> Result<(), Box<dyn std::error::Error>> {
    let sizes = vec![1_000, 5_000, 10_000];
    let edge_probability = 0.01;

    println!("Graph Size | Total Memory | Node Memory | Edge Memory | Efficiency");
    println!("-".repeat(70));

    for size in sizes {
        let graph = generators::erdos_renyi_graph(size, edge_probability, None)?;
        let stats = MemoryProfiler::profile_graph(&graph);

        println!(
            "{:10} | {:11} | {:11} | {:11} | {:9.1}%",
            format!("{}", size),
            format_bytes(stats.total_bytes),
            format_bytes(stats.node_bytes),
            format_bytes(stats.edge_bytes),
            stats.efficiency * 100.0
        );
    }

    Ok(())
}

#[allow(dead_code)]
fn representation_comparison() -> Result<(), Box<dyn std::error::Error>> {
    let n = 5_000;
    let edge_probability = 0.005; // Sparse graph

    // Create standard graph
    let graph = generators::erdos_renyi_graph(n, edge_probability, None)?;
    let standard_stats = MemoryProfiler::profile_graph(&graph);

    // Create CSR representation
    let edges: Vec<_> = (0..graph.node_count())
        .flat_map(|u| graph.neighbors(u).map(move |v| (u, v, 1.0)))
        .collect();
    let csr = CSRGraph::from_edges(n, edges.clone())?;

    // Create bit-packed representation (unweighted)
    let mut bitpacked = BitPackedGraph::new(n, false);
    for (u, v, _) in &edges {
        if u <= v {
            bitpacked.add_edge(*u, *v)?;
        }
    }

    // Create compressed adjacency list
    let adj_lists: Vec<Vec<_>> = (0..n).map(|u| graph.neighbors(u).collect()).collect();
    let compressed = CompressedAdjacencyList::from_adjacency(adj_lists);

    println!("Representation      | Memory Usage | vs Standard");
    println!("-".repeat(45));
    println!(
        "{:18} | {:11} | {:8}",
        "Standard Graph",
        format_bytes(standard_stats.total_bytes),
        "100.0%"
    );
    println!(
        "{:18} | {:11} | {:7.1}%",
        "CSR Graph",
        format_bytes(csr.memory_usage()),
        (csr.memory_usage() as f64 / standard_stats.total_bytes as f64) * 100.0
    );
    println!(
        "{:18} | {:11} | {:7.1}%",
        "Bit-packed Graph",
        format_bytes(bitpacked.memory_usage()),
        (bitpacked.memory_usage() as f64 / standard_stats.total_bytes as f64) * 100.0
    );
    println!(
        "{:18} | {:11} | {:7.1}%",
        "Compressed Adj.",
        format_bytes(compressed.memory_usage()),
        (compressed.memory_usage() as f64 / standard_stats.total_bytes as f64) * 100.0
    );

    Ok(())
}

#[allow(dead_code)]
fn fragmentation_analysis() -> Result<(), Box<dyn std::error::Error>> {
    // Create graphs with different degree distributions
    let graphs = vec![
        (
            "Erdős-Rényi (uniform)",
            generators::erdos_renyi_graph(2000, 0.01, None)?,
        ),
        (
            "Barabási-Albert (scale-free)",
            generators::barabasi_albert_graph(2000, 5, None)?,
        ),
        ("Complete", generators::complete_graph(100)?),
    ];

    println!("Graph Type           | Fragmentation | Wasted Memory");
    println!("-".repeat(55));

    for (name, graph) in graphs {
        let fragmentation = MemoryProfiler::analyze_fragmentation(&graph);
        println!(
            "{:19} | {:12.1}% | {:12}",
            name,
            fragmentation.fragmentation_ratio * 100.0,
            format_bytes(fragmentation.wasted_bytes)
        );
    }

    Ok(())
}

#[allow(dead_code)]
fn optimization_suggestions() -> Result<(), Box<dyn std::error::Error>> {
    // Create a graph with poor memory characteristics
    let mut graph = Graph::new();

    // Add nodes with uneven degree distribution
    for i in 0..1000 {
        graph.add_node(i)?;
    }

    // Add many edges to first few nodes (high degree)
    for i in 0..10 {
        for j in 10..1000 {
            graph.add_edge(i, j, 1.0)?;
        }
    }

    let stats = MemoryProfiler::profile_graph(&graph);
    let fragmentation = MemoryProfiler::analyze_fragmentation(&graph);
    let suggestions = suggest_optimizations(&stats, &fragmentation);

    println!(
        "Current memory efficiency: {:.1}%",
        stats.efficiency * 100.0
    );
    println!(
        "Fragmentation ratio: {:.1}%",
        fragmentation.fragmentation_ratio * 100.0
    );
    println!("\nOptimization suggestions:");
    for suggestion in &suggestions.suggestions {
        println!("  • {}", suggestion);
    }
    println!(
        "Potential savings: {}",
        format_bytes(suggestions.potential_savings)
    );

    Ok(())
}

#[allow(dead_code)]
fn real_time_monitoring() -> Result<(), Box<dyn std::error::Error>> {
    println!("Starting real-time memory monitoring...");

    let mut profiler = RealTimeMemoryProfiler::new(Duration::from_millis(100))?;
    profiler.start_monitoring()?;

    // Simulate memory-intensive operations
    for i in 0..5 {
        let size = 1000 * (i + 1);
        println!("  Creating graph with {} nodes...", size);
        let _graph = generators::erdos_renyi_graph(size, 0.01, None)?;
        thread::sleep(Duration::from_millis(200));
    }

    thread::sleep(Duration::from_millis(500));

    let metrics = profiler.get_current_metrics()?;
    profiler.stop_monitoring()?;

    println!("Monitoring results:");
    println!("  Peak memory: {}", format_bytes(metrics.peak_memory));
    println!("  Average memory: {}", format_bytes(metrics.average_memory));
    println!("  Growth rate: {:.2} MB/s", metrics.growth_rate_mb_per_sec);
    println!("  Sample count: {}", metrics.sample_count);

    Ok(())
}

#[allow(dead_code)]
fn optimized_builder_test() -> Result<(), Box<dyn std::error::Error>> {
    let n = 10_000;
    let expected_edges = 50_000;

    println!("Building graph with {} nodes, {} edges", n, expected_edges);

    // Time standard construction
    let start = std::time::Instant::now();
    let mut standard_graph = Graph::new();
    for i in 0..n {
        standard_graph.add_node(i)?;
    }
    for i in 0..expected_edges {
        let u = i % n;
        let v = (i + 1) % n;
        standard_graph.add_edge(u, v, 1.0)?;
    }
    let standard_time = start.elapsed();
    let standard_stats = MemoryProfiler::profile_graph(&standard_graph);

    // Time optimized construction
    let start = std::time::Instant::now();
    let mut builder = OptimizedGraphBuilder::new()
        .reserve_nodes(n)
        .reserve_edges(expected_edges)
        .with_estimated_edges_per_node(expected_edges / n);

    for i in 0..n {
        builder.add_node(i);
    }
    for i in 0..expected_edges {
        let u = i % n;
        let v = (i + 1) % n;
        builder.add_edge(u, v, 1.0);
    }
    let optimized_graph = builder.build()?;
    let optimized_time = start.elapsed();
    let optimized_stats = MemoryProfiler::profile_graph(&optimized_graph);

    println!("Construction Method | Time (ms) | Memory | Efficiency");
    println!("-".repeat(55));
    println!(
        "{:18} | {:8.1} | {:6} | {:9.1}%",
        "Standard",
        standard_time.as_millis() as f64,
        format_bytes(standard_stats.total_bytes),
        standard_stats.efficiency * 100.0
    );
    println!(
        "{:18} | {:8.1} | {:6} | {:9.1}%",
        "Optimized Builder",
        optimized_time.as_millis() as f64,
        format_bytes(optimized_stats.total_bytes),
        optimized_stats.efficiency * 100.0
    );

    let speedup = standard_time.as_nanos() as f64 / optimized_time.as_nanos() as f64;
    println!("Speedup: {:.2}x", speedup);

    Ok(())
}

#[allow(dead_code)]
fn format_bytes(bytes: usize) -> String {
    if bytes < 1024 {
        format!("{} B", bytes)
    } else if bytes < 1024 * 1024 {
        format!("{:.1} KB", bytes as f64 / 1024.0)
    } else if bytes < 1024 * 1024 * 1024 {
        format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
    } else {
        format!("{:.1} GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
    }
}