use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use std::hint::black_box;
use yo_common::Rng;
use yo_doc::{Builder, IndexKind, Key};
use yo_graph::{Dir, Graph};
const FOLLOWS: u32 = 1;
const NODES: u64 = 200_000;
const DEGREE: u64 = 20;
fn node_doc(id: u64) -> Vec<u8> {
let mut b = Builder::new();
b.begin_object().expect("open");
b.key(b"bucket").expect("key");
b.int((id % 16) as i64).expect("int");
b.key(b"name").expect("key");
b.text(&format!("n{id}")).expect("text");
b.end_object().expect("close");
b.finish().expect("finished").to_vec()
}
fn edge_doc(weight: i64) -> Vec<u8> {
let mut b = Builder::new();
b.begin_object().expect("open");
b.key(b"weight").expect("key");
b.int(weight).expect("int");
b.end_object().expect("close");
b.finish().expect("finished").to_vec()
}
fn build(nodes: u64, degree: u64, seed: u64) -> Graph {
let mut g = Graph::new();
for id in 0..nodes {
g.put_node(id, &node_doc(id)).expect("a node");
}
let mut rng = Rng::new(seed);
for src in 0..nodes {
for _ in 0..degree {
let dst = rng.next_u64() % nodes;
g.link(src, dst, FOLLOWS, &edge_doc((dst % 5) as i64))
.expect("linked");
}
}
g
}
fn plain(g: &Graph, from: u64) -> u64 {
g.neighbours(from, FOLLOWS, Dir::Out).iter().sum()
}
fn with_edges(g: &Graph, from: u64) -> i64 {
let mut total = 0;
for slot in g.edge_slots(from, FOLLOWS, Dir::Out) {
total += g
.edge(*slot)
.and_then(|d| d.get(b"weight"))
.and_then(|v| v.as_int())
.unwrap_or(0);
}
total
}
fn with_both(g: &Graph, from: u64) -> i64 {
let mut total = 0;
for (node, slot) in g.hop(from, FOLLOWS, Dir::Out) {
let w = g
.edge(slot)
.and_then(|d| d.get(b"weight"))
.and_then(|v| v.as_int())
.unwrap_or(0);
let b = g
.node(node)
.and_then(|d| d.get(b"bucket"))
.and_then(|v| v.as_int())
.unwrap_or(0);
total += w * b;
}
total
}
fn bench_hop(c: &mut Criterion) {
let mut rng = Rng::new(0x91ee);
let mut group = c.benchmark_group("graph/hop");
for degree in [4u64, 20] {
let g = build(20_000, degree, 0x5eed + degree);
group.bench_with_input(BenchmarkId::new("plain", degree), °ree, |b, _| {
b.iter(|| black_box(plain(black_box(&g), rng.next_u64() % 20_000)));
});
group.bench_with_input(BenchmarkId::new("edge props", degree), °ree, |b, _| {
b.iter(|| black_box(with_edges(black_box(&g), rng.next_u64() % 20_000)));
});
group.bench_with_input(BenchmarkId::new("both props", degree), °ree, |b, _| {
b.iter(|| black_box(with_both(black_box(&g), rng.next_u64() % 20_000)));
});
}
group.finish();
}
fn bench_two_hops(c: &mut Criterion) {
let g = build(NODES, DEGREE, 0x11ee);
let mut rng = Rng::new(7);
let mut group = c.benchmark_group("graph/two hops");
group.bench_function("plain", |b| {
b.iter(|| {
let from = rng.next_u64() % NODES;
let mut total = 0u64;
for hop in g.neighbours(from, FOLLOWS, Dir::Out) {
total = total.wrapping_add(plain(&g, *hop));
}
black_box(total)
});
});
group.bench_function("prefetched", |b| {
b.iter(|| {
let from = rng.next_u64() % NODES;
let first = g.neighbours(from, FOLLOWS, Dir::Out);
for hop in first {
g.prefetch(*hop, FOLLOWS, Dir::Out);
}
let mut total = 0u64;
for hop in first {
total = total.wrapping_add(plain(&g, *hop));
}
black_box(total)
});
});
group.bench_function("both props", |b| {
b.iter(|| {
let from = rng.next_u64() % NODES;
let mut total = 0i64;
for hop in g.neighbours(from, FOLLOWS, Dir::Out).to_vec() {
total = total.wrapping_add(with_both(&g, hop));
}
black_box(total)
});
});
group.finish();
}
fn bench_link(c: &mut Criterion) {
let mut rng = Rng::new(0xfeed);
let props = edge_doc(3);
let mut group = c.benchmark_group("graph/link");
group.bench_function("with props", |b| {
b.iter_batched_ref(
Graph::new,
|g| {
for src in 0..2000u64 {
for _ in 0..DEGREE {
g.link(src, rng.next_u64() % 2000, FOLLOWS, &props)
.expect("linked");
}
}
},
criterion::BatchSize::LargeInput,
);
});
group.finish();
}
fn bench_find(c: &mut Criterion) {
let mut g = build(50_000, 4, 0x1234);
g.index_nodes("$.bucket", IndexKind::Equality)
.expect("indexed");
let mut rng = Rng::new(3);
let mut group = c.benchmark_group("graph/find");
group.bench_function("count", |b| {
b.iter(|| {
let bucket = (rng.next_u64() % 16) as i64;
black_box(g.count_nodes("$.bucket", &Key::int(bucket)).unwrap_or(0))
});
});
group.bench_function("hop from each", |b| {
b.iter(|| {
let bucket = (rng.next_u64() % 16) as i64;
let mut ids = Vec::with_capacity(4096);
g.find_nodes("$.bucket", &Key::int(bucket), |id, _| ids.push(id))
.expect("found");
let mut total = 0u64;
for id in &ids {
total = total.wrapping_add(plain(&g, *id));
}
black_box(total)
});
});
group.finish();
}
criterion_group!(benches, bench_hop, bench_two_hops, bench_link, bench_find);
criterion_main!(benches);