use criterion::{Criterion, criterion_group, criterion_main};
use issundb::{Graph, GraphQueryExt, NodeId};
use serde_json::json;
use std::hint::black_box;
use tempfile::TempDir;
const RARE: usize = 50;
const LARGE: usize = 50_000;
const SMALL: usize = 5_000;
fn build_graph(common: usize) -> (TempDir, Graph, Vec<NodeId>) {
let dir = TempDir::new().unwrap();
let g = Graph::open(dir.path(), 1).unwrap();
let cities: Vec<NodeId> = (0..RARE)
.map(|i| {
g.add_node("City", &json!({ "name": format!("City{i}") }))
.unwrap()
})
.collect();
let mut persons = Vec::with_capacity(common);
for i in 0..common {
let p = g
.add_node(
"Person",
&json!({ "name": format!("P{i}"), "seq": i as i64 }),
)
.unwrap();
g.add_edge(p, cities[i % RARE], "KNOWS", &json!({}))
.unwrap();
persons.push(p);
}
g.rebuild_csr().unwrap();
(dir, g, persons)
}
fn bench_reduce_count(c: &mut Criterion) {
let (_dir, g, _) = build_graph(LARGE);
let mut group = c.benchmark_group("reduce_count");
group.bench_function("count_star_reduced", |b| {
b.iter(|| black_box(g.query("MATCH (n:Person) RETURN count(*)").unwrap()))
});
group.bench_function("count_property_scan", |b| {
b.iter(|| black_box(g.query("MATCH (n:Person) RETURN count(n.seq)").unwrap()))
});
group.finish();
}
fn bench_id_seek(c: &mut Criterion) {
let (_dir, g, persons) = build_graph(LARGE);
let mid = persons[persons.len() / 2];
let mid_seq = (persons.len() / 2) as i64;
let mut group = c.benchmark_group("id_seek");
group.bench_function("id_seek", |b| {
let q = format!("MATCH (n:Person) WHERE id(n) = {mid} RETURN n");
b.iter(|| black_box(g.query(&q).unwrap()))
});
group.bench_function("full_scan_unindexable", |b| {
let q = format!("MATCH (n:Person) WHERE n.seq % {LARGE} = {mid_seq} RETURN n");
b.iter(|| black_box(g.query(&q).unwrap()))
});
group.bench_function("indexed_property_lookup", |b| {
let q = format!("MATCH (n:Person) WHERE n.seq = {mid_seq} RETURN n");
b.iter(|| black_box(g.query(&q).unwrap()))
});
group.finish();
}
fn bench_scan_selection(c: &mut Criterion) {
let (_dir, g, _) = build_graph(SMALL);
let mut group = c.benchmark_group("scan_selection");
group.bench_function("common_to_rare", |b| {
b.iter(|| {
black_box(
g.query("MATCH (a:Person)-[:KNOWS]->(b:City) RETURN a, b")
.unwrap(),
)
})
});
group.finish();
}
fn bench_chain_fusion(c: &mut Criterion) {
let dir = TempDir::new().unwrap();
let g = Graph::open(dir.path(), 1).unwrap();
let hub = g.add_node("Hub", &json!({})).unwrap();
for _ in 0..SMALL {
let a = g.add_node("A", &json!({})).unwrap();
let b = g.add_node("B", &json!({})).unwrap();
let cc = g.add_node("C", &json!({})).unwrap();
g.add_edge(a, b, "R1", &json!({})).unwrap();
g.add_edge(b, cc, "R2", &json!({})).unwrap();
g.add_edge(cc, hub, "R3", &json!({})).unwrap();
}
g.rebuild_csr().unwrap();
let mut group = c.benchmark_group("chain_fusion");
group.bench_function("three_hop", |b| {
b.iter(|| {
black_box(
g.query("MATCH (a:A)-[:R1]->(x)-[:R2]->(y)-[:R3]->(z:Hub) RETURN a, z")
.unwrap(),
)
})
});
group.finish();
}
criterion_group!(
benches,
bench_reduce_count,
bench_id_seek,
bench_scan_selection,
bench_chain_fusion
);
criterion_main!(benches);