use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use rand::Rng;
use rand::SeedableRng;
use sqlitegraph::backend::SubscriptionFilter;
use sqlitegraph::{EdgeSpec, GraphConfig, NodeSpec, open_graph, snapshot::SnapshotId};
mod bench_utils;
use bench_utils::{MEASURE, WARM_UP, create_benchmark_temp_dir};
fn create_star_graph(size: usize) -> (tempfile::TempDir, std::path::PathBuf, i64) {
let temp_dir = create_benchmark_temp_dir();
let db_path = temp_dir.path().join("benchmark.db");
let graph = open_graph(&db_path, &GraphConfig::native()).expect("Failed to create graph");
let mut node_ids = Vec::with_capacity(size + 1);
for i in 0..=size {
let node_id = graph
.insert_node(NodeSpec {
kind: "Node".to_string(),
name: format!("node_{}", i),
file_path: None,
data: serde_json::json!({"id": i}),
})
.expect("Failed to insert node");
node_ids.push(node_id);
}
let center = node_ids[0];
for i in 1..=size {
graph
.insert_edge(EdgeSpec {
from: center,
to: node_ids[i],
edge_type: "star".to_string(),
data: serde_json::json!({"spoke": i}),
})
.expect("Failed to insert edge");
}
(temp_dir, db_path, center)
}
fn create_random_graph(
size: usize,
edge_count: usize,
) -> (tempfile::TempDir, std::path::PathBuf, i64) {
let temp_dir = create_benchmark_temp_dir();
let db_path = temp_dir.path().join("benchmark.db");
let graph = open_graph(&db_path, &GraphConfig::native()).expect("Failed to create graph");
let mut node_ids = Vec::with_capacity(size);
for i in 0..size {
let node_id = graph
.insert_node(NodeSpec {
kind: "Node".to_string(),
name: format!("node_{}", i),
file_path: None,
data: serde_json::json!({"id": i}),
})
.expect("Failed to insert node");
node_ids.push(node_id);
}
let mut rng = rand::rngs::StdRng::seed_from_u64(0xA17C);
for _ in 0..edge_count {
let from_idx = rng.gen_range(0..size);
let mut to_idx = rng.gen_range(0..size);
while to_idx == from_idx {
to_idx = rng.gen_range(0..size);
}
let _ = graph.insert_edge(EdgeSpec {
from: node_ids[from_idx],
to: node_ids[to_idx],
edge_type: "random".to_string(),
data: serde_json::json!({"random_id": rng.r#gen::<u64>()}),
});
}
(temp_dir, db_path, node_ids[0])
}
fn create_tree_graph(size: usize) -> (tempfile::TempDir, std::path::PathBuf, i64) {
let temp_dir = create_benchmark_temp_dir();
let db_path = temp_dir.path().join("benchmark.db");
let graph = open_graph(&db_path, &GraphConfig::native()).expect("Failed to create graph");
let mut node_ids = Vec::with_capacity(size);
for i in 0..size {
let node_id = graph
.insert_node(NodeSpec {
kind: "Node".to_string(),
name: format!("node_{}", i),
file_path: None,
data: serde_json::json!({"id": i}),
})
.expect("Failed to insert node");
node_ids.push(node_id);
}
let mut parent_idx = 0;
let mut child_idx = 1;
while child_idx < size && parent_idx < size {
for _ in 0..3 {
if child_idx >= size {
break;
}
let _ = graph.insert_edge(EdgeSpec {
from: node_ids[parent_idx],
to: node_ids[child_idx],
edge_type: "tree".to_string(),
data: serde_json::json!({"parent": parent_idx, "child": child_idx}),
});
child_idx += 1;
}
parent_idx += 1;
}
(temp_dir, db_path, node_ids[0])
}
fn bench_star_baseline(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("non_chain_star_baseline");
group.measurement_time(MEASURE);
group.warm_up_time(WARM_UP);
for &size in &[100, 500, 1000] {
group.throughput(Throughput::Elements(size as u64));
group.bench_with_input(BenchmarkId::new("star_bfs", size), &size, |b, &_size| {
b.iter(|| {
let (_temp_dir, db_path, start_node) = create_star_graph(size);
let graph =
open_graph(&db_path, &GraphConfig::native()).expect("Failed to open graph");
let _result = graph.bfs(SnapshotId::current(), start_node, size as u32);
std::mem::forget(_temp_dir);
});
});
}
group.finish();
}
fn bench_star_with_pubsub(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("non_chain_star_pubsub");
group.measurement_time(MEASURE);
group.warm_up_time(WARM_UP);
for &size in &[100, 500, 1000] {
group.throughput(Throughput::Elements(size as u64));
group.bench_with_input(BenchmarkId::new("star_bfs", size), &size, |b, &_size| {
b.iter(|| {
let (_temp_dir, db_path, start_node) = create_star_graph(size);
let graph =
open_graph(&db_path, &GraphConfig::native()).expect("Failed to open graph");
for _ in 0..5 {
let (_id, _rx) = graph
.subscribe(SubscriptionFilter::all())
.expect("Failed to subscribe");
}
let _result = graph.bfs(SnapshotId::current(), start_node, size as u32);
std::mem::forget(_temp_dir);
});
});
}
group.finish();
}
fn bench_random_baseline(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("non_chain_random_baseline");
group.measurement_time(MEASURE);
group.warm_up_time(WARM_UP);
for &size in &[100, 500, 1000] {
let edge_count = size * 2;
group.throughput(Throughput::Elements(size as u64));
group.bench_with_input(BenchmarkId::new("random_bfs", size), &size, |b, &_size| {
b.iter(|| {
let (_temp_dir, db_path, start_node) = create_random_graph(size, edge_count);
let graph =
open_graph(&db_path, &GraphConfig::native()).expect("Failed to open graph");
let _result = graph.bfs(SnapshotId::current(), start_node, size as u32);
std::mem::forget(_temp_dir);
});
});
}
group.finish();
}
fn bench_random_with_pubsub(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("non_chain_random_pubsub");
group.measurement_time(MEASURE);
group.warm_up_time(WARM_UP);
for &size in &[100, 500, 1000] {
let edge_count = size * 2;
group.throughput(Throughput::Elements(size as u64));
group.bench_with_input(BenchmarkId::new("random_bfs", size), &size, |b, &_size| {
b.iter(|| {
let (_temp_dir, db_path, start_node) = create_random_graph(size, edge_count);
let graph =
open_graph(&db_path, &GraphConfig::native()).expect("Failed to open graph");
for _ in 0..5 {
let (_id, _rx) = graph
.subscribe(SubscriptionFilter::all())
.expect("Failed to subscribe");
}
let _result = graph.bfs(SnapshotId::current(), start_node, size as u32);
std::mem::forget(_temp_dir);
});
});
}
group.finish();
}
fn bench_tree_baseline(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("non_chain_tree_baseline");
group.measurement_time(MEASURE);
group.warm_up_time(WARM_UP);
for &size in &[100, 500, 1000] {
group.throughput(Throughput::Elements(size as u64));
group.bench_with_input(BenchmarkId::new("tree_bfs", size), &size, |b, &_size| {
b.iter(|| {
let (_temp_dir, db_path, start_node) = create_tree_graph(size);
let graph =
open_graph(&db_path, &GraphConfig::native()).expect("Failed to open graph");
let _result = graph.bfs(SnapshotId::current(), start_node, size as u32);
std::mem::forget(_temp_dir);
});
});
}
group.finish();
}
fn bench_tree_with_pubsub(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("non_chain_tree_pubsub");
group.measurement_time(MEASURE);
group.warm_up_time(WARM_UP);
for &size in &[100, 500, 1000] {
group.throughput(Throughput::Elements(size as u64));
group.bench_with_input(BenchmarkId::new("tree_bfs", size), &size, |b, &_size| {
b.iter(|| {
let (_temp_dir, db_path, start_node) = create_tree_graph(size);
let graph =
open_graph(&db_path, &GraphConfig::native()).expect("Failed to open graph");
for _ in 0..5 {
let (_id, _rx) = graph
.subscribe(SubscriptionFilter::all())
.expect("Failed to subscribe");
}
let _result = graph.bfs(SnapshotId::current(), start_node, size as u32);
std::mem::forget(_temp_dir);
});
});
}
group.finish();
}
criterion_group!(
benches,
bench_star_baseline,
bench_star_with_pubsub,
bench_random_baseline,
bench_random_with_pubsub,
bench_tree_baseline,
bench_tree_with_pubsub
);
criterion_main!(benches);