use std::time::Duration;
use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main};
use rand::{Rng, SeedableRng};
use sqlitegraph::backend::native::{
NativeNodeId, edge_store::EdgeStore, graph_file::GraphFile, graph_ops::native_bfs,
node_store::NodeStore,
};
use tempfile::TempDir;
mod bench_utils;
use bench_utils::create_benchmark_temp_dir;
const MEASURE: Duration = Duration::from_millis(500);
const WARM_UP: Duration = Duration::from_millis(300);
fn create_chain_graph(size: usize, temp_dir: &TempDir) -> (GraphFile, Vec<NativeNodeId>) {
let db_path = temp_dir.path().join("benchmark_chain.db");
let mut graph_file = GraphFile::create(&db_path).expect("Failed to create graph file");
let mut node_ids = Vec::with_capacity(size);
for i in 0..size {
let mut node_store = NodeStore::new(&mut graph_file);
let node_id = node_store
.allocate_node_id()
.expect("Failed to allocate node ID");
let record = sqlitegraph::backend::native::NodeRecord::new(
node_id,
"Node".to_string(),
format!("node_{}", i),
serde_json::json!({"id": i}),
);
node_store
.write_node(&record)
.expect("Failed to write node");
node_ids.push(node_id);
}
let mut edge_store = EdgeStore::new(&mut graph_file);
for i in 0..size.saturating_sub(1) {
let edge = sqlitegraph::backend::native::EdgeRecord::new(
i as i64 + 1, node_ids[i], node_ids[i + 1], "chain".to_string(),
serde_json::json!({"order": i}),
);
edge_store
.write_edge(&edge)
.expect("Failed to write chain edge");
}
(graph_file, node_ids)
}
fn bench_chain_traversal(c: &mut Criterion) {
let mut group = c.benchmark_group("chain_traversal");
group.measurement_time(MEASURE);
group.warm_up_time(WARM_UP);
for &chain_size in &[100, 500] {
let temp_dir = create_benchmark_temp_dir();
let (mut graph_file, node_ids) = create_chain_graph(chain_size, &temp_dir);
let start_node = node_ids[0];
assert!(
node_ids.contains(&start_node),
"start_node {} not found in chain of {} nodes",
start_node,
chain_size
);
group.bench_with_input(
BenchmarkId::from_parameter(chain_size),
&chain_size,
|b, &_size| {
b.iter(|| {
let visited = native_bfs(&mut graph_file, start_node, chain_size as u32)
.expect("Failed to traverse chain");
black_box(visited)
});
},
);
std::mem::forget(temp_dir);
}
group.finish();
}
fn bench_star_traversal(c: &mut Criterion) {
let mut group = c.benchmark_group("star_traversal");
group.measurement_time(MEASURE);
group.warm_up_time(WARM_UP);
let star_size = 100;
let temp_dir = create_benchmark_temp_dir();
let db_path = temp_dir.path().join("benchmark_star.db");
let mut graph_file = GraphFile::create(&db_path).expect("Failed to create graph file");
let mut node_ids = Vec::with_capacity(star_size);
for i in 0..star_size {
let mut node_store = NodeStore::new(&mut graph_file);
let node_id = node_store
.allocate_node_id()
.expect("Failed to allocate node ID");
let record = sqlitegraph::backend::native::NodeRecord::new(
node_id,
"Node".to_string(),
if i == 0 {
"center".to_string()
} else {
format!("spoke_{}", i)
},
serde_json::json!({"id": i}),
);
node_store
.write_node(&record)
.expect("Failed to write node");
node_ids.push(node_id);
}
let center_node = node_ids[0];
let mut edge_store = EdgeStore::new(&mut graph_file);
for &spoke_node in &node_ids[1..] {
let edge_id = edge_store.allocate_edge_id();
let edge = sqlitegraph::backend::native::EdgeRecord::new(
edge_id,
center_node,
spoke_node,
"star".to_string(),
serde_json::json!({}),
);
edge_store
.write_edge(&edge)
.expect("Failed to write star edge");
}
group.bench_function(BenchmarkId::new("star", star_size), |b| {
b.iter(|| {
let mut graph_file = GraphFile::open(&db_path).expect("Failed to open graph");
let visited =
native_bfs(&mut graph_file, center_node, 2).expect("Failed to traverse star");
black_box(visited)
});
});
std::mem::forget(temp_dir);
group.finish();
}
fn bench_random_traversal(c: &mut Criterion) {
let mut group = c.benchmark_group("random_traversal");
group.measurement_time(MEASURE);
group.warm_up_time(WARM_UP);
for &random_size in &[100, 500] {
let edge_count = random_size * 2; let temp_dir = create_benchmark_temp_dir();
let db_path = temp_dir
.path()
.join(format!("benchmark_random_{}.db", random_size));
let mut graph_file = GraphFile::create(&db_path).expect("Failed to create graph file");
let mut node_ids = Vec::with_capacity(random_size);
for i in 0..random_size {
let mut node_store = NodeStore::new(&mut graph_file);
let node_id = node_store
.allocate_node_id()
.expect("Failed to allocate node ID");
let record = sqlitegraph::backend::native::NodeRecord::new(
node_id,
"Node".to_string(),
format!("node_{}", i),
serde_json::json!({"id": i}),
);
node_store
.write_node(&record)
.expect("Failed to write node");
node_ids.push(node_id);
}
let mut edge_store = EdgeStore::new(&mut graph_file);
let mut rng = rand::rngs::StdRng::seed_from_u64(0x5F3759DF);
for _ in 0..edge_count {
let from_idx = rng.gen_range(0..random_size);
let mut to_idx = rng.gen_range(0..random_size);
while to_idx == from_idx {
to_idx = rng.gen_range(0..random_size);
}
let edge_id = edge_store.allocate_edge_id();
let edge = sqlitegraph::backend::native::EdgeRecord::new(
edge_id,
node_ids[from_idx],
node_ids[to_idx],
"random".to_string(),
serde_json::json!({}),
);
edge_store
.write_edge(&edge)
.expect("Failed to write random edge");
}
let start_node = node_ids[0];
group.bench_function(BenchmarkId::new("random", random_size), |b| {
b.iter(|| {
let mut graph_file = GraphFile::open(&db_path).expect("Failed to open graph");
let visited = native_bfs(&mut graph_file, start_node, 10)
.expect("Failed to traverse random graph");
black_box(visited)
});
});
std::mem::forget(temp_dir);
}
group.finish();
}
criterion_group!(
io12_benches,
bench_chain_traversal,
bench_star_traversal,
bench_random_traversal
);
criterion_main!(io12_benches);