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:
@@ -139,13 +139,13 @@ impl FractalHeapHeader {
|
||||
offset_size: u8,
|
||||
length_size: u8,
|
||||
) -> Result<FractalHeapHeader, 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 header (two
|
||||
/// when it holds an I/O filter pipeline).
|
||||
pub fn parse_in(
|
||||
file: &dyn Storage,
|
||||
pub fn parse_in<S: Storage + ?Sized>(
|
||||
file: &S,
|
||||
offset: u64,
|
||||
offset_size: u8,
|
||||
length_size: u8,
|
||||
@@ -382,15 +382,15 @@ impl FractalHeapHeader {
|
||||
id_bytes: &[u8],
|
||||
offset_size: u8,
|
||||
) -> Result<Vec<u8>, FormatError> {
|
||||
self.read_managed_object_in(&file_data, id_bytes, offset_size)
|
||||
self.read_managed_object_in(file_data, id_bytes, offset_size)
|
||||
}
|
||||
|
||||
/// [`Self::read_managed_object`] over any [`Storage`]. A huge object
|
||||
/// found through the huge-object v2 B-tree still needs the whole file
|
||||
/// in memory ([`FormatError::ContiguousStorageRequired`] otherwise).
|
||||
pub fn read_managed_object_in(
|
||||
pub fn read_managed_object_in<S: Storage + ?Sized>(
|
||||
&self,
|
||||
file_data: &dyn Storage,
|
||||
file_data: &S,
|
||||
id_bytes: &[u8],
|
||||
offset_size: u8,
|
||||
) -> Result<Vec<u8>, FormatError> {
|
||||
@@ -423,7 +423,11 @@ impl FractalHeapHeader {
|
||||
}
|
||||
|
||||
/// Read a huge object (heap ID type 1).
|
||||
fn read_huge_object(&self, file: &dyn Storage, id: &[u8]) -> Result<Vec<u8>, FormatError> {
|
||||
fn read_huge_object<S: Storage + ?Sized>(
|
||||
&self,
|
||||
file: &S,
|
||||
id: &[u8],
|
||||
) -> Result<Vec<u8>, FormatError> {
|
||||
let os = usize::from(self.offset_size);
|
||||
let ls = usize::from(self.length_size);
|
||||
// (address, stored length, filter mask, decoded length); the last two
|
||||
@@ -475,9 +479,9 @@ impl FractalHeapHeader {
|
||||
|
||||
/// Look up huge object `key` in the huge-object v2 B-tree, returning
|
||||
/// (address, stored length, filter mask, decoded length).
|
||||
fn find_huge_record(
|
||||
fn find_huge_record<S: Storage + ?Sized>(
|
||||
&self,
|
||||
file: &dyn Storage,
|
||||
file: &S,
|
||||
key: u64,
|
||||
) -> Result<(u64, u64, u32, u64), FormatError> {
|
||||
if is_undefined(self.huge_btree_address, self.offset_size) {
|
||||
@@ -554,9 +558,9 @@ impl FractalHeapHeader {
|
||||
}
|
||||
|
||||
/// Read a managed object (heap ID type 0).
|
||||
fn read_heap_managed(
|
||||
fn read_heap_managed<S: Storage + ?Sized>(
|
||||
&self,
|
||||
file_data: &dyn Storage,
|
||||
file_data: &S,
|
||||
id_bytes: &[u8],
|
||||
offset_size: u8,
|
||||
) -> Result<Vec<u8>, FormatError> {
|
||||
@@ -604,9 +608,9 @@ impl FractalHeapHeader {
|
||||
/// header), so we just add it to the block address minus the block's heap
|
||||
/// offset. A filtered heap stores each direct block (header included)
|
||||
/// through its filter pipeline, so the block is decoded first.
|
||||
fn read_from_direct_block(
|
||||
fn read_from_direct_block<S: Storage + ?Sized>(
|
||||
&self,
|
||||
file: &dyn Storage,
|
||||
file: &S,
|
||||
block: DirectBlock,
|
||||
target_offset: u64,
|
||||
length: usize,
|
||||
@@ -645,9 +649,9 @@ impl FractalHeapHeader {
|
||||
|
||||
/// Read an object by traversing an indirect block to find the right direct block.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn read_from_indirect_block(
|
||||
fn read_from_indirect_block<S: Storage + ?Sized>(
|
||||
&self,
|
||||
file: &dyn Storage,
|
||||
file: &S,
|
||||
iblock_addr: usize,
|
||||
nrows: u16,
|
||||
iblock_heap_offset: u64,
|
||||
|
||||
Reference in New Issue
Block a user