Runs cargo fmt --all; all 573 tests still passing, clippy still clean. No logic changes — formatting only. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
115 lines
4.8 KiB
Rust
115 lines
4.8 KiB
Rust
//! 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);
|