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 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]>
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::error::FormatError;
|
||||
use crate::storage::{Storage, len_usize, read_exact_at};
|
||||
use crate::storage::{Storage, read_exact_at};
|
||||
|
||||
/// Symbol Table message (type 0x0011) found in v1 group object headers.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -80,17 +80,16 @@ impl SymbolTableNode {
|
||||
offset: usize,
|
||||
offset_size: u8,
|
||||
) -> Result<SymbolTableNode, FormatError> {
|
||||
Self::parse_in(&file_data, offset as u64, offset_size)
|
||||
Self::parse_in(file_data, offset as u64, offset_size)
|
||||
}
|
||||
|
||||
/// [`Self::parse`] over any [`Storage`]: one read of the node's header,
|
||||
/// one of its entries.
|
||||
pub fn parse_in(
|
||||
file: &dyn Storage,
|
||||
pub fn parse_in<S: Storage + ?Sized>(
|
||||
file: &S,
|
||||
offset: u64,
|
||||
offset_size: u8,
|
||||
) -> Result<SymbolTableNode, FormatError> {
|
||||
let file_len = len_usize(file);
|
||||
// signature(4) + version(1) + reserved(1) + number_of_symbols(2) = 8
|
||||
let header = read_exact_at(file, offset, 8)?;
|
||||
|
||||
@@ -108,42 +107,22 @@ impl SymbolTableNode {
|
||||
let os = offset_size as usize;
|
||||
// Each entry: link_name_offset(os) + obj_hdr_addr(os) + cache_type(4) + reserved(4) + scratch(16)
|
||||
let entry_size = os + os + 4 + 4 + 16;
|
||||
// `offset + 8` fits: the header's read checked it.
|
||||
let entries_start = offset as usize + 8;
|
||||
let needed = entries_start.checked_add(num_symbols * entry_size).ok_or(
|
||||
FormatError::UnexpectedEof {
|
||||
expected: usize::MAX,
|
||||
available: file_len,
|
||||
},
|
||||
)?;
|
||||
if needed > file_len {
|
||||
return Err(FormatError::UnexpectedEof {
|
||||
expected: needed,
|
||||
available: file_len,
|
||||
});
|
||||
}
|
||||
let body = read_exact_at(file, entries_start as u64, num_symbols * entry_size)?;
|
||||
// `offset + 8` fits: the header's read checked it. The entries'
|
||||
// read is the bounds check (`offset + 8 + entries > file length`,
|
||||
// which cannot overflow: at most 65535 entries of 40 bytes).
|
||||
let body = read_exact_at(file, offset + 8, num_symbols * entry_size)?;
|
||||
let file_data: &[u8] = &body;
|
||||
|
||||
let mut entries = Vec::with_capacity(num_symbols);
|
||||
let mut pos = 0usize;
|
||||
for _ in 0..num_symbols {
|
||||
let link_name_offset = read_offset(file_data, pos, offset_size)?;
|
||||
pos += os;
|
||||
let object_header_address = read_offset(file_data, pos, offset_size)?;
|
||||
pos += os;
|
||||
let cache_type = u32::from_le_bytes([
|
||||
file_data[pos],
|
||||
file_data[pos + 1],
|
||||
file_data[pos + 2],
|
||||
file_data[pos + 3],
|
||||
]);
|
||||
pos += 4;
|
||||
for entry in file_data.chunks_exact(entry_size) {
|
||||
let link_name_offset = read_offset(entry, 0, offset_size)?;
|
||||
let object_header_address = read_offset(entry, os, offset_size)?;
|
||||
let pos = 2 * os;
|
||||
let cache_type =
|
||||
u32::from_le_bytes([entry[pos], entry[pos + 1], entry[pos + 2], entry[pos + 3]]);
|
||||
// reserved 4 bytes
|
||||
pos += 4;
|
||||
let mut scratch_pad = [0u8; 16];
|
||||
scratch_pad.copy_from_slice(&file_data[pos..pos + 16]);
|
||||
pos += 16;
|
||||
scratch_pad.copy_from_slice(&entry[pos + 8..pos + 24]);
|
||||
|
||||
entries.push(SymbolTableEntry {
|
||||
link_name_offset,
|
||||
|
||||
Reference in New Issue
Block a user