use std::collections::BTreeMap;
use std::fs;
use std::hint::black_box;
use std::path::PathBuf;
use std::rc::Rc;
use criterion::{criterion_group, criterion_main, Criterion};
use harn_hostlib::{
code_index::CodeIndexCapability, BuiltinRegistry, HostlibCapability, RegisteredBuiltin,
};
use harn_vm::VmValue;
const FILE_COUNT: usize = 200;
const LINES_PER_FILE: usize = 500;
fn dict(entries: &[(&str, VmValue)]) -> VmValue {
let mut map: BTreeMap<String, VmValue> = BTreeMap::new();
for (k, v) in entries {
map.insert((*k).to_string(), v.clone());
}
VmValue::Dict(Rc::new(map))
}
fn call(registry: &BuiltinRegistry, name: &str, payload: VmValue) -> VmValue {
let entry: &RegisteredBuiltin = registry
.find(name)
.unwrap_or_else(|| panic!("builtin {name} not registered"));
(entry.handler)(&[payload]).unwrap_or_else(|err| panic!("builtin {name} failed: {err:?}"))
}
fn build_corpus() -> (tempfile::TempDir, PathBuf) {
let dir = tempfile::tempdir().expect("create tempdir");
let root = dir.path().to_path_buf();
fs::create_dir_all(root.join("src")).expect("mkdir src");
for i in 0..FILE_COUNT {
let mut src = String::with_capacity(LINES_PER_FILE * 32);
src.push_str(&format!("pub fn fn_{i}() {{\n"));
if i > 0 {
src.push_str(&format!(" fn_{prev}();\n", prev = i - 1));
}
for j in 0..LINES_PER_FILE {
src.push_str(&format!(" let v_{j} = {j};\n"));
}
src.push_str("}\n");
for j in 0..4 {
src.push_str(&format!("pub fn helper_{i}_{j}() {{}}\n"));
}
fs::write(root.join(format!("src/m{i}.rs")), src).expect("write file");
}
(dir, root)
}
fn cypher_query(reg: &BuiltinRegistry, query: &str) {
let payload = dict(&[("query", VmValue::String(Rc::from(query)))]);
let response = call(reg, "hostlib_code_index_cypher", payload);
black_box(response);
}
fn bench_cypher(c: &mut Criterion) {
let (_dir, root) = build_corpus();
let cap = CodeIndexCapability::new();
let mut reg = BuiltinRegistry::new();
cap.register_builtins(&mut reg);
call(
®,
"hostlib_code_index_rebuild",
dict(&[(
"root",
VmValue::String(Rc::from(root.to_string_lossy().as_ref())),
)]),
);
let mut group = c.benchmark_group("code_index_cypher");
group.sample_size(20);
group.bench_function("direct_by_name", |b| {
b.iter(|| {
cypher_query(
®,
"MATCH (f:Function {name: 'fn_42'}) RETURN f.path AS path",
);
});
});
group.bench_function("var_length_called_by", |b| {
b.iter(|| {
cypher_query(
®,
"MATCH (f:Function {name: 'fn_10'})<-[:CALLS*1..2]-(c:CallSite) RETURN c.path AS path",
);
});
});
group.bench_function("label_scan_with_where", |b| {
b.iter(|| {
cypher_query(
®,
"MATCH (f:Function) WHERE f.name = 'fn_99' RETURN f.path AS path",
);
});
});
group.finish();
}
criterion_group!(benches, bench_cypher);
criterion_main!(benches);