format: monomorphise the Storage parsers so local files stay as fast

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 at f2ff2c4
so it builds there for an A/B.

Provisional A/B against f2ff2c4 (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]>
This commit is contained in:
osobh
2026-09-26 14:22:37 -05:00
co-authored by Claude Opus 5.5
parent d2b25f154f
commit 052098bf36
18 changed files with 315 additions and 219 deletions
+19 -21
View File
@@ -77,13 +77,13 @@ impl BTreeV1Node {
offset_size: u8,
length_size: u8,
) -> Result<BTreeV1Node, FormatError> {
Self::parse_in(&file_data, offset as u64, offset_size, length_size)
Self::parse_in(file_data, offset as u64, offset_size, length_size)
}
/// [`Self::parse`] over any [`Storage`]: one read of the node's header,
/// one of its keys and children.
pub fn parse_in(
file: &dyn Storage,
pub fn parse_in<S: Storage + ?Sized>(
file: &S,
offset: u64,
offset_size: u8,
_length_size: u8,
@@ -126,24 +126,22 @@ impl BTreeV1Node {
let needed = eu * (key_size + os) + key_size; // eu children + (eu+1) keys
let body = read_exact_at(file, body_start, needed)?;
let file_data: &[u8] = &body;
let mut pos = 0usize;
let mut keys = Vec::with_capacity(eu + 1);
let mut children = Vec::with_capacity(eu);
for _i in 0..eu {
// key[i]
let key = read_offset(file_data, pos, offset_size)?;
keys.push(key);
pos += key_size;
// child[i]
let child = read_offset(file_data, pos, offset_size)?;
children.push(child);
pos += os;
if os == 0 {
// What reading the first key reports (and keeps `chunks_exact`
// below from being given a zero size).
return Err(FormatError::InvalidOffsetSize(offset_size));
}
// final key
let key = read_offset(file_data, pos, offset_size)?;
keys.push(key);
// `needed` bytes: key[0], child[0], ..., child[eu - 1], key[eu].
let (pairs, last) = file_data.split_at(eu * (key_size + os));
for pair in pairs.chunks_exact(key_size + os) {
keys.push(read_offset(pair, 0, offset_size)?);
children.push(read_offset(pair, key_size, offset_size)?);
}
keys.push(read_offset(last, 0, offset_size)?);
Ok(BTreeV1Node {
node_type,
@@ -167,12 +165,12 @@ pub fn collect_symbol_table_nodes(
offset_size: u8,
length_size: u8,
) -> Result<Vec<u64>, FormatError> {
collect_symbol_table_nodes_in(&file_data, btree_address, offset_size, length_size)
collect_symbol_table_nodes_in(file_data, btree_address, offset_size, length_size)
}
/// [`collect_symbol_table_nodes`] over any [`Storage`]: two reads per node.
pub fn collect_symbol_table_nodes_in(
file: &dyn Storage,
pub fn collect_symbol_table_nodes_in<S: Storage + ?Sized>(
file: &S,
btree_address: u64,
offset_size: u8,
length_size: u8,
@@ -180,8 +178,8 @@ pub fn collect_symbol_table_nodes_in(
collect_symbol_table_nodes_inner(file, btree_address, offset_size, length_size, 0)
}
fn collect_symbol_table_nodes_inner(
file: &dyn Storage,
fn collect_symbol_table_nodes_inner<S: Storage + ?Sized>(
file: &S,
btree_address: u64,
offset_size: u8,
length_size: u8,