Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/znippy-zoomies

ljar/src/bin/bench_large.rs

//! Benchmark ljar on a large ZIP file with wall-clock timing.
//!
//! Usage: cargo run --release --bin bench_large -- ~/work/large_test.zip

use std::time::Instant;

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let path = args.get(1).expect("usage: bench_large <file.zip>");

    let ncpus = std::thread::available_parallelism()
        .map(|n| n.get())
        .unwrap_or(1);
    eprintln!("CPUs available: {ncpus}");
    eprintln!(
        "LJAR_THREADS: {}",
        std::env::var("LJAR_THREADS").unwrap_or_else(|_| format!("{ncpus}"))
    );

    // Read entire file into memory (we're benchmarking decompression, not I/O)
    eprintln!("Loading {path} into memory...");
    let t0 = Instant::now();
    let data = std::fs::read(path).expect("read file");
    let load_time = t0.elapsed();
    eprintln!(
        "  Loaded {:.2} MB in {:.1}ms ({:.1} GB/s I/O)",
        data.len() as f64 / 1e6,
        load_time.as_secs_f64() * 1000.0,
        data.len() as f64 / load_time.as_secs_f64() / 1e9,
    );

    // Warm up (first call initializes thread pool + fixed tables)
    eprintln!("Warm-up run...");
    let t1 = Instant::now();
    let entries = ljar::decompress_jar(&data).expect("decompress");
    let warmup = t1.elapsed();
    let total_uncompressed: usize = entries.iter().map(|e| e.data.len()).sum();
    eprintln!(
        "  Warmup: {} entries, {:.2} GB decompressed in {:.1}ms",
        entries.len(),
        total_uncompressed as f64 / 1e9,
        warmup.as_secs_f64() * 1000.0,
    );
    drop(entries);

    // Timed runs
    eprintln!("\nBenchmark (5 runs):");
    let mut times = Vec::new();
    for i in 0..5 {
        let t = Instant::now();
        let entries = ljar::decompress_jar(&data).expect("decompress");
        let elapsed = t.elapsed();
        let n_entries = entries.len();
        let decomp_bytes: usize = entries.iter().map(|e| e.data.len()).sum();
        drop(entries);

        let secs = elapsed.as_secs_f64();
        let throughput_compressed = data.len() as f64 / secs / 1e9;
        let throughput_decompressed = decomp_bytes as f64 / secs / 1e9;
        eprintln!(
            "  Run {}: {:.1}ms | {n_entries} entries | {:.2} GB/s (compressed) | {:.2} GB/s (decompressed)",
            i + 1,
            secs * 1000.0,
            throughput_compressed,
            throughput_decompressed
        );
        times.push(secs);
    }

    let avg = times.iter().sum::<f64>() / times.len() as f64;
    let min = times.iter().cloned().fold(f64::INFINITY, f64::min);
    let max = times.iter().cloned().fold(0.0f64, f64::max);

    eprintln!("\n─── Summary ───────────────────────────────────────────");
    eprintln!("  File:         {path}");
    eprintln!("  Compressed:   {:.2} MB", data.len() as f64 / 1e6);
    eprintln!("  Decompressed: {:.2} GB", total_uncompressed as f64 / 1e9);
    eprintln!("  Entries:      {}", 163840); // known from gen
    eprintln!("  Threads:      {ncpus}");
    eprintln!("  Avg time:     {:.1} ms", avg * 1000.0);
    eprintln!("  Min time:     {:.1} ms", min * 1000.0);
    eprintln!("  Max time:     {:.1} ms", max * 1000.0);
    eprintln!(
        "  Throughput:   {:.2} GB/s (decompressed output)",
        total_uncompressed as f64 / avg / 1e9
    );
    eprintln!(
        "  Per-core:     {:.2} GB/s (÷ {ncpus})",
        total_uncompressed as f64 / avg / 1e9 / ncpus as f64
    );
    eprintln!("───────────────────────────────────────────────────────");
}