use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn bench_parse(c: &mut Criterion) {
let inputs = [
("trivial", "1 + 1"),
("let_chain", "let a = 1; b = 2; c = 3; in a + b + c"),
(
"nested_attrs",
"{ a = 1; b = { c = 2; d = { e = 3; f = 4; }; }; }",
),
(
"long_list",
"[ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ]",
),
(
"complex_flake",
r#"{
description = "test flake";
outputs = { self, nixpkgs }: {
packages.default = nixpkgs.hello;
devShells.default = nixpkgs.mkShell { buildInputs = [ nixpkgs.git ]; };
};
}"#,
),
];
let mut group = c.benchmark_group("parse");
for (name, input) in &inputs {
group.bench_function(*name, |b| {
b.iter(|| {
let parsed = rnix::Root::parse(black_box(input));
black_box(parsed);
});
});
}
group.finish();
}
fn bench_eval(c: &mut Criterion) {
let inputs = [
("arith", "1 + 2 * 3 - 4"),
("let_5", "let a = 1; b = a + 1; c = b + 1; d = c + 1; e = d + 1; in e"),
("rec_fib_small", "let f = n: if n < 2 then n else f (n - 1) + f (n - 2); in f 10"),
("list_map_20", "builtins.map (x: x * x) (builtins.genList (x: x) 20)"),
(
"list_foldl_100",
"builtins.foldl' (acc: x: acc + x) 0 (builtins.genList (x: x) 100)",
),
(
"attrset_merge",
"{ a = 1; b = 2; c = 3; } // { b = 20; d = 4; } // { e = 5; }",
),
];
let mut group = c.benchmark_group("eval");
for (name, input) in &inputs {
group.bench_function(*name, |b| {
b.iter(|| {
let result = sui_eval::eval(black_box(input));
black_box(result.expect("eval ok"));
});
});
}
group.finish();
}
fn bench_to_json(c: &mut Criterion) {
let value = sui_eval::eval(
"{ a = 1; b = [ 1 2 3 4 5 ]; c = { d = \"s\"; e = [ { f = 1; } { f = 2; } ]; }; }",
)
.unwrap();
c.bench_function("to_json_medium", |b| {
b.iter(|| {
let j = black_box(&value).to_json();
black_box(j);
});
});
}
fn bench_realistic(c: &mut Criterion) {
let big_attrset = {
let mut s = String::from("let attrs = { ");
for i in 0..50 {
s.push_str(&format!("key_{i:02} = {i}; "));
}
s.push_str("}; in attrs.key_00 + attrs.key_25 + attrs.key_49");
s
};
let overlay_then_access = {
let mut base = String::from("let base = { ");
for i in 0..20 {
base.push_str(&format!("a{i:02} = {i}; "));
}
base.push_str("}; ");
let mut overlay = String::from("overlay = { ");
for i in 20..30 {
overlay.push_str(&format!("a{i:02} = {i}; "));
}
overlay.push_str("}; ");
let mut final_layer = String::from("final = { ");
for i in 30..35 {
final_layer.push_str(&format!("a{i:02} = {i}; "));
}
final_layer.push_str("}; ");
format!(
"{base}{overlay}{final_layer}merged = base // overlay // final; in \
builtins.length (builtins.attrNames merged) + merged.a00 + \
merged.a25 + merged.a32"
)
};
let deep_let = {
let mut s = String::from("let x_00 = 0; ");
for i in 1..100 {
s.push_str(&format!("x_{i:02} = x_{:02} + 1; ", i - 1));
}
s.push_str("in x_99");
s
};
let mut group = c.benchmark_group("realistic");
group.bench_function("attrset_50_point_access", |b| {
b.iter(|| {
let result = sui_eval::eval(black_box(&big_attrset));
black_box(result.expect("eval ok"));
});
});
group.bench_function("overlay_merge_iter_access", |b| {
b.iter(|| {
let result = sui_eval::eval(black_box(&overlay_then_access));
black_box(result.expect("eval ok"));
});
});
group.bench_function("deep_let_chain_100", |b| {
b.iter(|| {
let result = sui_eval::eval(black_box(&deep_let));
black_box(result.expect("eval ok"));
});
});
group.finish();
}
fn bench_attrs(c: &mut Criterion) {
let small_access = r#"
let attrs = { a = 1; b = 2; c = 3; d = 4; e = 5; };
in attrs.a + attrs.b + attrs.c + attrs.d + attrs.e
"#;
let large_sparse_access = {
let mut s = String::from("let attrs = { ");
for i in 0..50 {
s.push_str(&format!("k{i:02} = {i}; "));
}
s.push_str("}; in attrs.k00 + attrs.k25 + attrs.k49");
s
};
let overlay_warm_then_access = {
let mut base = String::from("let base = { ");
for i in 0..10 {
base.push_str(&format!("b{i:02} = {i}; "));
}
base.push_str("}; overlay = { ");
for i in 10..20 {
base.push_str(&format!("b{i:02} = {i}; "));
}
base.push_str("}; final = { ");
for i in 20..25 {
base.push_str(&format!("b{i:02} = {i}; "));
}
base.push_str("}; merged = base // overlay // final; in ");
base.push_str("(builtins.length (builtins.attrNames merged)) + ");
base.push_str("merged.b00 + merged.b10 + merged.b20 + merged.b05 + merged.b24");
base
};
let has_attr_probe = {
let mut s = String::from("let attrs = { ");
for i in 0..30 {
s.push_str(&format!("k{i:02} = {i}; "));
}
s.push_str("}; in builtins.hasAttr \"k00\" attrs && ");
s.push_str("builtins.hasAttr \"k15\" attrs && ");
s.push_str("builtins.hasAttr \"k29\" attrs && ");
s.push_str("!(builtins.hasAttr \"missing\" attrs)");
s
};
let mut group = c.benchmark_group("attrs");
group.bench_function("small_5_all", |b| {
b.iter(|| {
let r = sui_eval::eval(black_box(small_access));
black_box(r.expect("eval ok"));
});
});
group.bench_function("large_50_sparse_3", |b| {
b.iter(|| {
let r = sui_eval::eval(black_box(&large_sparse_access));
black_box(r.expect("eval ok"));
});
});
group.bench_function("overlay_warm_then_5", |b| {
b.iter(|| {
let r = sui_eval::eval(black_box(&overlay_warm_then_access));
black_box(r.expect("eval ok"));
});
});
group.bench_function("has_attr_30_probe_4", |b| {
b.iter(|| {
let r = sui_eval::eval(black_box(&has_attr_probe));
black_box(r.expect("eval ok"));
});
});
group.finish();
}
criterion_group!(
benches,
bench_parse,
bench_eval,
bench_to_json,
bench_realistic,
bench_attrs
);
criterion_main!(benches);