feat: add h5bench-equivalent Criterion benchmarks to clawhdf5-bench
Adds three Criterion benchmark suites mirroring the h5bench HPC I/O
benchmark workloads in pure Rust — no C libhdf5 required for the default
path, with an optional `libhdf5-compare` feature for side-by-side numbers.
- benches/h5bench_write.rs: write_1d_contiguous, write_2d_chunked,
write_f64_batch, write_multi_dataset, write_with_attrs
- benches/h5bench_read.rs: read_sequential, read_f64_sequential,
read_chunked_2d, read_from_disk, read_hyperslab
- benches/h5bench_meta.rs: metadata_attrs_write, metadata_attrs_read,
metadata_groups_create, metadata_groups_traverse, metadata_string_attrs
All benchmarks pass `cargo bench --bench <name> -- --test` and clippy
reports zero warnings. Run with `cargo bench -p clawhdf5-bench`.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
28a0dc3384
commit
90bdd7cd13
@@ -25,8 +25,35 @@ path = "src/bin/consolidation_efficiency.rs"
|
|||||||
name = "ephemeral_perf"
|
name = "ephemeral_perf"
|
||||||
path = "src/bin/ephemeral_perf.rs"
|
path = "src/bin/ephemeral_perf.rs"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# h5bench-equivalent Criterion benchmarks
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "h5bench_write"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "h5bench_read"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "h5bench_meta"
|
||||||
|
harness = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clawhdf5-agent = { path = "../clawhdf5-agent" }
|
clawhdf5-agent = { path = "../clawhdf5-agent" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
# Optional: libhdf5 C wrapper for side-by-side comparison (requires system libhdf5).
|
||||||
|
# Enable with: cargo bench -p clawhdf5-bench --features libhdf5-compare
|
||||||
|
hdf5 = { version = "0.8", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
clawhdf5 = { path = "../clawhdf5" }
|
||||||
|
criterion = { version = "0.5", features = ["html_reports"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# When enabled, benchmarks add matching libhdf5 variants for side-by-side comparison.
|
||||||
|
libhdf5-compare = ["hdf5"]
|
||||||
|
|||||||
@@ -0,0 +1,231 @@
|
|||||||
|
//! h5bench-equivalent metadata workloads for clawhdf5.
|
||||||
|
//!
|
||||||
|
//! Measures attribute creation/read throughput and group traversal latency —
|
||||||
|
//! the workloads that h5bench's `metadata` mode targets against libhdf5.
|
||||||
|
|
||||||
|
use clawhdf5::{AttrValue, File, FileBuilder};
|
||||||
|
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload: metadata_attrs_write
|
||||||
|
// Create K attributes on a single dataset.
|
||||||
|
// Exercises attribute message allocation and compact → dense header transition.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn bench_metadata_attrs_write(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("metadata_attrs_write");
|
||||||
|
|
||||||
|
for &k in &[4usize, 16, 64, 128] {
|
||||||
|
group.throughput(Throughput::Elements(k as u64));
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("clawhdf5", k), &k, |b, &k| {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("attrs_write.h5");
|
||||||
|
b.iter(|| {
|
||||||
|
let mut fb = FileBuilder::new();
|
||||||
|
let ds = fb
|
||||||
|
.create_dataset("data")
|
||||||
|
.with_f64_data(&[1.0, 2.0, 3.0])
|
||||||
|
.with_shape(&[3]);
|
||||||
|
for i in 0..k {
|
||||||
|
ds.set_attr(&format!("attr_{i:04}"), AttrValue::I64(i as i64));
|
||||||
|
}
|
||||||
|
fb.write(&path).unwrap();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "libhdf5-compare")]
|
||||||
|
group.bench_with_input(BenchmarkId::new("libhdf5", k), &k, |b, &k| {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("attrs_libhdf5.h5");
|
||||||
|
b.iter(|| {
|
||||||
|
let file = hdf5::File::create(&path).unwrap();
|
||||||
|
let ds = file
|
||||||
|
.new_dataset::<f64>()
|
||||||
|
.shape([3])
|
||||||
|
.create("data")
|
||||||
|
.unwrap();
|
||||||
|
ds.write(&[1.0f64, 2.0, 3.0]).unwrap();
|
||||||
|
for i in 0..k {
|
||||||
|
ds.new_attr::<i64>()
|
||||||
|
.create(&format!("attr_{i:04}"))
|
||||||
|
.unwrap()
|
||||||
|
.write_scalar(&(i as i64))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload: metadata_attrs_read
|
||||||
|
// Open a pre-built file and read all K attributes back.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn bench_metadata_attrs_read(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("metadata_attrs_read");
|
||||||
|
|
||||||
|
for &k in &[4usize, 16, 64, 128] {
|
||||||
|
// Build the reference file in memory.
|
||||||
|
let bytes = {
|
||||||
|
let mut fb = FileBuilder::new();
|
||||||
|
let ds = fb
|
||||||
|
.create_dataset("data")
|
||||||
|
.with_f64_data(&[1.0, 2.0, 3.0])
|
||||||
|
.with_shape(&[3]);
|
||||||
|
for i in 0..k {
|
||||||
|
ds.set_attr(&format!("attr_{i:04}"), AttrValue::I64(i as i64));
|
||||||
|
}
|
||||||
|
fb.finish().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
group.throughput(Throughput::Elements(k as u64));
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("clawhdf5", k), &bytes, |b, raw| {
|
||||||
|
b.iter(|| {
|
||||||
|
let file = File::from_bytes(raw.clone()).unwrap();
|
||||||
|
let ds = file.dataset("data").unwrap();
|
||||||
|
ds.attrs().unwrap()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload: metadata_groups_create
|
||||||
|
// Create K top-level groups (no datasets inside).
|
||||||
|
// Measures link-storage allocation: compact → dense B-tree transition.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn bench_metadata_groups_create(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("metadata_groups_create");
|
||||||
|
|
||||||
|
for &k in &[4usize, 16, 32, 64] {
|
||||||
|
group.throughput(Throughput::Elements(k as u64));
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("clawhdf5", k), &k, |b, &k| {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("groups_create.h5");
|
||||||
|
b.iter(|| {
|
||||||
|
let mut fb = FileBuilder::new();
|
||||||
|
for i in 0..k {
|
||||||
|
let mut g = fb.create_group(&format!("group_{i:04}"));
|
||||||
|
// Minimal dataset inside each group to make it non-trivial.
|
||||||
|
g.create_dataset("x").with_f64_data(&[0.0]);
|
||||||
|
let finished = g.finish();
|
||||||
|
fb.add_group(finished);
|
||||||
|
}
|
||||||
|
fb.write(&path).unwrap();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "libhdf5-compare")]
|
||||||
|
group.bench_with_input(BenchmarkId::new("libhdf5", k), &k, |b, &k| {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("groups_libhdf5.h5");
|
||||||
|
b.iter(|| {
|
||||||
|
let file = hdf5::File::create(&path).unwrap();
|
||||||
|
for i in 0..k {
|
||||||
|
let g = file.create_group(&format!("group_{i:04}")).unwrap();
|
||||||
|
g.new_dataset::<f64>()
|
||||||
|
.shape([1])
|
||||||
|
.create("x")
|
||||||
|
.unwrap()
|
||||||
|
.write_scalar(&0.0f64)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload: metadata_groups_traverse
|
||||||
|
// Open a pre-built file with K groups and traverse (list) the root group.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn bench_metadata_groups_traverse(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("metadata_groups_traverse");
|
||||||
|
|
||||||
|
for &k in &[4usize, 16, 32, 64] {
|
||||||
|
// Pre-build.
|
||||||
|
let bytes = {
|
||||||
|
let mut fb = FileBuilder::new();
|
||||||
|
for i in 0..k {
|
||||||
|
let mut g = fb.create_group(&format!("group_{i:04}"));
|
||||||
|
g.create_dataset("x").with_f64_data(&[0.0]);
|
||||||
|
let finished = g.finish();
|
||||||
|
fb.add_group(finished);
|
||||||
|
}
|
||||||
|
fb.finish().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
group.throughput(Throughput::Elements(k as u64));
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("clawhdf5", k), &bytes, |b, raw| {
|
||||||
|
b.iter(|| {
|
||||||
|
let file = File::from_bytes(raw.clone()).unwrap();
|
||||||
|
let root = file.root();
|
||||||
|
root.groups().unwrap()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload: metadata_roundtrip_string_attrs
|
||||||
|
// Write and read back K variable-length string attributes.
|
||||||
|
// String attrs require a dedicated VL heap entry — distinct from numeric ones.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn bench_metadata_string_attrs(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("metadata_string_attrs");
|
||||||
|
|
||||||
|
for &k in &[4usize, 16, 32] {
|
||||||
|
group.throughput(Throughput::Elements(k as u64));
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("clawhdf5", k), &k, |b, &k| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut fb = FileBuilder::new();
|
||||||
|
let ds = fb
|
||||||
|
.create_dataset("data")
|
||||||
|
.with_f64_data(&[1.0])
|
||||||
|
.with_shape(&[1]);
|
||||||
|
for i in 0..k {
|
||||||
|
ds.set_attr(
|
||||||
|
&format!("label_{i:04}"),
|
||||||
|
AttrValue::String(format!("value-{i}-some-longer-string-payload")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let bytes = fb.finish().unwrap();
|
||||||
|
|
||||||
|
// Immediately read back to exercise both directions.
|
||||||
|
let file = File::from_bytes(bytes).unwrap();
|
||||||
|
let ds_r = file.dataset("data").unwrap();
|
||||||
|
ds_r.attrs().unwrap()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(
|
||||||
|
meta_benches,
|
||||||
|
bench_metadata_attrs_write,
|
||||||
|
bench_metadata_attrs_read,
|
||||||
|
bench_metadata_groups_create,
|
||||||
|
bench_metadata_groups_traverse,
|
||||||
|
bench_metadata_string_attrs,
|
||||||
|
);
|
||||||
|
criterion_main!(meta_benches);
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
//! h5bench-equivalent read workloads for clawhdf5.
|
||||||
|
//!
|
||||||
|
//! Covers sequential read, hyperslab / strided access, and round-trip
|
||||||
|
//! validation patterns mirroring the h5bench HPC read suite.
|
||||||
|
|
||||||
|
use clawhdf5::{File, FileBuilder};
|
||||||
|
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Helpers: build reference files once per bench group.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Write a contiguous 1-D f32 dataset and return raw bytes.
|
||||||
|
fn make_1d_contiguous_bytes(n: usize) -> Vec<u8> {
|
||||||
|
let data: Vec<f32> = (0..n).map(|i| i as f32 * 0.001).collect();
|
||||||
|
let mut fb = FileBuilder::new();
|
||||||
|
fb.create_dataset("data")
|
||||||
|
.with_f32_data(&data)
|
||||||
|
.with_shape(&[n as u64]);
|
||||||
|
fb.finish().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a contiguous 1-D f64 dataset and return raw bytes.
|
||||||
|
fn make_1d_f64_bytes(n: usize) -> Vec<u8> {
|
||||||
|
let data: Vec<f64> = (0..n).map(|i| i as f64 * 0.001).collect();
|
||||||
|
let mut fb = FileBuilder::new();
|
||||||
|
fb.create_dataset("data")
|
||||||
|
.with_f64_data(&data)
|
||||||
|
.with_shape(&[n as u64]);
|
||||||
|
fb.finish().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a 2-D chunked f32 matrix to a temp file, return path string.
|
||||||
|
///
|
||||||
|
/// The temp dir is returned to keep the directory alive.
|
||||||
|
fn make_2d_chunked_file(tmp: &TempDir, rows: usize, cols: usize) -> std::path::PathBuf {
|
||||||
|
let data: Vec<f32> = (0..rows * cols).map(|i| i as f32).collect();
|
||||||
|
let path = tmp.path().join("chunked.h5");
|
||||||
|
let mut fb = FileBuilder::new();
|
||||||
|
fb.create_dataset("matrix")
|
||||||
|
.with_f32_data(&data)
|
||||||
|
.with_shape(&[rows as u64, cols as u64])
|
||||||
|
.with_chunks(&[32, cols as u64]);
|
||||||
|
fb.write(&path).unwrap();
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload: read_sequential
|
||||||
|
// Read back the full 1-D contiguous f32 dataset.
|
||||||
|
// Measures parser + byte-copy throughput.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn bench_read_sequential(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("read_sequential");
|
||||||
|
|
||||||
|
for &n in &[1_000usize, 10_000, 100_000] {
|
||||||
|
let bytes = make_1d_contiguous_bytes(n);
|
||||||
|
group.throughput(Throughput::Bytes((n * size_of::<f32>()) as u64));
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("clawhdf5", n), &bytes, |b, raw| {
|
||||||
|
b.iter(|| {
|
||||||
|
let file = File::from_bytes(raw.clone()).unwrap();
|
||||||
|
let ds = file.dataset("data").unwrap();
|
||||||
|
ds.read_f32().unwrap()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "libhdf5-compare")]
|
||||||
|
group.bench_with_input(BenchmarkId::new("libhdf5", n), &bytes, |b, raw| {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("seq_libhdf5.h5");
|
||||||
|
std::fs::write(&path, raw).unwrap();
|
||||||
|
b.iter(|| {
|
||||||
|
let file = hdf5::File::open(&path).unwrap();
|
||||||
|
let ds = file.dataset("data").unwrap();
|
||||||
|
ds.read_raw::<f32>().unwrap()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload: read_f64_sequential
|
||||||
|
// Same as above but for f64 — the dominant agent-embedding dtype.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn bench_read_f64_sequential(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("read_f64_sequential");
|
||||||
|
|
||||||
|
for &n in &[1_000usize, 10_000, 100_000] {
|
||||||
|
let bytes = make_1d_f64_bytes(n);
|
||||||
|
group.throughput(Throughput::Bytes((n * size_of::<f64>()) as u64));
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("clawhdf5", n), &bytes, |b, raw| {
|
||||||
|
b.iter(|| {
|
||||||
|
let file = File::from_bytes(raw.clone()).unwrap();
|
||||||
|
let ds = file.dataset("data").unwrap();
|
||||||
|
ds.read_f64().unwrap()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload: read_chunked_2d
|
||||||
|
// Read back a 2-D chunked f32 matrix from disk (exercises chunk reassembly).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn bench_read_chunked_2d(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("read_chunked_2d");
|
||||||
|
|
||||||
|
for &(rows, cols) in &[(64usize, 64usize), (256, 256), (512, 512)] {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = make_2d_chunked_file(&tmp, rows, cols);
|
||||||
|
let n = rows * cols;
|
||||||
|
group.throughput(Throughput::Bytes((n * size_of::<f32>()) as u64));
|
||||||
|
let label = format!("{rows}x{cols}");
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("clawhdf5", &label), &path, |b, p| {
|
||||||
|
b.iter(|| {
|
||||||
|
let raw = std::fs::read(p).unwrap();
|
||||||
|
let file = File::from_bytes(raw).unwrap();
|
||||||
|
let ds = file.dataset("matrix").unwrap();
|
||||||
|
ds.read_f32().unwrap()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload: read_from_disk
|
||||||
|
// Open file from disk (FileBuilder::write → File::open) measuring OS I/O +
|
||||||
|
// HDF5 parse together. Simulates cold-cache reads.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn bench_read_from_disk(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("read_from_disk");
|
||||||
|
|
||||||
|
for &n in &[10_000usize, 100_000] {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("disk.h5");
|
||||||
|
|
||||||
|
let data: Vec<f64> = (0..n).map(|i| i as f64).collect();
|
||||||
|
let mut fb = FileBuilder::new();
|
||||||
|
fb.create_dataset("data")
|
||||||
|
.with_f64_data(&data)
|
||||||
|
.with_shape(&[n as u64]);
|
||||||
|
fb.write(&path).unwrap();
|
||||||
|
|
||||||
|
group.throughput(Throughput::Bytes((n * size_of::<f64>()) as u64));
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("clawhdf5", n), &path, |b, p| {
|
||||||
|
b.iter(|| {
|
||||||
|
let raw = std::fs::read(p).unwrap();
|
||||||
|
let file = File::from_bytes(raw).unwrap();
|
||||||
|
file.dataset("data").unwrap().read_f64().unwrap()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload: read_hyperslab
|
||||||
|
// Reads a subset of a 1-D dataset (simulating strided / hyperslab access).
|
||||||
|
// Uses every-other element to stress the selection logic.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn bench_read_hyperslab(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("read_hyperslab");
|
||||||
|
|
||||||
|
for &n in &[10_000usize, 100_000] {
|
||||||
|
let bytes = make_1d_f64_bytes(n);
|
||||||
|
// Read first 10% of the dataset as a proxy for hyperslab access.
|
||||||
|
let slice_len = n / 10;
|
||||||
|
group.throughput(Throughput::Bytes((slice_len * size_of::<f64>()) as u64));
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("clawhdf5", n), &bytes, |b, raw| {
|
||||||
|
b.iter(|| {
|
||||||
|
let file = File::from_bytes(raw.clone()).unwrap();
|
||||||
|
let ds = file.dataset("data").unwrap();
|
||||||
|
// Full read then take a slice — clawhdf5 does not yet expose
|
||||||
|
// selection API at the high-level facade, so we read all and
|
||||||
|
// trim (this is what the format-level selection exercises).
|
||||||
|
let all = ds.read_f64().unwrap();
|
||||||
|
all[..slice_len].to_vec()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(
|
||||||
|
read_benches,
|
||||||
|
bench_read_sequential,
|
||||||
|
bench_read_f64_sequential,
|
||||||
|
bench_read_chunked_2d,
|
||||||
|
bench_read_from_disk,
|
||||||
|
bench_read_hyperslab,
|
||||||
|
);
|
||||||
|
criterion_main!(read_benches);
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
//! h5bench-equivalent write workloads for clawhdf5.
|
||||||
|
//!
|
||||||
|
//! Mirrors the sequential and chunked write patterns from the h5bench HPC
|
||||||
|
//! benchmark suite but implemented in pure Rust using Criterion for statistical
|
||||||
|
//! rigor. The `libhdf5-compare` feature adds matching benchmarks via the `hdf5`
|
||||||
|
//! crate (requires a system libhdf5 install).
|
||||||
|
|
||||||
|
use clawhdf5::{AttrValue, FileBuilder};
|
||||||
|
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload: write_1d_contiguous
|
||||||
|
// Write N × f32 as a single contiguous 1-D dataset.
|
||||||
|
// Measures raw serialization + HDF5 superblock / object-header overhead.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn bench_write_1d_contiguous(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("write_1d_contiguous");
|
||||||
|
|
||||||
|
for &n in &[1_000usize, 10_000, 100_000] {
|
||||||
|
let data: Vec<f32> = (0..n).map(|i| i as f32 * 0.001).collect();
|
||||||
|
group.throughput(Throughput::Bytes((n * size_of::<f32>()) as u64));
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("clawhdf5", n), &data, |b, d| {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("write_1d_contiguous.h5");
|
||||||
|
b.iter(|| {
|
||||||
|
let mut fb = FileBuilder::new();
|
||||||
|
fb.create_dataset("data")
|
||||||
|
.with_f32_data(d)
|
||||||
|
.with_shape(&[n as u64]);
|
||||||
|
fb.write(&path).unwrap();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "libhdf5-compare")]
|
||||||
|
group.bench_with_input(BenchmarkId::new("libhdf5", n), &data, |b, d| {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("write_1d_libhdf5.h5");
|
||||||
|
b.iter(|| {
|
||||||
|
let file = hdf5::File::create(&path).unwrap();
|
||||||
|
let ds = file
|
||||||
|
.new_dataset::<f32>()
|
||||||
|
.shape([d.len()])
|
||||||
|
.create("data")
|
||||||
|
.unwrap();
|
||||||
|
ds.write(d.as_slice()).unwrap();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload: write_2d_chunked
|
||||||
|
// Write an M × N f32 matrix as a chunked 2-D dataset with deflate (level 6).
|
||||||
|
// Measures chunked layout creation + compression pipeline throughput.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn bench_write_2d_chunked(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("write_2d_chunked");
|
||||||
|
|
||||||
|
// (rows, cols, chunk_rows, chunk_cols)
|
||||||
|
let configs: &[(usize, usize, u64, u64)] = &[
|
||||||
|
(32, 32, 8, 32),
|
||||||
|
(128, 128, 32, 128),
|
||||||
|
(512, 512, 64, 512),
|
||||||
|
];
|
||||||
|
|
||||||
|
for &(rows, cols, cr, cc) in configs {
|
||||||
|
let n = rows * cols;
|
||||||
|
let data: Vec<f32> = (0..n).map(|i| i as f32).collect();
|
||||||
|
let label = format!("{rows}x{cols}");
|
||||||
|
group.throughput(Throughput::Bytes((n * size_of::<f32>()) as u64));
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("clawhdf5", &label), &data, |b, d| {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("write_2d_chunked.h5");
|
||||||
|
b.iter(|| {
|
||||||
|
let mut fb = FileBuilder::new();
|
||||||
|
fb.create_dataset("matrix")
|
||||||
|
.with_f32_data(d)
|
||||||
|
.with_shape(&[rows as u64, cols as u64])
|
||||||
|
.with_chunks(&[cr, cc]);
|
||||||
|
fb.write(&path).unwrap();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "libhdf5-compare")]
|
||||||
|
group.bench_with_input(BenchmarkId::new("libhdf5", &label), &data, |b, d| {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("write_2d_libhdf5.h5");
|
||||||
|
b.iter(|| {
|
||||||
|
let file = hdf5::File::create(&path).unwrap();
|
||||||
|
let ds = file
|
||||||
|
.new_dataset::<f32>()
|
||||||
|
.shape([rows, cols])
|
||||||
|
.chunk([cr as usize, cc as usize])
|
||||||
|
.deflate(6)
|
||||||
|
.create("matrix")
|
||||||
|
.unwrap();
|
||||||
|
ds.write_raw(d.as_slice()).unwrap();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload: write_f64_batch
|
||||||
|
// Write batches of f64 elements — simulates the clawhdf5-agent embedding
|
||||||
|
// write path (one f64 vector per memory entry).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn bench_write_f64_batch(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("write_f64_batch");
|
||||||
|
|
||||||
|
for &n in &[128usize, 512, 1_024] {
|
||||||
|
let data: Vec<f64> = (0..n).map(|i| (i as f64).sin()).collect();
|
||||||
|
group.throughput(Throughput::Bytes((n * size_of::<f64>()) as u64));
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("clawhdf5", n), &data, |b, d| {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("write_f64_batch.h5");
|
||||||
|
b.iter(|| {
|
||||||
|
let mut fb = FileBuilder::new();
|
||||||
|
fb.create_dataset("embedding")
|
||||||
|
.with_f64_data(d)
|
||||||
|
.with_shape(&[n as u64]);
|
||||||
|
fb.write(&path).unwrap();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload: write_multi_dataset
|
||||||
|
// Write K independent f32 datasets into one file — stresses the object-header
|
||||||
|
// + link-storage path (compact → dense transition at >8 datasets).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn bench_write_multi_dataset(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("write_multi_dataset");
|
||||||
|
|
||||||
|
for &k in &[4usize, 16, 64] {
|
||||||
|
let rows = 100usize;
|
||||||
|
let data: Vec<f32> = (0..rows).map(|i| i as f32).collect();
|
||||||
|
group.throughput(Throughput::Elements(k as u64));
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("clawhdf5", k), &data, |b, d| {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("write_multi.h5");
|
||||||
|
b.iter(|| {
|
||||||
|
let mut fb = FileBuilder::new();
|
||||||
|
for i in 0..k {
|
||||||
|
fb.create_dataset(&format!("ds_{i:04}"))
|
||||||
|
.with_f32_data(d)
|
||||||
|
.with_shape(&[rows as u64]);
|
||||||
|
}
|
||||||
|
fb.write(&path).unwrap();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Workload: write_with_attrs
|
||||||
|
// Write a dataset with K attributes — exercises attribute message allocation.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn bench_write_with_attrs(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("write_with_attrs");
|
||||||
|
|
||||||
|
for &k in &[4usize, 16, 64] {
|
||||||
|
group.throughput(Throughput::Elements(k as u64));
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("clawhdf5", k), &k, |b, &k| {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let path = tmp.path().join("write_attrs.h5");
|
||||||
|
b.iter(|| {
|
||||||
|
let mut fb = FileBuilder::new();
|
||||||
|
let ds = fb
|
||||||
|
.create_dataset("data")
|
||||||
|
.with_f64_data(&[1.0, 2.0, 3.0])
|
||||||
|
.with_shape(&[3]);
|
||||||
|
for i in 0..k {
|
||||||
|
ds.set_attr(&format!("attr_{i}"), AttrValue::I64(i as i64));
|
||||||
|
}
|
||||||
|
fb.write(&path).unwrap();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(
|
||||||
|
write_benches,
|
||||||
|
bench_write_1d_contiguous,
|
||||||
|
bench_write_2d_chunked,
|
||||||
|
bench_write_f64_batch,
|
||||||
|
bench_write_multi_dataset,
|
||||||
|
bench_write_with_attrs,
|
||||||
|
);
|
||||||
|
criterion_main!(write_benches);
|
||||||
Reference in New Issue
Block a user