use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main};
use std::hint::black_box;
use std::time::Duration;
use yo::MEMORY;
fn key(i: usize) -> String {
format!("key:{i:012}")
}
const KEYS: usize = 100_000;
fn keys() -> usize {
if std::env::var_os("YO_BENCH_SMOKE").is_some() {
1_000
} else {
KEYS
}
}
fn filled(n: usize) -> (yo_kv::Keyspace, yo::Db) {
let mut store = yo_kv::Keyspace::new();
let db = yo::open(MEMORY).expect("in memory always opens");
let keys = db.strings();
for i in 0..n {
let k = key(i);
store
.set_plain(k.as_bytes(), k.as_bytes())
.expect("room for a record");
keys.set(&k, &k).expect("room for a record");
}
(store, db)
}
fn bench_get(c: &mut Criterion) {
let n = keys();
let (mut store, db) = filled(n);
let api = db.strings();
let sample: Vec<String> = (0..64).map(|i| key(i * (n / 64).max(1))).collect();
let mut g = c.benchmark_group("get");
g.throughput(Throughput::Elements(1));
let mut at = 0usize;
g.bench_function("store", |b| {
b.iter(|| {
at = (at + 1) & 63;
black_box(
store
.get(sample[at].as_bytes())
.expect("a string")
.map(|v| v.len()),
)
});
});
g.bench_function("api/with", |b| {
b.iter(|| {
at = (at + 1) & 63;
black_box(api.with(&sample[at], |v| v.len()))
});
});
g.bench_function("api/get", |b| {
b.iter(|| {
at = (at + 1) & 63;
black_box(api.get(&sample[at]))
});
});
g.finish();
}
fn bench_set(c: &mut Criterion) {
let n = keys();
let sample: Vec<String> = (0..n).map(key).collect();
let mut g = c.benchmark_group("set");
g.throughput(Throughput::Elements(1));
g.bench_function("store", |b| {
let mut store = yo_kv::Keyspace::new();
let mut i = 0usize;
b.iter(|| {
i = (i + 1) % n;
store
.set_plain(sample[i].as_bytes(), sample[i].as_bytes())
.expect("room");
});
});
g.bench_function("api", |b| {
let db = yo::open(MEMORY).expect("in memory always opens");
let keys = db.strings();
let mut i = 0usize;
b.iter(|| {
i = (i + 1) % n;
keys.set(&sample[i], &sample[i]).expect("room");
});
});
g.finish();
}
fn bench_incr(c: &mut Criterion) {
let mut g = c.benchmark_group("incr");
g.throughput(Throughput::Elements(1));
g.bench_function("store", |b| {
let mut store = yo_kv::Keyspace::new();
b.iter(|| black_box(store.incr(b"hits").expect("a counter")));
});
g.bench_function("api", |b| {
let db = yo::open(MEMORY).expect("in memory always opens");
let keys = db.strings();
b.iter(|| black_box(keys.incr("hits").expect("a counter")));
});
g.bench_function("api/counter", |b| {
let db = yo::open(MEMORY).expect("in memory always opens");
let hits = db.counter("hits");
b.iter(|| black_box(hits.incr().expect("a counter")));
});
g.finish();
}
fn bench_mset(c: &mut Criterion) {
let pairs: Vec<(String, String)> = (0..8).map(|i| (key(i), key(i))).collect();
let mut g = c.benchmark_group("mset");
g.throughput(Throughput::Elements(pairs.len() as u64));
g.bench_function("store", |b| {
let mut store = yo_kv::Keyspace::new();
b.iter(|| {
store
.mset(pairs.iter().map(|(k, v)| (k.as_bytes(), v.as_bytes())))
.expect("room");
});
});
g.bench_function("api", |b| {
let db = yo::open(MEMORY).expect("in memory always opens");
let keys = db.strings();
b.iter(|| keys.set_many(&pairs).expect("room"));
});
g.finish();
}
fn set_of(n: usize) -> yo_kv::Keyspace {
let mut store = yo_kv::Keyspace::new();
let members: Vec<String> = (0..n).map(|i| format!("m{i}")).collect();
store
.sadd(b"s", members.iter().map(|m| m.as_bytes()))
.expect("a set");
store
}
fn crowded_set_of(n: usize, others: usize) -> yo_kv::Keyspace {
let mut store = set_of(n);
for i in 0..others {
let k = key(i);
store
.set_plain(k.as_bytes(), k.as_bytes())
.expect("room for a record");
}
store
}
fn api_set_of(n: usize, others: usize) -> (yo::Db, yo::Set) {
let db = yo::open(MEMORY).expect("in memory always opens");
let s = db.set("s");
let members: Vec<String> = (0..n).map(|i| format!("m{i}")).collect();
s.add_many(&members).expect("a set");
let keys = db.strings();
for i in 0..others {
let k = key(i);
keys.set(&k, &k).expect("room for a record");
}
(db, s)
}
fn bench_sets(c: &mut Criterion) {
let mut g = c.benchmark_group("sadd");
g.throughput(Throughput::Elements(1));
g.bench_function("store/hot", |b| {
let mut store = set_of(1_000);
let members: Vec<String> = (0..1_024).map(|i| format!("m{i}")).collect();
let mut i = 0usize;
b.iter(|| {
i = (i + 1) & 1_023;
black_box(
store
.sadd(b"s", std::iter::once(members[i].as_bytes()))
.expect("a set"),
)
});
});
g.bench_function("store/hot-crowded", |b| {
let mut store = crowded_set_of(1_000, 100_000);
let members: Vec<String> = (0..1_024).map(|i| format!("m{i}")).collect();
let mut i = 0usize;
b.iter(|| {
i = (i + 1) & 1_023;
black_box(
store
.sadd(b"s", std::iter::once(members[i].as_bytes()))
.expect("a set"),
)
});
});
g.bench_function("api/hot", |b| {
let (_db, s) = api_set_of(1_000, 0);
let members: Vec<String> = (0..1_024).map(|i| format!("m{i}")).collect();
let mut i = 0usize;
b.iter(|| {
i = (i + 1) & 1_023;
black_box(s.add(&members[i]).expect("a set"))
});
});
g.bench_function("api/hot-crowded", |b| {
let (_db, s) = api_set_of(1_000, 100_000);
let members: Vec<String> = (0..1_024).map(|i| format!("m{i}")).collect();
let mut i = 0usize;
b.iter(|| {
i = (i + 1) & 1_023;
black_box(s.add(&members[i]).expect("a set"))
});
});
g.bench_function("api/contains", |b| {
let (_db, s) = api_set_of(1_000, 100_000);
let members: Vec<String> = (0..1_024).map(|i| format!("m{i}")).collect();
let mut i = 0usize;
b.iter(|| {
i = (i + 1) & 1_023;
black_box(s.contains(&members[i]).expect("a set"))
});
});
g.finish();
const POP: usize = 1_000;
let mut g = c.benchmark_group("spop");
g.throughput(Throughput::Elements(POP as u64));
g.bench_function("store/copy", |b| {
b.iter_batched_ref(
|| set_of(POP),
|store| black_box(store.spop_n(b"s", POP).expect("a set").len()),
BatchSize::LargeInput,
);
});
g.bench_function("store/into", |b| {
b.iter_batched_ref(
|| set_of(POP),
|store| {
let mut bytes = 0;
store
.spop_into(b"s", POP, |m| bytes += m.byte_len())
.expect("a set");
black_box(bytes)
},
BatchSize::LargeInput,
);
});
g.bench_function("api/pop_n", |b| {
b.iter_batched_ref(
|| api_set_of(POP, 0),
|(_db, s)| black_box(s.pop_n(POP).expect("a set").len()),
BatchSize::LargeInput,
);
});
g.finish();
}
fn bench_clock(c: &mut Criterion) {
let mut g = c.benchmark_group("clock");
g.throughput(Throughput::Elements(1));
g.bench_function("off", |b| {
let db = yo::open(MEMORY).expect("in memory always opens");
let keys = db.strings();
keys.set("k", "v").expect("room");
assert!(!db.reads_the_clock());
b.iter(|| black_box(keys.with("k", |v| v.len())));
});
g.bench_function("on", |b| {
let db = yo::open(MEMORY).expect("in memory always opens");
let keys = db.strings();
keys.set("k", "v").expect("room");
keys.set_for("ttl", "v", Duration::from_secs(3_600))
.expect("room");
assert!(db.reads_the_clock());
b.iter(|| black_box(keys.with("k", |v| v.len())));
});
g.finish();
}
fn bench_expiry(c: &mut Criterion) {
let mut g = c.benchmark_group("expire");
g.throughput(Throughput::Elements(1));
let hour = Duration::from_secs(3_600);
g.bench_function("string", |b| {
let db = yo::open(MEMORY).expect("in memory always opens");
db.strings().set("k", "v").expect("room");
let keys = db.keys();
b.iter(|| black_box(keys.expire_in("k", hour).expect("a key")));
});
for members in [1usize, 10_000] {
g.bench_function(format!("set/{members}"), |b| {
let (db, s) = api_set_of(members, 0);
s.add("m0").expect("room");
let keys = db.keys();
b.iter(|| black_box(keys.expire_in("s", hour).expect("a key")));
});
}
g.bench_function("ttl", |b| {
let db = yo::open(MEMORY).expect("in memory always opens");
db.strings().set("k", "v").expect("room");
let keys = db.keys();
keys.expire_in("k", hour).expect("a key");
b.iter(|| black_box(keys.ttl("k").expect("a key")));
});
g.bench_function("persist", |b| {
let db = yo::open(MEMORY).expect("in memory always opens");
db.strings().set("k", "v").expect("room");
let keys = db.keys();
b.iter(|| {
keys.expire_in("k", hour).expect("a key");
black_box(keys.persist("k").expect("a key"))
});
});
g.finish();
}
criterion_group!(
benches,
bench_get,
bench_set,
bench_incr,
bench_mset,
bench_sets,
bench_expiry,
bench_clock
);
criterion_main!(benches);