use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use interstellar::prelude::*;
use interstellar::storage::{Graph, GraphStorage};
use std::collections::HashMap;
use std::sync::Arc;
use std::thread;
fn create_benchmark_graph(num_vertices: usize, num_edges: usize) -> Graph {
let graph = Graph::new();
let mut vertex_ids = Vec::with_capacity(num_vertices);
for i in 0..num_vertices {
let (label, props) = if i % 2 == 0 {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String(format!("person_{}", i)));
props.insert("age".to_string(), Value::Int((i % 100) as i64));
("person", props)
} else {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String(format!("software_{}", i)));
props.insert(
"version".to_string(),
Value::String(format!("1.{}", i % 10)),
);
("software", props)
};
let id = graph.add_vertex(label, props);
vertex_ids.push(id);
}
for i in 0..num_edges {
let src_idx = i % num_vertices;
let dst_idx = (i * 7 + 13) % num_vertices;
if src_idx == dst_idx {
continue;
}
let label = if i % 3 == 0 { "knows" } else { "uses" };
let mut props = HashMap::new();
props.insert("weight".to_string(), Value::Float((i % 100) as f64 / 10.0));
let _ = graph.add_edge(vertex_ids[src_idx], vertex_ids[dst_idx], label, props);
}
graph
}
fn create_multi_label_graph(num_vertices: usize, num_labels: usize) -> Graph {
let graph = Graph::new();
for i in 0..num_vertices {
let label = format!("label_{}", i % num_labels);
let mut props = HashMap::new();
props.insert("id".to_string(), Value::Int(i as i64));
graph.add_vertex(&label, props);
}
graph
}
fn bench_step_count_scaling(c: &mut Criterion) {
let graph = create_benchmark_graph(1_000, 5_000);
let mut group = c.benchmark_group("perf: step_count_scaling");
group.throughput(Throughput::Elements(1_000));
group.bench_function("v().count()", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
black_box(g.v().count())
})
});
group.bench_function("v().out().count()", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
black_box(g.v().out().count())
})
});
group.bench_function("v().out().out().count()", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
black_box(g.v().out().out().count())
})
});
group.bench_function("v().out().out().out().count()", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
black_box(g.v().out().out().out().count())
})
});
group.bench_function("v().out().out().out().out().dedup().count()", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
black_box(g.v().out().out().out().out().dedup().count())
})
});
group.finish();
}
fn bench_count_vs_direct(c: &mut Criterion) {
let mut group = c.benchmark_group("perf: count_vs_direct");
for size in [1_000, 10_000, 100_000] {
let graph = create_benchmark_graph(size, 0);
group.throughput(Throughput::Elements(size as u64));
group.bench_with_input(BenchmarkId::new("g.v().count()", size), &size, |b, _| {
b.iter(|| {
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
black_box(g.v().count())
})
});
group.bench_with_input(
BenchmarkId::new("snapshot.vertex_count()", size),
&size,
|b, _| {
b.iter(|| {
let snapshot = graph.snapshot();
black_box(snapshot.vertex_count())
})
},
);
group.bench_with_input(
BenchmarkId::new("all_vertices().count()", size),
&size,
|b, _| {
b.iter(|| {
let snapshot = graph.snapshot();
black_box(snapshot.all_vertices().count())
})
},
);
}
group.finish();
}
fn bench_has_label_scaling(c: &mut Criterion) {
let mut group = c.benchmark_group("perf: has_label_scaling");
let num_vertices = 10_000;
for num_labels in [1, 10, 100] {
let graph = create_multi_label_graph(num_vertices, num_labels);
let target_label = "label_0";
group.bench_with_input(
BenchmarkId::new("has_label()", num_labels),
&num_labels,
|b, _| {
b.iter(|| {
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
black_box(g.v().has_label(target_label).count())
})
},
);
}
let graph = create_multi_label_graph(num_vertices, 100);
group.bench_function("all_vertices().filter(label==)", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
black_box(
snapshot
.all_vertices()
.filter(|v| v.label == "label_0")
.count(),
)
})
});
group.finish();
}
fn bench_navigation_step_overhead(c: &mut Criterion) {
let graph = create_benchmark_graph(1_000, 5_000);
let mut group = c.benchmark_group("perf: navigation_overhead");
group.bench_function("v(0).out().count()", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
black_box(g.v_ids([VertexId(0)]).out().count())
})
});
group.bench_function("v(0).out(\"knows\").count()", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
black_box(g.v_ids([VertexId(0)]).out_labels(&["knows"]).count())
})
});
group.bench_function("v(0).out_e().in_v().count()", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
black_box(g.v_ids([VertexId(0)]).out_e().in_v().count())
})
});
group.bench_function("storage.out_edges(0).count()", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
black_box(snapshot.out_edges(VertexId(0)).count())
})
});
group.finish();
}
fn bench_iterator_boxing(c: &mut Criterion) {
let graph = create_benchmark_graph(10_000, 50_000);
let mut group = c.benchmark_group("perf: iterator_boxing");
group.throughput(Throughput::Elements(10_000));
group.bench_function("all_vertices() [boxed]", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
black_box(snapshot.all_vertices().count())
})
});
group.bench_function("all_vertices().collect::<Vec<_>>().len()", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
let vertices: Vec<_> = snapshot.all_vertices().collect();
black_box(vertices.len())
})
});
group.finish();
}
fn bench_concurrent_reads(c: &mut Criterion) {
let graph = Arc::new(create_benchmark_graph(10_000, 50_000));
let mut group = c.benchmark_group("perf: concurrent_reads");
group.bench_function("single_thread: 1000 reads", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
for i in 0..1000 {
black_box(snapshot.get_vertex(VertexId(i % 10_000)));
}
})
});
group.bench_function("4_threads: 250 reads each", |b| {
b.iter(|| {
let handles: Vec<_> = (0..4)
.map(|thread_id| {
let graph_clone = Arc::clone(&graph);
thread::spawn(move || {
let snapshot = graph_clone.snapshot();
for i in 0..250 {
let vertex_id = (thread_id * 250 + i) % 10_000;
black_box(snapshot.get_vertex(VertexId(vertex_id as u64)));
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
})
});
group.bench_function("4_threads: v().count() each", |b| {
b.iter(|| {
let handles: Vec<_> = (0..4)
.map(|_| {
let graph_clone = Arc::clone(&graph);
thread::spawn(move || {
let snapshot = graph_clone.snapshot();
let g = snapshot.gremlin();
black_box(g.v().count())
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
})
});
group.finish();
}
fn bench_memory_pressure(c: &mut Criterion) {
let mut group = c.benchmark_group("perf: memory_pressure");
for size in [1_000, 10_000, 50_000] {
let graph = create_benchmark_graph(size, size * 5);
group.throughput(Throughput::Elements(size as u64));
group.bench_with_input(BenchmarkId::new("v().count()", size), &size, |b, _| {
b.iter(|| {
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
black_box(g.v().count())
})
});
group.bench_with_input(
BenchmarkId::new("v().out().out().dedup().count()", size),
&size,
|b, _| {
b.iter(|| {
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
black_box(g.v().out().out().dedup().count())
})
},
);
}
group.finish();
}
fn bench_key_metrics(c: &mut Criterion) {
let graph = create_benchmark_graph(10_000, 50_000);
let mut group = c.benchmark_group("perf: key_metrics");
group.bench_function("v().count() [10K vertices]", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
black_box(g.v().count())
})
});
group.bench_function("v().has_label().count() [10K vertices]", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
black_box(g.v().has_label("person").count())
})
});
group.bench_function(
"v().out().out().dedup().count() [10K vertices, 50K edges]",
|b| {
b.iter(|| {
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
black_box(g.v().out().out().dedup().count())
})
},
);
group.bench_function("vertex_count() [direct]", |b| {
b.iter(|| {
let snapshot = graph.snapshot();
black_box(snapshot.vertex_count())
})
});
group.finish();
}
criterion_group!(step_scaling, bench_step_count_scaling,);
criterion_group!(count_optimization, bench_count_vs_direct,);
criterion_group!(label_comparison, bench_has_label_scaling,);
criterion_group!(
navigation,
bench_navigation_step_overhead,
bench_iterator_boxing,
);
criterion_group!(concurrency, bench_concurrent_reads,);
criterion_group!(memory, bench_memory_pressure,);
criterion_group!(key_metrics, bench_key_metrics,);
criterion_main!(
step_scaling,
count_optimization,
label_comparison,
navigation,
concurrency,
memory,
key_metrics
);