//! Criterion benchmarks for IBLT set reconciliation. //! //! Groups: //! - `iblt_manifest/encode/N` — build + serialise an IBLT sketch //! - `iblt_manifest/decode/D` — decode a difference sketch with D elements //! - `iblt_manifest/roundtrip/N/D` — full encode-send-decode cycle //! - `iblt_manifest/size/N` — report sketch bytes vs flat manifest bytes //! //! Run: //! cargo bench -p clawsync-onion -- iblt_bench use clawsync_onion::iblt::{DEFAULT_HASH_COUNT, IbltSketch, MIN_CELLS}; use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; // ───────────────────────────────────────────────────────────────────────────── const SEED: u64 = 0x0123_4567_89AB_CDEF; fn keys(n: usize) -> Vec { (0..n as u64).collect() } fn diff_sketches(n: usize, d: usize) -> (IbltSketch, Vec) { // A has keys 0..n, B has keys 0..(n-d) let a_keys = keys(n); let b_keys = keys(n.saturating_sub(d)); // Use generous m to ensure reliable decode let m = ((d * 4).max(MIN_CELLS)).max(IbltSketch::recommended_cells(n)); let mut a = IbltSketch::new(m, DEFAULT_HASH_COUNT, SEED); let mut b = IbltSketch::new(m, DEFAULT_HASH_COUNT, SEED); for &k in &a_keys { a.insert(k); } for &k in &b_keys { b.insert(k); } let b_bytes = b.to_bytes(); a.subtract(&b); (a, b_bytes) } // ───────────────────────────────────────────────────────────────────────────── fn bench_iblt(c: &mut Criterion) { let ns = [10usize, 100, 1_000, 10_000]; let ds = [0usize, 1, 10, 50]; // ── Encode (build + serialise) ──────────────────────────────────────────── let mut group = c.benchmark_group("iblt_manifest/encode"); for &n in &ns { let ks = keys(n); group.bench_with_input(BenchmarkId::from_parameter(n), &ks, |b, k| { b.iter(|| { let s = IbltSketch::from_keys(black_box(k), SEED); black_box(s.to_bytes()) }); }); } group.finish(); // ── Decode diff sketch ──────────────────────────────────────────────────── let mut group = c.benchmark_group("iblt_manifest/decode"); for &d in &ds { let n = 1_000usize; let (diff_sketch, _) = diff_sketches(n, d); group.bench_with_input(BenchmarkId::from_parameter(d), &diff_sketch, |b, s| { b.iter(|| black_box(s.decode())); }); } group.finish(); // ── Full roundtrip (encode A, encode B, subtract, decode) ───────────────── let mut group = c.benchmark_group("iblt_manifest/roundtrip"); for &n in &[100usize, 1_000, 10_000] { for &d in &[0usize, 10] { let a_keys = keys(n); let b_keys = keys(n.saturating_sub(d)); group.bench_with_input( BenchmarkId::new(format!("n{n}"), format!("d{d}")), &(a_keys, b_keys), |b, (ak, bk)| { b.iter(|| { let m = (d * 4).max(MIN_CELLS).max(IbltSketch::recommended_cells(n)); let mut a = IbltSketch::new(m, DEFAULT_HASH_COUNT, SEED); let mut b_sk = IbltSketch::new(m, DEFAULT_HASH_COUNT, SEED); for &k in black_box(ak) { a.insert(k); } for &k in black_box(bk) { b_sk.insert(k); } a.subtract(&b_sk); black_box(a.decode()) }); }, ); } } group.finish(); // ── Wire size table (not timed) ─────────────────────────────────────────── println!("\n=== IBLT sketch size vs flat manifest (N × 60 B) ==="); println!( "{:>8} {:>10} {:>12} {:>10}", "N", "IBLT (B)", "Flat (B)", "ratio" ); for &n in &[10usize, 100, 1_000, 10_000, 100_000] { let ks = keys(n); let sz = IbltSketch::from_keys(&ks, SEED).to_bytes().len(); let flat = n * 60; println!( "{:>8} {:>10} {:>12} {:>9.1}×", n, sz, flat, flat as f64 / sz as f64 ); } } criterion_group!(benches, bench_iblt); criterion_main!(benches);