//! Criterion benchmarks for epoch-based (lazy) GC vs immediate GC. //! //! Measures: //! - `gc_keepn_immediate/N` — existing O(N·P) immediate compact path //! - `gc_epoch_flip/N` — new O(N) mark-only path //! - `flush_after_epoch_gc/N` — deferred compaction cost inside flush() //! //! Run: //! cargo bench -p clawhdf5-onion -- epoch_gc use clawhdf5_onion::gc::GcPolicy; use clawhdf5_onion::writer::OnionFile; use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; use tempfile::NamedTempFile; const PAGE_SIZE: u32 = 4096; // ───────────────────────────────────────────────────────────────────────────── // Helpers // ───────────────────────────────────────────────────────────────────────────── fn make_onion(n: usize) -> (NamedTempFile, std::path::PathBuf, OnionFile) { let tmp = NamedTempFile::new().unwrap(); let h5 = tmp.path().with_extension("h5"); std::fs::write(&h5, b"\x89HDF\r\n\x1a\n").unwrap(); let mut onion = OnionFile::create(&h5, PAGE_SIZE).unwrap(); for i in 0..n { let mut s = onion.begin_session(None).unwrap(); s.record_page(0, &vec![(i % 256) as u8; PAGE_SIZE as usize]); onion.commit_session(s, None).unwrap(); } (tmp, h5, onion) } // ───────────────────────────────────────────────────────────────────────────── // Benchmarks // ───────────────────────────────────────────────────────────────────────────── fn bench_epoch_gc(c: &mut Criterion) { let sizes = [100usize, 500, 1000]; // ── Immediate GC (baseline) ─────────────────────────────────────────────── let mut group = c.benchmark_group("epoch_gc/immediate"); for &n in &sizes { group.bench_with_input(BenchmarkId::new("keep_last_half", n), &n, |b, &n| { b.iter_with_setup( || make_onion(n), |(_tmp, _h5, mut onion)| { let _ = onion .gc(black_box(GcPolicy::KeepLastN((n / 2) as u64))) .unwrap(); }, ); }); } group.finish(); // ── Epoch flip (mark only, no compaction) ───────────────────────────────── let mut group = c.benchmark_group("epoch_gc/epoch_flip_mark"); for &n in &sizes { group.bench_with_input(BenchmarkId::new("keep_last_half", n), &n, |b, &n| { b.iter_with_setup( || make_onion(n), |(_tmp, _h5, mut onion)| { let _ = onion .gc(black_box(GcPolicy::EpochFlip(Box::new( GcPolicy::KeepLastN((n / 2) as u64), )))) .unwrap(); }, ); }); } group.finish(); // ── Flush after epoch flip (deferred compaction cost) ──────────────────── let mut group = c.benchmark_group("epoch_gc/flush_after_epoch_flip"); for &n in &sizes { group.bench_with_input(BenchmarkId::new("keep_last_half", n), &n, |b, &n| { b.iter_with_setup( || { let (tmp, h5, mut onion) = make_onion(n); onion .gc(GcPolicy::EpochFlip(Box::new(GcPolicy::KeepLastN( (n / 2) as u64, )))) .unwrap(); (tmp, h5, onion) }, |(_tmp, _h5, mut onion)| { onion.flush().unwrap(); }, ); }); } group.finish(); // ── Comparison: flush without any GC (baseline for flush cost) ─────────── let mut group = c.benchmark_group("epoch_gc/flush_no_gc"); for &n in &sizes { group.bench_with_input(BenchmarkId::new("n_revisions", n), &n, |b, &n| { b.iter_with_setup( || make_onion(n), |(_tmp, _h5, mut onion)| { onion.flush().unwrap(); }, ); }); } group.finish(); } criterion_group!(benches, bench_epoch_gc); criterion_main!(benches);