Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/admit/benches/admit.rs

//! **plan + admit + record, 1 → N cores.**
//!
//! One op = what a product does once per transfer: `usage(who)` (the table's
//! 30 loads), `plan` (estimate + the sentence), `admit` (a class slot's
//! `fetch_add`), `record` (one CAS on the day bucket, the striped EWMA, and
//! the slot's `fetch_sub`). Workers are fanned out with the house engine
//! (`gatling_run`, one unit per worker so the dispatcher's counter is touched
//! once per worker, not per op).
//!
//! Three shapes:
//! * **spread** — 4 096 namespaces, each worker walking its own stride of
//!   them: the multi-tenant case;
//! * **hot** — every worker on ONE namespace: the worst case for the one
//!   shared cache line (that namespace's day bucket and the class counter);
//! * **core** — spread, but the plan (and its sentence) built once per worker
//!   and re-used: usage + admit + record only, i.e. the shared data
//!   structures without the per-transfer string formatting.
//!
//! Prints `threads  ns/op (per thread)  Mops/s (total)  scaling`, where
//! scaling is total throughput over the 1-thread run.
//!
//! `ADMIT_BENCH_OPS` overrides the ops per worker (default 200 000).

use std::sync::Arc;
use std::time::Instant;

use edda_admit::{Admit, Consumer, Cost, Settings, Slots, Work, WorkKind};

fn ops_per_worker() -> usize {
    std::env::var("ADMIT_BENCH_OPS").ok().and_then(|v| v.parse().ok()).unwrap_or(200_000)
}

#[derive(Clone, Copy, PartialEq)]
enum Shape {
    Spread,
    Hot,
    Core,
}

fn run(share: &Arc<dyn Admit>, who: &[Consumer], threads: usize, shape: Shape) -> f64 {
    let ops = ops_per_worker();
    let work = Work::new(WorkKind::Clone, Some(1_000_000));
    let cost = Cost { net_out: 1_200_000, cpu_ms: 3, ..Cost::default() };
    let start = Instant::now();
    gatling::gatling_forkjoin::gatling_run(threads, threads, |w| {
        if shape == Shape::Core {
            let plans: Vec<_> = (0..64)
                .map(|i| {
                    let c = &who[(w * 7919 + i) % who.len()];
                    share.plan(&work, c, &share.usage(c))
                })
                .collect();
            for i in 0..ops {
                let plan = &plans[i % plans.len()];
                std::hint::black_box(share.usage(&plan.who));
                if let Ok(g) = share.admit(plan) {
                    share.record(g, &cost);
                }
            }
            return;
        }
        for i in 0..ops {
            let c = if shape == Shape::Hot { &who[0] } else { &who[(w * 7919 + i) % who.len()] };
            let usage = share.usage(c);
            let plan = share.plan(&work, c, &usage);
            if let Ok(g) = share.admit(&plan) {
                share.record(g, &cost);
            }
        }
    });
    start.elapsed().as_nanos() as f64 / ops as f64 // ns per op per thread
}

fn main() {
    let cores = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1);
    let mut counts = vec![1usize];
    while *counts.last().unwrap() * 2 <= cores {
        counts.push(counts.last().unwrap() * 2);
    }
    if *counts.last().unwrap() != cores {
        counts.push(cores);
    }
    let who: Vec<Consumer> = (0..4096).map(|i| Consumer::Namespace(format!("ns-{i}"))).collect();
    for shape in [Shape::Spread, Shape::Hot, Shape::Core] {
        let share: Arc<dyn Admit> = Settings {
            limit: Some(u64::MAX / 2),
            slots: Some(Slots { full: usize::MAX / 2, anonymous: 16 }),
            ..Settings::default()
        }
        .build();
        // Warm: every consumer seen once, so first-sight inserts are not timed.
        for c in &who {
            let g = share.admit(&share.plan(&Work::new(WorkKind::Clone, None), c, &share.usage(c))).unwrap();
            share.record(g, &Cost::default());
        }
        println!(
            "edda-admit plan+admit+record, {} ({} cores, {} ops/worker)",
            match shape {
                Shape::Spread => "SPREAD: 4096 namespaces",
                Shape::Hot => "HOT: one namespace",
                Shape::Core => "CORE: usage+admit+record, plan re-used",
            },
            cores,
            ops_per_worker()
        );
        println!("{:>8} {:>14} {:>14} {:>9}", "threads", "ns/op/thread", "Mops/s total", "scaling");
        let mut base = 0.0;
        for &n in &counts {
            let ns = run(&share, &who, n, shape);
            let mops = n as f64 * 1e3 / ns;
            if n == 1 {
                base = mops;
            }
            println!("{:>8} {:>14.1} {:>14.2} {:>8.2}x", n, ns, mops, mops / base);
        }
    }
}