use std::fs;
use std::io::Cursor;
use std::path::PathBuf;
const L0: horon::quant::SemLayout = horon::quant::SemLayout { dims: 0, quantized: false, gacl: false };
use criterion::{
black_box, criterion_group, criterion_main,
Criterion, BenchmarkId, Throughput,
};
use horon::{Horon, HoronConfig};
use horon::format::*;
use horon::snapshot::NodeEntry;
use horon::wal::{WalEntry, WalPayload};
use tempfile::NamedTempFile;
fn temp_path() -> PathBuf {
NamedTempFile::new().unwrap().into_temp_path().to_path_buf()
}
fn config_raw() -> HoronConfig {
HoronConfig {
dimension: 4,
semantic_dims: 0,
compression: false,
auto_compact_threshold: 0,
..Default::default()
}
}
fn config_zstd() -> HoronConfig {
HoronConfig {
dimension: 4,
semantic_dims: 0,
compression: true,
auto_compact_threshold: 0,
..Default::default()
}
}
fn prepopulate(n: usize, config: HoronConfig) -> PathBuf {
let path = temp_path();
let gf = Horon::open_with_config(&path, config).unwrap();
for i in 0..n {
gf.put(&format!("/node_{}", i), format!("data_{}", i).as_bytes()).unwrap();
}
gf.flush().unwrap();
drop(gf);
path
}
fn prepopulate_compacted(n: usize, config: HoronConfig) -> PathBuf {
let path = temp_path();
let gf = Horon::open_with_config(&path, config).unwrap();
for i in 0..n {
gf.put(&format!("/node_{}", i), format!("data_{}", i).as_bytes()).unwrap();
}
gf.compact().unwrap();
drop(gf);
path
}
fn bench_wal_serialization(c: &mut Criterion) {
let mut group = c.benchmark_group("wal_serialization");
let insert_entry = WalEntry {
seq: 1,
op: OP_INSERT,
key: "/test/node".to_string(),
payload: WalPayload::Insert(NodeEntry {
key: "/test/node".to_string(),
data: b"hello world, this is some payload data".to_vec(),
metadata: vec![
("author".to_string(), "alice".to_string()),
("type".to_string(), "text".to_string()),
],
semantic_coords: vec![],
}),
};
group.bench_function("write_insert_entry", |b| {
b.iter(|| {
let mut buf = Vec::with_capacity(256);
insert_entry.write_to(&mut buf, &L0).unwrap();
black_box(buf.len());
})
});
let delete_entry = WalEntry {
seq: 2, op: OP_DELETE, key: "/test/node".to_string(),
payload: WalPayload::Delete,
};
group.bench_function("write_delete_entry", |b| {
b.iter(|| {
let mut buf = Vec::with_capacity(64);
delete_entry.write_to(&mut buf, &L0).unwrap();
black_box(buf.len());
})
});
let meta_entry = WalEntry {
seq: 3, op: OP_SET_META, key: "/test/node".to_string(),
payload: WalPayload::SetMeta {
meta_key: "author".to_string(),
meta_value: "alice".to_string(),
},
};
group.bench_function("write_set_meta_entry", |b| {
b.iter(|| {
let mut buf = Vec::with_capacity(128);
meta_entry.write_to(&mut buf, &L0).unwrap();
black_box(buf.len());
})
});
let sem_entry = WalEntry {
seq: 1,
op: OP_INSERT,
key: "/semantic/node".to_string(),
payload: WalPayload::Insert(NodeEntry {
key: "/semantic/node".to_string(),
data: b"payload".to_vec(),
metadata: vec![],
semantic_coords: vec![0u8; 256], }),
};
group.bench_function("write_insert_16sem", |b| {
b.iter(|| {
let mut buf = Vec::with_capacity(512);
sem_entry.write_to(&mut buf, &L0).unwrap();
black_box(buf.len());
})
});
let entries: Vec<WalEntry> = (0..100).map(|i| WalEntry {
seq: i,
op: OP_INSERT,
key: format!("/batch/node_{}", i),
payload: WalPayload::Insert(NodeEntry {
key: format!("/batch/node_{}", i),
data: format!("data for node {}", i).into_bytes(),
metadata: vec![],
semantic_coords: vec![],
}),
}).collect();
group.throughput(Throughput::Elements(100));
group.bench_function("write_100_inserts", |b| {
b.iter(|| {
let mut buf = Vec::with_capacity(8192);
for e in &entries {
e.write_to(&mut buf, &L0).unwrap();
}
black_box(buf.len());
})
});
let mut serialized = Vec::new();
for e in &entries {
e.write_to(&mut serialized, &L0).unwrap();
}
group.bench_function("read_100_inserts", |b| {
b.iter(|| {
let mut cursor = Cursor::new(&serialized);
for _ in 0..100 {
let e = WalEntry::read_from(&mut cursor, &L0).unwrap().unwrap();
black_box(&e);
}
})
});
group.finish();
}
fn bench_snapshot_serialization(c: &mut Criterion) {
use horon::snapshot;
let mut group = c.benchmark_group("snapshot_serialization");
for &n in &[10, 50, 100, 500] {
let entries: Vec<NodeEntry> = (0..n).map(|i| NodeEntry {
key: format!("/node_{}", i),
data: format!("data for node {} with some content", i).into_bytes(),
metadata: vec![("idx".to_string(), i.to_string())],
semantic_coords: vec![],
}).collect();
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(
BenchmarkId::new("write_raw", n),
&n,
|b, _| {
b.iter(|| {
let mut buf = Vec::with_capacity(n * 100);
snapshot::write_snapshot(&mut buf, &entries, false, true, &L0).unwrap();
black_box(buf.len());
})
},
);
group.bench_with_input(
BenchmarkId::new("write_zstd", n),
&n,
|b, _| {
b.iter(|| {
let mut buf = Vec::with_capacity(n * 100);
snapshot::write_snapshot(&mut buf, &entries, true, true, &L0).unwrap();
black_box(buf.len());
})
},
);
let mut raw_buf = Vec::new();
snapshot::write_snapshot(&mut raw_buf, &entries, false, true, &L0).unwrap();
group.bench_with_input(
BenchmarkId::new("read_raw", n),
&n,
|b, _| {
b.iter(|| {
let mut cursor = Cursor::new(&raw_buf);
let parsed = snapshot::read_snapshot(&mut cursor, false, &L0, true).unwrap();
black_box(parsed.len());
})
},
);
let mut zstd_buf = Vec::new();
snapshot::write_snapshot(&mut zstd_buf, &entries, true, true, &L0).unwrap();
group.bench_with_input(
BenchmarkId::new("read_zstd", n),
&n,
|b, _| {
b.iter(|| {
let mut cursor = Cursor::new(&zstd_buf);
let parsed = snapshot::read_snapshot(&mut cursor, true, &L0, true).unwrap();
black_box(parsed.len());
})
},
);
}
group.finish();
}
fn bench_e2e_insert(c: &mut Criterion) {
let mut group = c.benchmark_group("e2e_insert");
group.sample_size(10);
for &n in &[10, 25, 50] {
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(
BenchmarkId::new("full_stack", n),
&n,
|b, &n| {
b.iter_with_setup(
|| {
let path = temp_path();
Horon::open_with_config(&path, config_raw()).unwrap()
},
|gf| {
for i in 0..n {
gf.put(&format!("/n{}", i), b"data").unwrap();
}
black_box(&gf);
},
);
},
);
}
for &base in &[10, 50, 100] {
let path = temp_path();
let gf = Horon::open_with_config(&path, config_raw()).unwrap();
for i in 0..base {
gf.put(&format!("/pre_{}", i), b"x").unwrap();
}
let mut idx = base;
group.bench_with_input(
BenchmarkId::new("marginal_at", base),
&base,
|b, _| {
b.iter(|| {
gf.put(&format!("/m{}", idx), b"x").unwrap();
idx += 1;
black_box(&gf);
})
},
);
}
group.finish();
}
fn bench_cold_start(c: &mut Criterion) {
let mut group = c.benchmark_group("cold_start");
group.sample_size(10);
for &n in &[10, 50, 100] {
let wal_path = prepopulate(n, config_raw());
group.bench_with_input(
BenchmarkId::new("wal_replay_raw", n),
&n,
|b, _| {
b.iter(|| {
let gf = Horon::open(black_box(&wal_path)).unwrap();
black_box(gf.len());
})
},
);
let snap_path = prepopulate_compacted(n, config_raw());
group.bench_with_input(
BenchmarkId::new("snapshot_raw", n),
&n,
|b, _| {
b.iter(|| {
let gf = Horon::open(black_box(&snap_path)).unwrap();
black_box(gf.len());
})
},
);
let zstd_path = prepopulate_compacted(n, config_zstd());
group.bench_with_input(
BenchmarkId::new("snapshot_zstd", n),
&n,
|b, _| {
b.iter(|| {
let gf = Horon::open(black_box(&zstd_path)).unwrap();
black_box(gf.len());
})
},
);
}
group.finish();
}
fn bench_compaction(c: &mut Criterion) {
let mut group = c.benchmark_group("compaction");
group.sample_size(10);
for &n in &[10, 50, 100] {
group.bench_with_input(
BenchmarkId::new("raw", n),
&n,
|b, &n| {
b.iter_with_setup(
|| {
let path = temp_path();
let gf = Horon::open_with_config(&path, config_raw()).unwrap();
for i in 0..n {
gf.put(&format!("/n{}", i), format!("d{}", i).as_bytes()).unwrap();
}
gf
},
|gf| {
gf.compact().unwrap();
black_box(&gf);
},
);
},
);
group.bench_with_input(
BenchmarkId::new("zstd", n),
&n,
|b, &n| {
b.iter_with_setup(
|| {
let path = temp_path();
let gf = Horon::open_with_config(&path, config_zstd()).unwrap();
for i in 0..n {
gf.put(&format!("/n{}", i), format!("d{}", i).as_bytes()).unwrap();
}
gf
},
|gf| {
gf.compact().unwrap();
black_box(&gf);
},
);
},
);
}
group.finish();
}
fn bench_read_throughput(c: &mut Criterion) {
let mut group = c.benchmark_group("read_throughput");
for &n in &[50, 100, 500] {
let path = temp_path();
let gf = Horon::open_with_config(&path, config_raw()).unwrap();
for i in 0..n {
gf.put(&format!("/node_{}", i), format!("data_{}", i).as_bytes()).unwrap();
}
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(
BenchmarkId::new("get", n),
&n,
|b, &n| {
b.iter(|| {
for i in 0..n {
black_box(gf.get(&format!("/node_{}", i)).unwrap());
}
})
},
);
group.bench_with_input(
BenchmarkId::new("exists", n),
&n,
|b, &n| {
b.iter(|| {
for i in 0..n {
black_box(gf.exists(&format!("/node_{}", i)));
}
})
},
);
group.bench_with_input(
BenchmarkId::new("get_meta", n),
&n,
|b, &n| {
b.iter(|| {
for i in 0..n {
black_box(gf.get_meta(&format!("/node_{}", i)).unwrap());
}
})
},
);
}
group.finish();
}
fn bench_spatial(c: &mut Criterion) {
use g_math::fixed_point::FixedPoint;
let mut group = c.benchmark_group("spatial");
group.sample_size(20);
let origin = [FixedPoint::ZERO; 4];
let off_center = [
FixedPoint::from_f64(0.3),
FixedPoint::from_f64(0.2),
FixedPoint::from_f64(-0.1),
FixedPoint::from_f64(0.1),
];
for &n in &[20, 50, 100] {
let path = temp_path();
let gf = Horon::open_with_config(&path, config_raw()).unwrap();
for i in 0..n {
gf.put(&format!("/n{}", i), b"d").unwrap();
}
group.bench_with_input(
BenchmarkId::new("nearest_origin", n),
&n,
|b, _| b.iter(|| black_box(gf.nearest(&origin).unwrap())),
);
group.bench_with_input(
BenchmarkId::new("nearest_off_center", n),
&n,
|b, _| b.iter(|| black_box(gf.nearest(&off_center).unwrap())),
);
group.bench_with_input(
BenchmarkId::new("neighbors_k3", n),
&n,
|b, _| b.iter(|| black_box(gf.neighbors("/n0", 3).unwrap())),
);
}
group.finish();
}
fn bench_gacl(c: &mut Criterion) {
use horon::gacl::{NodeAccessBands, AccessBand, Credentials};
use g_math::fixed_point::FixedPoint;
let mut group = c.benchmark_group("gacl");
let band = AccessBand::from_f64(0.3, 0.8);
let cred = FixedPoint::from_f64(0.5);
group.bench_function("band_permits", |b| {
b.iter(|| black_box(band.permits(black_box(cred))))
});
let creds = Credentials::root();
let bands = NodeAccessBands {
read: AccessBand::from_f64(0.5, 1.0),
write: AccessBand::from_f64(0.7, 1.0),
exec: AccessBand::from_f64(0.0, 1.0),
domain: AccessBand::from_f64(0.3, 0.8),
classification: AccessBand::from_f64(0.5, 1.0),
identity: AccessBand::open(),
};
group.bench_function("can_access_6_bands", |b| {
b.iter(|| black_box(creds.can_access(black_box(&bands))))
});
let parent = NodeAccessBands {
read: AccessBand::from_f64(0.3, 0.9),
write: AccessBand::from_f64(0.5, 1.0),
exec: AccessBand::open(),
domain: AccessBand::from_f64(0.2, 0.8),
classification: AccessBand::open(),
identity: AccessBand::open(),
};
let child = NodeAccessBands::public();
group.bench_function("narrow_inheritance", |b| {
b.iter(|| black_box(child.narrow(black_box(&parent))))
});
group.bench_function("to_semantic_bytes_16", |b| {
b.iter(|| black_box(bands.to_semantic_bytes(16)))
});
let bytes = bands.to_semantic_bytes(16);
group.bench_function("from_semantic_bytes_16", |b| {
b.iter(|| black_box(NodeAccessBands::from_semantic_bytes(black_box(&bytes))))
});
let groups: Vec<Credentials> = (0..5).map(|i| {
let v = (i as f64 + 1.0) / 6.0;
Credentials {
read: FixedPoint::from_f64(v),
write: FixedPoint::from_f64(v * 0.8),
exec: FixedPoint::from_f64(v * 0.5),
domain: FixedPoint::from_f64(v * 0.9),
classification: FixedPoint::from_f64(v * 0.7),
identity: FixedPoint::from_f64(0.42),
}
}).collect();
group.bench_function("from_groups_5", |b| {
b.iter(|| black_box(Credentials::from_groups(black_box(&groups))))
});
group.finish();
}
fn bench_file_sizes(c: &mut Criterion) {
let mut group = c.benchmark_group("file_size");
group.sample_size(10);
for &n in &[50, 100] {
let raw_path = prepopulate_compacted(n, config_raw());
let zstd_path = prepopulate_compacted(n, config_zstd());
let raw_size = fs::metadata(&raw_path).unwrap().len();
let zstd_size = fs::metadata(&zstd_path).unwrap().len();
eprintln!(
" {} nodes: raw={} bytes, zstd={} bytes, ratio={:.1}x",
n, raw_size, zstd_size,
raw_size as f64 / zstd_size as f64
);
group.bench_with_input(
BenchmarkId::new("open_raw", n),
&n,
|b, _| b.iter(|| {
let gf = Horon::open(black_box(&raw_path)).unwrap();
black_box(gf.len());
}),
);
group.bench_with_input(
BenchmarkId::new("open_zstd", n),
&n,
|b, _| b.iter(|| {
let gf = Horon::open(black_box(&zstd_path)).unwrap();
black_box(gf.len());
}),
);
}
group.finish();
}
criterion_group!(
benches,
bench_wal_serialization,
bench_snapshot_serialization,
bench_e2e_insert,
bench_cold_start,
bench_compaction,
bench_read_throughput,
bench_spatial,
bench_gacl,
bench_file_sizes,
);
criterion_main!(benches);