use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use std::hint::black_box;
use yo_kv::setops::{self, Plan};
use yo_kv::{Set, SetLimits};
const N: usize = 200_000;
const KS: [usize; 7] = [2, 4, 6, 7, 8, 12, 16];
fn ks() -> &'static [usize] {
if std::env::var_os("YO_BENCH_SMOKE").is_some() {
&KS[..2]
} else {
&KS
}
}
fn build(k: usize, keep: usize) -> Vec<Set> {
(0..k)
.map(|s| {
let mut set = Set::with_hint(b"shared:000000000000", N, &SetLimits::DEFAULT);
for i in 0..keep {
set.add(format!("shared:{i:012}").as_bytes(), &SetLimits::DEFAULT);
}
for i in keep..N {
set.add(format!("own{s}:{i:012}").as_bytes(), &SetLimits::DEFAULT);
}
set
})
.collect()
}
fn bench_crossover(c: &mut Criterion) {
let mut g = c.benchmark_group("setops");
g.sample_size(10);
for (shape, keep) in [("dense", N * 9 / 10), ("sparse", N / 100)] {
for &k in ks() {
let sets = build(k, keep);
let refs: Vec<&Set> = sets.iter().collect();
for (name, how) in [("probe", Plan::Probe), ("accumulate", Plan::Accumulate)] {
g.bench_with_input(
BenchmarkId::new(format!("{shape}_{name}"), k),
&k,
|b, _| {
let mut sc = setops::Scratch::new();
b.iter(|| {
let mut n = 0usize;
setops::inter_with(&mut sc, how, black_box(&refs), 0, |_| n += 1);
n
})
},
);
}
}
}
g.finish();
}
fn bench_union_and_diff(c: &mut Criterion) {
let mut g = c.benchmark_group("setops");
g.sample_size(10);
for &k in ks() {
let sets = build(k, N / 2);
let refs: Vec<&Set> = sets.iter().collect();
g.bench_with_input(BenchmarkId::new("union", k), &k, |b, _| {
let mut sc = setops::Scratch::new();
b.iter(|| {
let mut n = 0usize;
setops::union(&mut sc, black_box(&refs), 0, |_| n += 1);
n
})
});
g.bench_with_input(BenchmarkId::new("diff", k), &k, |b, _| {
b.iter(|| {
let mut n = 0usize;
setops::diff(black_box(&refs), 0, |_| n += 1);
n
})
});
}
g.finish();
}
fn bench_store(c: &mut Criterion) {
let mut g = c.benchmark_group("setops");
g.sample_size(10);
for &k in ks() {
let sets = build(k, N * 9 / 10);
let refs: Vec<&Set> = sets.iter().collect();
let upper = refs.iter().map(|s| s.len()).min().unwrap_or(0);
g.bench_with_input(BenchmarkId::new("interstore", k), &k, |b, _| {
let mut sc = setops::Scratch::new();
b.iter(|| {
setops::collect(upper, &SetLimits::DEFAULT, |f| {
setops::inter(&mut sc, black_box(&refs), 0, f);
})
.map_or(0, |s| s.len())
})
});
}
g.finish();
}
#[derive(Clone, Copy)]
enum Spread {
Banded,
Striped,
}
fn build_ints(k: usize, n: usize, keep: usize, how: Spread) -> Vec<Set> {
(0..k)
.map(|s| {
let mut set = Set::new();
for i in 0..keep {
set.add(i.to_string().as_bytes(), &SetLimits::DEFAULT);
}
for i in keep..n {
let m = match how {
Spread::Banded => (s + 1) * 10_000_000 + i,
Spread::Striped => 10_000_000 + i * k + s,
};
set.add(m.to_string().as_bytes(), &SetLimits::DEFAULT);
}
assert!(set.ints().is_some(), "every operand has to be an intset");
set
})
.collect()
}
fn bench_merge(c: &mut Criterion) {
let mut g = c.benchmark_group("setops_ints");
g.sample_size(10);
for (shape, sizes, keep, spread) in [
("dense", (N, N), N * 9 / 10, Spread::Banded),
("sparse", (N, N), N / 100, Spread::Banded),
("striped", (N, N), N / 100, Spread::Striped),
("skewed", (N / 1_000, N), N / 2_000, Spread::Banded),
] {
for &k in ks() {
let mut sets = build_ints(k, sizes.1, keep, spread);
if sizes.0 != sizes.1 {
sets[0] = build_ints(1, sizes.0, keep, spread).remove(0);
}
let refs: Vec<&Set> = sets.iter().collect();
for (name, how) in [
("merge", Plan::Merge),
("probe", Plan::Probe),
("accumulate", Plan::Accumulate),
] {
g.bench_with_input(
BenchmarkId::new(format!("{shape}_{name}"), k),
&k,
|b, _| {
let mut sc = setops::Scratch::new();
b.iter(|| {
let mut n = 0usize;
setops::inter_with(&mut sc, how, black_box(&refs), 0, |_| n += 1);
n
})
},
);
}
}
}
for &k in ks() {
let sets = build_ints(k, N, N / 2, Spread::Striped);
let refs: Vec<&Set> = sets.iter().collect();
for (name, how) in [("union_merge", Plan::Merge), ("union_table", Plan::Probe)] {
g.bench_with_input(BenchmarkId::new(name, k), &k, |b, _| {
let mut sc = setops::Scratch::new();
b.iter(|| {
let mut n = 0usize;
setops::union_with(&mut sc, how, black_box(&refs), 0, |_| n += 1);
n
})
});
}
for (name, how) in [("diff_merge", Plan::Merge), ("diff_probe", Plan::Probe)] {
g.bench_with_input(BenchmarkId::new(name, k), &k, |b, _| {
b.iter(|| {
let mut n = 0usize;
setops::diff_with(how, black_box(&refs), 0, |_| n += 1);
n
})
});
}
}
for &k in ks() {
let sets = build_ints(k, N, N * 9 / 10, Spread::Banded);
let refs: Vec<&Set> = sets.iter().collect();
let upper = refs.iter().map(|s| s.len()).min().unwrap_or(0);
for (name, how) in [
("interstore_merge", Plan::Merge),
("interstore_probe", Plan::Probe),
] {
g.bench_with_input(BenchmarkId::new(name, k), &k, |b, _| {
let mut sc = setops::Scratch::new();
b.iter(|| {
setops::collect(upper, &SetLimits::DEFAULT, |f| {
setops::inter_with(&mut sc, how, black_box(&refs), 0, f);
})
.map_or(0, |s| s.len())
})
});
}
}
g.finish();
}
fn bench_small(c: &mut Criterion) {
let mut g = c.benchmark_group("setops_small");
for &n in &[8usize, 64] {
for &k in &[2usize, 3] {
let ints = build_ints(k, n, n / 2, Spread::Banded);
let int_refs: Vec<&Set> = ints.iter().collect();
let text = build_small_text(k, n, n / 2);
let text_refs: Vec<&Set> = text.iter().collect();
for (what, refs) in [("ints", &int_refs), ("text", &text_refs)] {
let id = format!("{what}/n{n}/k{k}");
g.bench_with_input(BenchmarkId::new("inter", &id), &n, |b, _| {
let mut sc = setops::Scratch::new();
b.iter(|| {
let mut c = 0usize;
setops::inter(&mut sc, black_box(refs), 0, |_| c += 1);
c
})
});
g.bench_with_input(BenchmarkId::new("union", &id), &n, |b, _| {
let mut sc = setops::Scratch::new();
b.iter(|| {
let mut c = 0usize;
setops::union(&mut sc, black_box(refs), 0, |_| c += 1);
c
})
});
}
}
}
g.finish();
}
fn build_small_text(k: usize, n: usize, keep: usize) -> Vec<Set> {
(0..k)
.map(|s| {
let mut set = Set::new();
for i in 0..keep {
set.add(format!("shared:{i}").as_bytes(), &SetLimits::DEFAULT);
}
for i in keep..n {
set.add(format!("own:{s}:{i}").as_bytes(), &SetLimits::DEFAULT);
}
set
})
.collect()
}
criterion_group!(
benches,
bench_crossover,
bench_union_and_diff,
bench_store,
bench_merge,
bench_small
);
criterion_main!(benches);