Skip to main content

bench_embed_mt/
bench_embed_mt.rs

1//! Multi-threaded in-process throughput for `kevy_embedded::Store` — measures
2//! how the embedded keyspace scales across cores. An embed consumer (mailrs is
3//! a multi-threaded web server) shares one `Store` across request threads;
4//! this bench clones the `Store` (cheap Arc bump → same inner) into T threads
5//! all hammering GET / SET, and reports aggregate ops/s at T = 1..N.
6//!
7//! Run: `cargo run -p kevy-embedded --example bench_embed_mt --release`
8//! (pin to disjoint cores, e.g. `taskset -c 0-9`). `KEVY_BENCH_N` = ops/thread.
9
10use kevy_embedded::{Config, Store};
11use std::thread;
12use std::time::Instant;
13
14const KEYS: usize = 4096;
15const VAL: &[u8] = b"value-payload-16";
16
17fn run(label: &str, store: &Store, threads: usize, n_per: usize, keys: &[Vec<u8>], write: bool) {
18    let start = Instant::now();
19    let handles: Vec<_> = (0..threads)
20        .map(|tid| {
21            let s = store.clone(); // Arc bump → same inner / same lock
22            let keys = keys.to_vec();
23            thread::spawn(move || {
24                let mut acc = 0usize;
25                for i in 0..n_per {
26                    // De-correlate threads' key streams so they don't all hit
27                    // the same key/bucket in lockstep.
28                    let k = &keys[(i.wrapping_mul(31).wrapping_add(tid * 7)) % KEYS];
29                    if write {
30                        s.set(k, VAL).unwrap();
31                        acc += 1;
32                    } else if s.get(k).unwrap().is_some() {
33                        acc += 1;
34                    }
35                }
36                acc
37            })
38        })
39        .collect();
40    for h in handles {
41        h.join().unwrap();
42    }
43    let secs = start.elapsed().as_secs_f64();
44    let total = (threads * n_per) as f64;
45    println!(
46        "[{label:<10}] threads={threads:2}  {:>10.0} ops/s  ({:>5.1}M)",
47        total / secs,
48        total / secs / 1e6
49    );
50}
51
52fn main() {
53    let n_per: usize = std::env::var("KEVY_BENCH_N")
54        .ok()
55        .and_then(|s| s.parse().ok())
56        .unwrap_or(2_000_000);
57    let keys: Vec<Vec<u8>> = (0..KEYS).map(|i| format!("k{i}").into_bytes()).collect();
58    let shards: usize = std::env::var("KEVY_SHARDS")
59        .ok()
60        .and_then(|s| s.parse().ok())
61        .unwrap_or(1);
62
63    // Shared in-memory store (no AOF) — isolate the lock/keyspace from disk.
64    let store = Store::open(Config::default().with_shards(shards).with_ttl_reaper_manual()).unwrap();
65    for k in &keys {
66        store.set(k, VAL).unwrap();
67    }
68
69    println!(
70        "kevy-embedded MULTI-THREAD throughput — in-memory, shards={shards}, {KEYS} keys, {}B val, n={n_per}/thread",
71        VAL.len()
72    );
73    for &t in &[1usize, 2, 4, 8, 10] {
74        run("GET", &store, t, n_per, &keys, false);
75    }
76    for &t in &[1usize, 2, 4, 8, 10] {
77        run("SET", &store, t, n_per, &keys, true);
78    }
79}