Every `*_in` core and the read helpers take `file: &S` with `S: Storage + ?Sized` instead of `&dyn Storage`, and the `&[u8]` wrappers pass the slice itself, so they compile to a `[u8]` instance: `as_contiguous()` inlines to `Some(self)` and each structure read is the slice code's bounds check again, with no indirect call. `&dyn Storage` still works (`S = dyn Storage`); there is one parser implementation. Also, so the structure reads cost no more than the slice checks did: - ObjectHeader::parse_in reads the prefix once (signature included) instead of the signature and then the prefix: two reads for a one-chunk header instead of three on a range backend; - the symbol-table node and group B-tree (v1) loops walk their entries with chunks_exact over the bytes read, and the node's redundant second bounds check is gone (the entries' read is the check, same error); - a version-1 header's message list is sized from its (capped) count. Same results and errors; the unit and equivalence tests are unchanged. New Criterion bench `clawhdf5/benches/local_metadata_bench.rs` over a 400-group version-1 file written by h5py (new fixture `v1_groups_400.h5`): ObjectHeader::parse, symbol-table nodes, the group B-tree walk and a facade listing, using only APIs that exist atf2ff2c4so it builds there for an A/B. Provisional A/B againstf2ff2c4(busy machine, not for docs): both builds linked into one binary and timed in alternation, 200 rounds; median ratio new/old: facade listing -0.5% to -3.5% (was +14%), ObjectHeader::parse +1% to +2% (was +25%), symbol-table nodes -18%, group B-tree walk -18%, local-heap names and resolve_group_children within +-1.5%. An old-vs-old-copy run shows +-2% from code layout alone. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
92 lines
3.2 KiB
Rust
92 lines
3.2 KiB
Rust
//! Metadata parsing over an in-memory file: the local fast path that the
|
|
//! range-read `Storage` migration must not slow down
|
|
//! (`docs/design/range-reads.md`, "Keeping the local fast path").
|
|
//!
|
|
//! Only the `&[u8]` APIs are used, so the same file builds against older
|
|
//! revisions for an A/B comparison. The input is a version-1 (symbol table)
|
|
//! file with 400 groups, written by h5py with `libver='earliest'` and a
|
|
//! 512-byte user block (`clawhdf5-format/tests/fixtures/v1_groups_400.h5`).
|
|
|
|
use clawhdf5::{File, Group};
|
|
use clawhdf5_format::btree_v1::collect_symbol_table_nodes;
|
|
use clawhdf5_format::message_type::MessageType;
|
|
use clawhdf5_format::object_header::ObjectHeader;
|
|
use clawhdf5_format::superblock::Superblock;
|
|
use clawhdf5_format::symbol_table::{SymbolTableMessage, SymbolTableNode};
|
|
use criterion::{Criterion, criterion_group, criterion_main};
|
|
use std::hint::black_box;
|
|
|
|
const FIXTURE: &str = concat!(
|
|
env!("CARGO_MANIFEST_DIR"),
|
|
"/../clawhdf5-format/tests/fixtures/v1_groups_400.h5"
|
|
);
|
|
|
|
fn walk(g: &Group<'_>, objs: &mut usize) {
|
|
for name in g.datasets().unwrap_or_default() {
|
|
*objs += 1;
|
|
if let Ok(ds) = g.dataset(&name) {
|
|
let _ = black_box(ds.shape());
|
|
let _ = black_box(ds.dtype());
|
|
let _ = black_box(ds.attrs());
|
|
}
|
|
}
|
|
for name in g.groups().unwrap_or_default() {
|
|
*objs += 1;
|
|
if let Ok(sub) = g.group(&name) {
|
|
walk(&sub, objs);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn bench_local_metadata(c: &mut Criterion) {
|
|
let bytes = std::fs::read(FIXTURE).unwrap();
|
|
let (_, f) = clawhdf5_format::signature::split_user_block(&bytes).unwrap();
|
|
let sb = Superblock::parse(f, 0).unwrap();
|
|
let (os, ls) = (sb.offset_size, sb.length_size);
|
|
let root = ObjectHeader::parse(f, sb.root_group_address as usize, os, ls).unwrap();
|
|
let stm = root
|
|
.messages
|
|
.iter()
|
|
.find(|m| m.msg_type == MessageType::SymbolTable)
|
|
.map(|m| SymbolTableMessage::parse(&m.data, os).unwrap())
|
|
.unwrap();
|
|
let nodes = collect_symbol_table_nodes(f, stm.btree_address, os, ls).unwrap();
|
|
let headers: Vec<u64> = nodes
|
|
.iter()
|
|
.flat_map(|&a| SymbolTableNode::parse(f, a as usize, os).unwrap().entries)
|
|
.map(|e| e.object_header_address)
|
|
.collect();
|
|
assert_eq!(headers.len(), 401);
|
|
|
|
let mut g = c.benchmark_group("local_metadata");
|
|
g.bench_function("object_header_parse_x401", |b| {
|
|
b.iter(|| {
|
|
for &a in &headers {
|
|
black_box(ObjectHeader::parse(f, a as usize, os, ls).unwrap());
|
|
}
|
|
})
|
|
});
|
|
g.bench_function("snod_parse_all", |b| {
|
|
b.iter(|| {
|
|
for &a in &nodes {
|
|
black_box(SymbolTableNode::parse(f, a as usize, os).unwrap());
|
|
}
|
|
})
|
|
});
|
|
g.bench_function("btree_v1_walk", |b| {
|
|
b.iter(|| black_box(collect_symbol_table_nodes(f, stm.btree_address, os, ls).unwrap()))
|
|
});
|
|
let file = File::open(FIXTURE).unwrap();
|
|
g.bench_function("facade_list_400_groups", |b| {
|
|
b.iter(|| {
|
|
let mut n = 0;
|
|
walk(&file.root(), &mut n);
|
|
assert_eq!(n, 401);
|
|
})
|
|
});
|
|
g.finish();
|
|
}
|
|
|
|
criterion_group!(benches, bench_local_metadata);
|
|
criterion_main!(benches);
|