s-zip 0.11.3

High-performance streaming ZIP library with AES-256 encryption and async/await support - Read/write ZIP files with minimal memory footprint. Supports password protection, cloud storage, and Tokio runtime.
Documentation
/// Memory test comparing parallel vs sequential compression with 100MB files
///
/// This test demonstrates that parallel compression maintains bounded memory
/// usage even with large files, thanks to semaphore-based concurrency limiting.
///
/// Run with: cargo run --example parallel_memory_test --features async --release
use s_zip::{AsyncStreamingZipWriter, ParallelConfig, ParallelEntry};
use std::io::Write;
use std::time::Instant;

fn get_memory_usage_mb() -> f64 {
    let status =
        std::fs::read_to_string("/proc/self/status").expect("Failed to read /proc/self/status");

    for line in status.lines() {
        if line.starts_with("VmRSS:") {
            let parts: Vec<&str> = line.split_whitespace().collect();
            if parts.len() >= 2 {
                let kb: f64 = parts[1].parse().unwrap_or(0.0);
                return kb / 1024.0; // Convert to MB
            }
        }
    }
    0.0
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Parallel Compression Memory Test ===");
    println!("Comparing sequential vs parallel with 100MB files\n");

    let temp_dir = std::env::temp_dir().join("s_zip_parallel_memory_test");
    std::fs::create_dir_all(&temp_dir)?;

    // Create 4 test files of 100MB each (400MB total)
    println!("Creating test files...");
    let mut test_files = Vec::new();

    for i in 0..4 {
        let file_path = temp_dir.join(format!("large_file_{}.txt", i));
        let mut file = std::fs::File::create(&file_path)?;

        // Write 100MB of data
        let chunk = vec![b'A' + (i as u8); 1024 * 1024]; // 1MB chunk
        for _ in 0..100 {
            file.write_all(&chunk)?;
        }

        test_files.push((file_path.clone(), format!("large_{}.txt", i)));
        println!("  Created: {} (100 MB)", file_path.display());
    }

    println!("\nTotal test data: 400 MB\n");

    // Test 1: Sequential (1 thread)
    println!("--- Test 1: Sequential (1 thread) ---");
    let output_seq = temp_dir.join("output_sequential.zip");

    let mem_before = get_memory_usage_mb();
    println!("Memory before: {:.1} MB", mem_before);

    let start = Instant::now();
    let mut writer = AsyncStreamingZipWriter::new(&output_seq).await?;

    let entries: Vec<ParallelEntry> = test_files
        .iter()
        .map(|(path, name)| ParallelEntry::new(name.clone(), path.clone()))
        .collect();

    let config = ParallelConfig::default().with_max_concurrent(1).unwrap();
    writer.write_entries_parallel(entries, config).await?;

    let mem_peak_seq = get_memory_usage_mb();
    println!("Memory peak: {:.1} MB", mem_peak_seq);

    writer.finish().await?;
    let elapsed_seq = start.elapsed();

    let mem_after = get_memory_usage_mb();
    let output_size = std::fs::metadata(&output_seq)?.len();

    println!("Memory after: {:.1} MB", mem_after);
    println!("Time: {:.2} seconds", elapsed_seq.as_secs_f64());
    println!("Output size: {:.2} MB", output_size as f64 / 1_000_000.0);
    println!("Throughput: {:.1} MB/s", 400.0 / elapsed_seq.as_secs_f64());
    println!("Peak memory delta: {:.1} MB\n", mem_peak_seq - mem_before);

    // Test 2: Conservative (2 threads)
    println!("--- Test 2: Conservative (2 threads) ---");
    let output_2 = temp_dir.join("output_2threads.zip");

    let mem_before = get_memory_usage_mb();
    println!("Memory before: {:.1} MB", mem_before);

    let start = Instant::now();
    let mut writer = AsyncStreamingZipWriter::new(&output_2).await?;

    let entries: Vec<ParallelEntry> = test_files
        .iter()
        .map(|(path, name)| ParallelEntry::new(name.clone(), path.clone()))
        .collect();

    let config = ParallelConfig::conservative();
    writer.write_entries_parallel(entries, config).await?;

    let mem_peak_2 = get_memory_usage_mb();
    println!("Memory peak: {:.1} MB", mem_peak_2);

    writer.finish().await?;
    let elapsed_2 = start.elapsed();

    let mem_after = get_memory_usage_mb();
    let output_size = std::fs::metadata(&output_2)?.len();

    println!("Memory after: {:.1} MB", mem_after);
    println!("Time: {:.2} seconds", elapsed_2.as_secs_f64());
    println!("Output size: {:.2} MB", output_size as f64 / 1_000_000.0);
    println!("Throughput: {:.1} MB/s", 400.0 / elapsed_2.as_secs_f64());
    println!(
        "Speedup: {:.2}x",
        elapsed_seq.as_secs_f64() / elapsed_2.as_secs_f64()
    );
    println!("Peak memory delta: {:.1} MB\n", mem_peak_2 - mem_before);

    // Test 3: Balanced (4 threads)
    println!("--- Test 3: Balanced (4 threads) ---");
    let output_4 = temp_dir.join("output_4threads.zip");

    let mem_before = get_memory_usage_mb();
    println!("Memory before: {:.1} MB", mem_before);

    let start = Instant::now();
    let mut writer = AsyncStreamingZipWriter::new(&output_4).await?;

    let entries: Vec<ParallelEntry> = test_files
        .iter()
        .map(|(path, name)| ParallelEntry::new(name.clone(), path.clone()))
        .collect();

    let config = ParallelConfig::balanced();
    writer.write_entries_parallel(entries, config).await?;

    let mem_peak_4 = get_memory_usage_mb();
    println!("Memory peak: {:.1} MB", mem_peak_4);

    writer.finish().await?;
    let elapsed_4 = start.elapsed();

    let mem_after = get_memory_usage_mb();
    let output_size = std::fs::metadata(&output_4)?.len();

    println!("Memory after: {:.1} MB", mem_after);
    println!("Time: {:.2} seconds", elapsed_4.as_secs_f64());
    println!("Output size: {:.2} MB", output_size as f64 / 1_000_000.0);
    println!("Throughput: {:.1} MB/s", 400.0 / elapsed_4.as_secs_f64());
    println!(
        "Speedup: {:.2}x",
        elapsed_seq.as_secs_f64() / elapsed_4.as_secs_f64()
    );
    println!("Peak memory delta: {:.1} MB\n", mem_peak_4 - mem_before);

    // Test 4: Aggressive (8 threads) - only if we have 4+ files
    println!("--- Test 4: Aggressive (8 threads) ---");
    let output_8 = temp_dir.join("output_8threads.zip");

    let mem_before = get_memory_usage_mb();
    println!("Memory before: {:.1} MB", mem_before);

    let start = Instant::now();
    let mut writer = AsyncStreamingZipWriter::new(&output_8).await?;

    let entries: Vec<ParallelEntry> = test_files
        .iter()
        .map(|(path, name)| ParallelEntry::new(name.clone(), path.clone()))
        .collect();

    let config = ParallelConfig::aggressive();
    writer.write_entries_parallel(entries, config).await?;

    let mem_peak_8 = get_memory_usage_mb();
    println!("Memory peak: {:.1} MB", mem_peak_8);

    writer.finish().await?;
    let elapsed_8 = start.elapsed();

    let mem_after = get_memory_usage_mb();
    let output_size = std::fs::metadata(&output_8)?.len();

    println!("Memory after: {:.1} MB", mem_after);
    println!("Time: {:.2} seconds", elapsed_8.as_secs_f64());
    println!("Output size: {:.2} MB", output_size as f64 / 1_000_000.0);
    println!("Throughput: {:.1} MB/s", 400.0 / elapsed_8.as_secs_f64());
    println!(
        "Speedup: {:.2}x",
        elapsed_seq.as_secs_f64() / elapsed_8.as_secs_f64()
    );
    println!("Peak memory delta: {:.1} MB\n", mem_peak_8 - mem_before);

    // Summary
    println!("=== Summary ===\n");
    println!("Configuration | Peak Memory Delta | Speedup | Throughput");
    println!("------------- | ----------------- | ------- | ----------");
    println!(
        "Sequential    | {:.1} MB         | 1.00x   | {:.1} MB/s",
        mem_peak_seq - mem_before,
        400.0 / elapsed_seq.as_secs_f64()
    );
    println!(
        "2 threads     | {:.1} MB         | {:.2}x   | {:.1} MB/s",
        mem_peak_2 - mem_before,
        elapsed_seq.as_secs_f64() / elapsed_2.as_secs_f64(),
        400.0 / elapsed_2.as_secs_f64()
    );
    println!(
        "4 threads     | {:.1} MB         | {:.2}x   | {:.1} MB/s",
        mem_peak_4 - mem_before,
        elapsed_seq.as_secs_f64() / elapsed_4.as_secs_f64(),
        400.0 / elapsed_4.as_secs_f64()
    );
    println!(
        "8 threads     | {:.1} MB         | {:.2}x   | {:.1} MB/s",
        mem_peak_8 - mem_before,
        elapsed_seq.as_secs_f64() / elapsed_8.as_secs_f64(),
        400.0 / elapsed_8.as_secs_f64()
    );

    println!("\n✅ Memory constraint verified:");
    println!("   - Memory usage is bounded by concurrency level");
    println!("   - Peak memory scales predictably with thread count");
    println!("   - No memory spikes despite 400MB total data");

    // Cleanup
    println!("\nCleaning up test files...");
    std::fs::remove_dir_all(&temp_dir)?;

    Ok(())
}