//! Criterion benchmarks: FastCDC vs SIMD Gear hash CDC. //! //! Run: //! cargo bench -p clawsync-core --features simd-cdc -- cdc_bench //! //! Groups: //! - `cdc/fastcdc/{data_type}/{size}` — baseline (FastCDC from `fastcdc` crate) //! - `cdc/simd/{data_type}/{size}` — SIMD Gear hash path //! - `cdc/scalar/{data_type}/{size}` — scalar Gear hash (same algorithm, no SIMD skip) use clawsync_core::cdc::{chunk_data, chunk_data_simd}; #[cfg(feature = "simd-cdc")] use clawsync_core::simd_cdc::chunk_scalar; use criterion::{BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main}; // ───────────────────────────────────────────────────────────────────────────── // Data generators // ───────────────────────────────────────────────────────────────────────────── fn random_data(n: usize) -> Vec { (0..n) .map(|i| { let v = (i as u64) .wrapping_mul(6364136223846793005) .wrapping_add(1442695040888963407); (v ^ (v >> 33)) as u8 }) .collect() } /// Simulates IEEE 754 float array bytes: exponent bytes cluster in 0x3F-0x45, /// mantissa bytes spread across 0x00-0xFF. fn float_data(n: usize) -> Vec { let mut v = Vec::with_capacity(n); let mut i = 0usize; while i + 4 <= n { // Synthetic f32: sign(0) | exp(127±small) | mantissa let exp_byte = 0x3Fu8.wrapping_add((i % 16) as u8); let mant0 = (i.wrapping_mul(2654435769) >> 24) as u8; let mant1 = (i.wrapping_mul(0x811c9dc5) >> 16) as u8; let mant2 = (i.wrapping_mul(0x01000193)) as u8; v.push(mant2); // byte 0: low mantissa (often < 64 → hot) v.push(mant1); v.push(mant0); v.push(exp_byte); // byte 3: exponent (often 0x3F-0x45 ≥ 64 → cold) i += 4; } v.truncate(n); v } /// All bytes are cold (≥ 64) — worst case for SIMD skip, exercises batch-shift path. fn cold_data(n: usize) -> Vec { (0..n).map(|i| 64u8 + ((i * 7) % 192) as u8).collect() } // ───────────────────────────────────────────────────────────────────────────── // Benchmark // ───────────────────────────────────────────────────────────────────────────── fn bench_cdc(c: &mut Criterion) { let datasets: &[(&str, fn(usize) -> Vec)] = &[ ("random", random_data), ("float", float_data), ("cold_only", cold_data), ]; let sizes = [1 << 20, 4 << 20, 16 << 20]; // 1 MB, 4 MB, 16 MB for &(label, make) in datasets { // ── FastCDC (baseline) ─────────────────────────────────────────────── let mut group = c.benchmark_group(format!("cdc/fastcdc/{label}")); for &size in &sizes { let data = make(size); group.throughput(Throughput::Bytes(size as u64)); group.bench_with_input( BenchmarkId::new("chunk_data", format!("{}MB", size >> 20)), &data, |b, d| b.iter(|| chunk_data(black_box(d))), ); } group.finish(); // ── SIMD Gear hash ─────────────────────────────────────────────────── let mut group = c.benchmark_group(format!("cdc/simd/{label}")); for &size in &sizes { let data = make(size); group.throughput(Throughput::Bytes(size as u64)); group.bench_with_input( BenchmarkId::new("chunk_data_simd", format!("{}MB", size >> 20)), &data, |b, d| b.iter(|| chunk_data_simd(black_box(d))), ); } group.finish(); // ── Scalar Gear hash (no SIMD skip) ────────────────────────────────── #[cfg(feature = "simd-cdc")] { let mut group = c.benchmark_group(format!("cdc/scalar/{label}")); for &size in &sizes { let data = make(size); group.throughput(Throughput::Bytes(size as u64)); group.bench_with_input( BenchmarkId::new("chunk_scalar", format!("{}MB", size >> 20)), &data, |b, d| b.iter(|| chunk_scalar(black_box(d), 8192, 65536, 262144)), ); } group.finish(); } } } criterion_group!(benches, bench_cdc); criterion_main!(benches);