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:
@@ -51,7 +51,7 @@ impl AttributeMessage {
|
||||
///
|
||||
/// `length_size` is needed for dataspace dimension parsing.
|
||||
pub fn parse(data: &[u8], length_size: u8) -> Result<AttributeMessage, FormatError> {
|
||||
Self::parse_impl(data, length_size, None)
|
||||
Self::parse_impl(data, length_size, None::<(&[u8], u8)>)
|
||||
}
|
||||
|
||||
/// [`AttributeMessage::parse`] with access to the rest of the file, which
|
||||
@@ -66,24 +66,24 @@ impl AttributeMessage {
|
||||
offset_size: u8,
|
||||
length_size: u8,
|
||||
) -> Result<AttributeMessage, FormatError> {
|
||||
Self::parse_in_storage(data, &file_data, offset_size, length_size)
|
||||
Self::parse_in_storage(data, file_data, offset_size, length_size)
|
||||
}
|
||||
|
||||
/// [`AttributeMessage::parse_in_file`] with the file behind any
|
||||
/// [`Storage`].
|
||||
pub fn parse_in_storage(
|
||||
pub fn parse_in_storage<S: Storage + ?Sized>(
|
||||
data: &[u8],
|
||||
file: &dyn Storage,
|
||||
file: &S,
|
||||
offset_size: u8,
|
||||
length_size: u8,
|
||||
) -> Result<AttributeMessage, FormatError> {
|
||||
Self::parse_impl(data, length_size, Some((file, offset_size)))
|
||||
}
|
||||
|
||||
fn parse_impl(
|
||||
fn parse_impl<S: Storage + ?Sized>(
|
||||
data: &[u8],
|
||||
length_size: u8,
|
||||
file: Option<(&dyn Storage, u8)>,
|
||||
file: Option<(&S, u8)>,
|
||||
) -> Result<AttributeMessage, FormatError> {
|
||||
ensure_len(data, 0, 2)?;
|
||||
let version = data[0];
|
||||
@@ -98,12 +98,12 @@ impl AttributeMessage {
|
||||
|
||||
/// The bytes of an embedded datatype/dataspace message, following the
|
||||
/// shared-message reference when `shared` is set.
|
||||
fn embedded_message<'a>(
|
||||
fn embedded_message<'a, S: Storage + ?Sized>(
|
||||
bytes: &'a [u8],
|
||||
shared: bool,
|
||||
msg_type: MessageType,
|
||||
length_size: u8,
|
||||
file: Option<(&dyn Storage, u8)>,
|
||||
file: Option<(&S, u8)>,
|
||||
) -> Result<Cow<'a, [u8]>, FormatError> {
|
||||
if !shared {
|
||||
return Ok(Cow::Borrowed(bytes));
|
||||
@@ -155,10 +155,10 @@ impl AttributeMessage {
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_v2(
|
||||
fn parse_v2<S: Storage + ?Sized>(
|
||||
data: &[u8],
|
||||
length_size: u8,
|
||||
file: Option<(&dyn Storage, u8)>,
|
||||
file: Option<(&S, u8)>,
|
||||
) -> Result<AttributeMessage, FormatError> {
|
||||
// Flags: bit 0 = datatype is shared, bit 1 = dataspace is shared.
|
||||
let flags = data.get(1).copied().unwrap_or(0);
|
||||
@@ -209,10 +209,10 @@ impl AttributeMessage {
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_v3(
|
||||
fn parse_v3<S: Storage + ?Sized>(
|
||||
data: &[u8],
|
||||
length_size: u8,
|
||||
file: Option<(&dyn Storage, u8)>,
|
||||
file: Option<(&S, u8)>,
|
||||
) -> Result<AttributeMessage, FormatError> {
|
||||
// Flags: bit 0 = datatype is shared, bit 1 = dataspace is shared.
|
||||
let flags = data.get(1).copied().unwrap_or(0);
|
||||
@@ -427,15 +427,15 @@ pub fn extract_attributes_full(
|
||||
offset_size: u8,
|
||||
length_size: u8,
|
||||
) -> Result<Vec<AttributeMessage>, FormatError> {
|
||||
extract_attributes_full_in(&file_data, header, offset_size, length_size)
|
||||
extract_attributes_full_in(file_data, header, offset_size, length_size)
|
||||
}
|
||||
|
||||
/// [`extract_attributes_full`] over any [`Storage`]. Dense attribute
|
||||
/// storage is indexed by a v2 B-tree, which is not read over [`Storage`]
|
||||
/// yet: on a backend without the whole file in memory an object with dense
|
||||
/// attributes is [`FormatError::ContiguousStorageRequired`].
|
||||
pub fn extract_attributes_full_in(
|
||||
file: &dyn Storage,
|
||||
pub fn extract_attributes_full_in<S: Storage + ?Sized>(
|
||||
file: &S,
|
||||
header: &ObjectHeader,
|
||||
offset_size: u8,
|
||||
length_size: u8,
|
||||
@@ -457,13 +457,13 @@ pub fn extract_attributes_tolerant(
|
||||
offset_size: u8,
|
||||
length_size: u8,
|
||||
) -> Result<(Vec<AttributeMessage>, Vec<FormatError>), FormatError> {
|
||||
extract_attributes_tolerant_in(&file_data, header, offset_size, length_size)
|
||||
extract_attributes_tolerant_in(file_data, header, offset_size, length_size)
|
||||
}
|
||||
|
||||
/// [`extract_attributes_tolerant`] over any [`Storage`] (see
|
||||
/// [`extract_attributes_full_in`] for dense storage).
|
||||
pub fn extract_attributes_tolerant_in(
|
||||
file_data: &dyn Storage,
|
||||
pub fn extract_attributes_tolerant_in<S: Storage + ?Sized>(
|
||||
file_data: &S,
|
||||
header: &ObjectHeader,
|
||||
offset_size: u8,
|
||||
length_size: u8,
|
||||
@@ -478,8 +478,8 @@ pub fn extract_attributes_tolerant_in(
|
||||
|
||||
/// Read every attribute; each one that fails goes to `on_error`, which
|
||||
/// either stops the read (returns the error) or skips that attribute.
|
||||
fn extract_attributes_with(
|
||||
file_data: &dyn Storage,
|
||||
fn extract_attributes_with<S: Storage + ?Sized>(
|
||||
file_data: &S,
|
||||
header: &ObjectHeader,
|
||||
offset_size: u8,
|
||||
length_size: u8,
|
||||
@@ -572,8 +572,8 @@ fn find_attribute_info(
|
||||
/// Extract attributes from dense storage (fractal heap + B-tree v2), and
|
||||
/// each one's creation order into `orders`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn extract_dense_attributes(
|
||||
file_data: &dyn Storage,
|
||||
fn extract_dense_attributes<S: Storage + ?Sized>(
|
||||
file_data: &S,
|
||||
attr_info: &AttributeInfoMessage,
|
||||
fh_addr: u64,
|
||||
offset_size: u8,
|
||||
|
||||
Reference in New Issue
Block a user