Gunnar

nordisk/znippy-zoomies

benches/gatling_roundtrip.rs

//! Tiny round-trip bench for the sync `gatling` engine (byte mode).
//!
//! Times an end-to-end `gatling::run`: reader → split → N workers (per-byte
//! transform) → ordered collector → sink, over a small in-memory corpus. The
//! point is to exercise the engine's thread fan-out / ordered-assembly path, not
//! to benchmark a heavy codec — the transform is deliberately trivial.
//!
//! Kept SMALL (256 KB, reduced sample size) — this runs on a laptop.

use criterion::{Criterion, Throughput, criterion_group, criterion_main};
use std::hint::black_box;

use anyhow::Result;
use znippy_zoomies::gatling::{self, Codec, Config, Sink, SlotFill, Split};

#[inline]
fn xform(b: u8) -> u8 {
    b.wrapping_add(0x5A) ^ 0x3C
}

struct ByteCodec;
impl Codec for ByteCodec {
    type Seg = (usize, usize);
    fn split(&self, data: &[u8], n_workers: usize, _is_last: bool) -> Option<Split<Self::Seg>> {
        if data.is_empty() {
            return None;
        }
        let n = n_workers.max(1).min(data.len());
        let base = data.len() / n;
        let mut segments = Vec::with_capacity(n);
        let mut start = 0;
        for i in 0..n {
            let end = if i == n - 1 { data.len() } else { start + base };
            segments.push((start, end));
            start = end;
        }
        Some(Split {
            segments,
            consumed: data.len(),
        })
    }
    fn decode(&self, data: &[u8], seg: &Self::Seg) -> Vec<u8> {
        data[seg.0..seg.1].iter().map(|&b| xform(b)).collect()
    }
}

struct CountSink {
    bytes: usize,
}
impl Sink for CountSink {
    fn safe_end(&self, assembled: &[u8], _is_last: bool) -> usize {
        assembled.len()
    }
    fn process(&mut self, bytes: &[u8]) -> Result<()> {
        self.bytes += bytes.len();
        Ok(())
    }
}

fn corpus(len: usize) -> Vec<u8> {
    let mut s = 0x9E3779B97F4A7C15u64;
    (0..len)
        .map(|_| {
            s ^= s >> 12;
            s ^= s << 25;
            s ^= s >> 27;
            (s >> 33) as u8
        })
        .collect()
}

fn bench_roundtrip(c: &mut Criterion) {
    let len = 256 * 1024;
    let data = corpus(len);

    let mut group = c.benchmark_group("gatling");
    group.sample_size(20);
    group.throughput(Throughput::Bytes(len as u64));
    group.bench_function("run_roundtrip_256k_4w", |b| {
        b.iter(|| {
            let mut sink = CountSink { bytes: 0 };
            let cfg = Config {
                chunk_size: 16 * 1024,
                carry_headroom: 0,
                ring_slots: 4,
                initial_carry: Vec::new(),
                slot_fill: SlotFill::Incremental,
            };
            gatling::run(black_box(&data[..]), ByteCodec, &mut sink, 4, cfg).expect("gatling::run");
            black_box(sink.bytes)
        })
    });
    group.finish();
}

criterion_group!(benches, bench_roundtrip);
criterion_main!(benches);