clawhdf5: parse local files through the slice, and cache the contiguous view
Since M2 the facade handed the metadata parsers its FileData view, so a local file ran the parsers monomorphised for FileData, whose Storage impl worked out contiguous() (patched and overlay checks, two range conversions) on every structure read. A metadata walk of h5stat_newgrat.h5 (35,001 groups: open, entries and attrs of each) was about 4.5% slower than at8f59b2e. - FileData works out its contiguous slice once at open (a borrow of its own heap/mapped buffer, kept as a pointer; see the SAFETY notes). - with_bytes! hands the in-memory slice to the format parsers when the file has one (header parsing, attributes, group listings and lookups, path resolution, shared messages, VL decoding), so local files run the [u8] parsers as before; storage-backed files still get FileData. Provisional A/B on tank (load 5-11), best of 30, 5 alternating rounds: walk 26.07-26.37 ms at8f59b2e, 27.24-28.03 ms before this commit, 26.52-26.98 ms after. Caching alone did not move it (27.02-27.47 ms); the dispatch did. File::open read_f32 on 32M f32 (contiguous, chunked, gzip) stays within noise of8f59b2e. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
+135
-53
@@ -64,6 +64,24 @@ impl Backing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Evaluate `$body` with `$d` bound to the bytes the `clawhdf5_format`
|
||||||
|
/// parsers read: the in-memory slice when the file has one (a `Vec`, an
|
||||||
|
/// mmap), so local files run the parsers monomorphised for `[u8]` — the
|
||||||
|
/// slice code, as before the range-read migration — and otherwise the
|
||||||
|
/// [`FileData`] itself, whose reads go to the storage.
|
||||||
|
macro_rules! with_bytes {
|
||||||
|
($data:expr, |$d:ident| $body:expr) => {{
|
||||||
|
let data: &FileData = $data;
|
||||||
|
match data.contiguous() {
|
||||||
|
Some($d) => $body,
|
||||||
|
None => {
|
||||||
|
let $d = data;
|
||||||
|
$body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
/// The file's bytes, viewed from the superblock on and up to the end of
|
/// The file's bytes, viewed from the superblock on and up to the end of
|
||||||
/// file the superblock records. A file may start with a user block (the
|
/// file the superblock records. A file may start with a user block (the
|
||||||
/// superblock at 512, 1024, …); every HDF5 address is relative to the
|
/// superblock at 512, 1024, …); every HDF5 address is relative to the
|
||||||
@@ -93,8 +111,28 @@ struct FileData {
|
|||||||
/// then); every object lookup here fails with this error, and no
|
/// then); every object lookup here fails with this error, and no
|
||||||
/// metadata is read from the file's own, possibly stale, bytes.
|
/// metadata is read from the file's own, possibly stale, bytes.
|
||||||
image_error: Option<FormatError>,
|
image_error: Option<FormatError>,
|
||||||
|
/// [`Self::contiguous`], worked out once at open: every structure a
|
||||||
|
/// parser reads asks for it, and the patched/overlay checks and range
|
||||||
|
/// conversions behind it cost a local metadata walk a few percent.
|
||||||
|
contiguous: Option<WholeView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A borrow of the HDF5 data held by a [`FileData`]'s own `backing` or
|
||||||
|
/// `patched` buffer (see [`FileData::contiguous`]).
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct WholeView {
|
||||||
|
ptr: *const u8,
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: a `WholeView` is only a borrow of bytes owned (through `backing`
|
||||||
|
// or `patched`) by the `FileData` holding it, which is `Send + Sync`: the
|
||||||
|
// bytes are never written after open, so sharing the pointer across
|
||||||
|
// threads is sharing a `&[u8]`.
|
||||||
|
unsafe impl Send for WholeView {}
|
||||||
|
// SAFETY: as above.
|
||||||
|
unsafe impl Sync for WholeView {}
|
||||||
|
|
||||||
impl FileData {
|
impl FileData {
|
||||||
/// Locate the superblock and parse it. A truncated file is refused, and
|
/// Locate the superblock and parse it. A truncated file is refused, and
|
||||||
/// bytes past the recorded end of file are not read, as in libhdf5.
|
/// bytes past the recorded end of file are not read, as in libhdf5.
|
||||||
@@ -127,17 +165,17 @@ impl FileData {
|
|||||||
ImageView::Patched(p) => (Some(p), None),
|
ImageView::Patched(p) => (Some(p), None),
|
||||||
ImageView::Unloadable(e) => (None, Some(e)),
|
ImageView::Unloadable(e) => (None, Some(e)),
|
||||||
};
|
};
|
||||||
Ok((
|
let mut data = Self {
|
||||||
Self {
|
backing,
|
||||||
backing,
|
base: base as u64,
|
||||||
base: base as u64,
|
end: end as u64,
|
||||||
end: end as u64,
|
patched,
|
||||||
patched,
|
overlay: Vec::new(),
|
||||||
overlay: Vec::new(),
|
image_error,
|
||||||
image_error,
|
contiguous: None,
|
||||||
},
|
};
|
||||||
superblock,
|
data.contiguous = data.find_contiguous();
|
||||||
))
|
Ok((data, superblock))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`Self::new`] for a [`Storage`] backend: the same checks, through
|
/// [`Self::new`] for a [`Storage`] backend: the same checks, through
|
||||||
@@ -152,7 +190,11 @@ impl FileData {
|
|||||||
patched: None,
|
patched: None,
|
||||||
overlay: Vec::new(),
|
overlay: Vec::new(),
|
||||||
image_error: None,
|
image_error: None,
|
||||||
|
contiguous: None,
|
||||||
};
|
};
|
||||||
|
// Worked out again below, once the end of file and any cache image
|
||||||
|
// are known.
|
||||||
|
data.contiguous = data.find_contiguous();
|
||||||
let superblock = Superblock::parse_in(&data, 0)?;
|
let superblock = Superblock::parse_in(&data, 0)?;
|
||||||
data.end = base + superblock.data_end(base, file_len)?;
|
data.end = base + superblock.data_end(base, file_len)?;
|
||||||
match superblock_ext::cache_image_state_in(&data, &superblock)? {
|
match superblock_ext::cache_image_state_in(&data, &superblock)? {
|
||||||
@@ -167,13 +209,37 @@ impl FileData {
|
|||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
data.contiguous = data.find_contiguous();
|
||||||
Ok((data, superblock))
|
Ok((data, superblock))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The HDF5 data as one slice, when the file is in memory (a `Vec`, an
|
/// The HDF5 data as one slice, when the file is in memory (a `Vec`, an
|
||||||
/// mmap, or a storage that holds it all and has no cache image to lay
|
/// mmap, or a storage that holds it all and has no cache image to lay
|
||||||
/// over it).
|
/// over it).
|
||||||
|
#[inline]
|
||||||
fn contiguous(&self) -> Option<&[u8]> {
|
fn contiguous(&self) -> Option<&[u8]> {
|
||||||
|
// SAFETY: `find_contiguous` borrowed these bytes from `backing` or
|
||||||
|
// `patched`, which this `FileData` owns and never changes after
|
||||||
|
// open. They live on the heap or in a mapping (a `Vec`'s buffer, an
|
||||||
|
// mmap, a private copy, or a buffer inside the `Arc`'d storage), so
|
||||||
|
// they stay put when the `FileData` moves, and they live as long as
|
||||||
|
// `self`.
|
||||||
|
self.contiguous
|
||||||
|
.map(|v| unsafe { core::slice::from_raw_parts(v.ptr, v.len) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`Self::contiguous`], worked out from `backing` and `patched`.
|
||||||
|
fn find_contiguous(&self) -> Option<WholeView> {
|
||||||
|
let bytes = self.compute_contiguous()?;
|
||||||
|
Some(WholeView {
|
||||||
|
ptr: bytes.as_ptr(),
|
||||||
|
len: bytes.len(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The HDF5 data as one slice, from `backing` and `patched` (see
|
||||||
|
/// [`Self::contiguous`]).
|
||||||
|
fn compute_contiguous(&self) -> Option<&[u8]> {
|
||||||
if let Some(p) = &self.patched {
|
if let Some(p) = &self.patched {
|
||||||
return p.get(usize::try_from(self.base).ok()?..usize::try_from(self.end).ok()?);
|
return p.get(usize::try_from(self.base).ok()?..usize::try_from(self.end).ok()?);
|
||||||
}
|
}
|
||||||
@@ -224,6 +290,7 @@ impl FileData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Storage for FileData {
|
impl Storage for FileData {
|
||||||
|
#[inline]
|
||||||
fn read_at(&self, offset: u64, len: usize) -> Result<Cow<'_, [u8]>, FormatError> {
|
fn read_at(&self, offset: u64, len: usize) -> Result<Cow<'_, [u8]>, FormatError> {
|
||||||
if let Some(all) = self.contiguous() {
|
if let Some(all) = self.contiguous() {
|
||||||
return all.read_at(offset, len);
|
return all.read_at(offset, len);
|
||||||
@@ -261,6 +328,7 @@ impl Storage for FileData {
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn as_contiguous(&self) -> Option<&[u8]> {
|
fn as_contiguous(&self) -> Option<&[u8]> {
|
||||||
self.contiguous()
|
self.contiguous()
|
||||||
}
|
}
|
||||||
@@ -400,8 +468,11 @@ impl File {
|
|||||||
///
|
///
|
||||||
/// The path uses `/` separators (e.g., `"group1/values"`).
|
/// The path uses `/` separators (e.g., `"group1/values"`).
|
||||||
pub fn dataset(&self, path: &str) -> Result<Dataset<'_>, Error> {
|
pub fn dataset(&self, path: &str) -> Result<Dataset<'_>, Error> {
|
||||||
let data = self.data.meta()?;
|
let addr = with_bytes!(self.data.meta()?, |d| group_v2::resolve_path_any_in(
|
||||||
let addr = group_v2::resolve_path_any_in(data, &self.superblock, path)?;
|
d,
|
||||||
|
&self.superblock,
|
||||||
|
path
|
||||||
|
))?;
|
||||||
let hdr = self.parse_header(addr)?;
|
let hdr = self.parse_header(addr)?;
|
||||||
if !has_message(&hdr, MessageType::DataLayout) {
|
if !has_message(&hdr, MessageType::DataLayout) {
|
||||||
return Err(Error::NotADataset(path.to_string()));
|
return Err(Error::NotADataset(path.to_string()));
|
||||||
@@ -447,8 +518,11 @@ impl File {
|
|||||||
/// The path uses `/` separators (e.g., `"sensors"`).
|
/// The path uses `/` separators (e.g., `"sensors"`).
|
||||||
/// Use `"/"` or `""` for the root group.
|
/// Use `"/"` or `""` for the root group.
|
||||||
pub fn group(&self, path: &str) -> Result<Group<'_>, Error> {
|
pub fn group(&self, path: &str) -> Result<Group<'_>, Error> {
|
||||||
let data = self.data.meta()?;
|
let addr = with_bytes!(self.data.meta()?, |d| group_v2::resolve_path_any_in(
|
||||||
let addr = group_v2::resolve_path_any_in(data, &self.superblock, path)?;
|
d,
|
||||||
|
&self.superblock,
|
||||||
|
path
|
||||||
|
))?;
|
||||||
Ok(Group {
|
Ok(Group {
|
||||||
file: self,
|
file: self,
|
||||||
address: addr,
|
address: addr,
|
||||||
@@ -560,13 +634,13 @@ impl File {
|
|||||||
/// [`AttrValue::Raw`] attribute. Variable-length strings are resolved in
|
/// [`AttrValue::Raw`] attribute. Variable-length strings are resolved in
|
||||||
/// this file's global heap; see [`Dataset::read_string`] for the values.
|
/// this file's global heap; see [`Dataset::read_string`] for the values.
|
||||||
pub fn decode_strings(&self, datatype: &Datatype, raw: &[u8]) -> Result<Vec<String>, Error> {
|
pub fn decode_strings(&self, datatype: &Datatype, raw: &[u8]) -> Result<Vec<String>, Error> {
|
||||||
crate::vlen::decode_strings(
|
with_bytes!(&self.data, |d| crate::vlen::decode_strings(
|
||||||
&self.data,
|
d,
|
||||||
datatype,
|
datatype,
|
||||||
raw,
|
raw,
|
||||||
self.offset_size(),
|
self.offset_size(),
|
||||||
self.length_size(),
|
self.length_size(),
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like [`decode_strings`](Self::decode_strings) for variable-length
|
/// Like [`decode_strings`](Self::decode_strings) for variable-length
|
||||||
@@ -577,13 +651,13 @@ impl File {
|
|||||||
datatype: &Datatype,
|
datatype: &Datatype,
|
||||||
raw: &[u8],
|
raw: &[u8],
|
||||||
) -> Result<Vec<Vec<u8>>, Error> {
|
) -> Result<Vec<Vec<u8>>, Error> {
|
||||||
crate::vlen::decode_string_bytes(
|
with_bytes!(self.data.meta()?, |d| crate::vlen::decode_string_bytes(
|
||||||
self.data.meta()?,
|
d,
|
||||||
datatype,
|
datatype,
|
||||||
raw,
|
raw,
|
||||||
self.offset_size(),
|
self.offset_size(),
|
||||||
self.length_size(),
|
self.length_size(),
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decode the variable-length sequences in `raw`, a buffer of elements
|
/// Decode the variable-length sequences in `raw`, a buffer of elements
|
||||||
@@ -595,22 +669,22 @@ impl File {
|
|||||||
datatype: &Datatype,
|
datatype: &Datatype,
|
||||||
raw: &[u8],
|
raw: &[u8],
|
||||||
) -> Result<Vec<Vec<T>>, Error> {
|
) -> Result<Vec<Vec<T>>, Error> {
|
||||||
crate::vlen::decode_vlen(
|
with_bytes!(self.data.meta()?, |d| crate::vlen::decode_vlen(
|
||||||
self.data.meta()?,
|
d,
|
||||||
datatype,
|
datatype,
|
||||||
raw,
|
raw,
|
||||||
self.offset_size(),
|
self.offset_size(),
|
||||||
self.length_size(),
|
self.length_size(),
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_header(&self, address: u64) -> Result<ObjectHeader, FormatError> {
|
fn parse_header(&self, address: u64) -> Result<ObjectHeader, FormatError> {
|
||||||
ObjectHeader::parse_in(
|
with_bytes!(self.data.meta()?, |d| ObjectHeader::parse_in(
|
||||||
self.data.meta()?,
|
d,
|
||||||
address,
|
address,
|
||||||
self.superblock.offset_size,
|
self.superblock.offset_size,
|
||||||
self.superblock.length_size,
|
self.superblock.length_size,
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn offset_size(&self) -> u8 {
|
fn offset_size(&self) -> u8 {
|
||||||
@@ -688,8 +762,12 @@ impl<'f> Group<'f> {
|
|||||||
&self,
|
&self,
|
||||||
) -> Result<(HashMap<String, AttrValue>, Vec<FormatError>), Error> {
|
) -> Result<(HashMap<String, AttrValue>, Vec<FormatError>), Error> {
|
||||||
let hdr = self.file.parse_header(self.address)?;
|
let hdr = self.file.parse_header(self.address)?;
|
||||||
let data = &self.file.data;
|
with_bytes!(&self.file.data, |d| read_attrs(
|
||||||
read_attrs(data, &hdr, self.file.offset_size(), self.file.length_size())
|
d,
|
||||||
|
&hdr,
|
||||||
|
self.file.offset_size(),
|
||||||
|
self.file.length_size()
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a dataset within this group by name.
|
/// Get a dataset within this group by name.
|
||||||
@@ -719,14 +797,13 @@ impl<'f> Group<'f> {
|
|||||||
/// stored densely.
|
/// stored densely.
|
||||||
pub fn attr(&self, name: &str) -> Result<Option<AttrValue>, Error> {
|
pub fn attr(&self, name: &str) -> Result<Option<AttrValue>, Error> {
|
||||||
let hdr = self.file.parse_header(self.address)?;
|
let hdr = self.file.parse_header(self.address)?;
|
||||||
let data = &self.file.data;
|
with_bytes!(&self.file.data, |d| read_attr(
|
||||||
read_attr(
|
d,
|
||||||
data,
|
|
||||||
&hdr,
|
&hdr,
|
||||||
name,
|
name,
|
||||||
self.file.offset_size(),
|
self.file.offset_size(),
|
||||||
self.file.length_size(),
|
self.file.length_size(),
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The object header address of the child called `name`: the entry of
|
/// The object header address of the child called `name`: the entry of
|
||||||
@@ -734,9 +811,13 @@ impl<'f> Group<'f> {
|
|||||||
/// name index rather than by listing the group (see
|
/// name index rather than by listing the group (see
|
||||||
/// [`group_v2::resolve_child`]).
|
/// [`group_v2::resolve_child`]).
|
||||||
fn child_address(&self, name: &str) -> Result<u64, Error> {
|
fn child_address(&self, name: &str) -> Result<u64, Error> {
|
||||||
let data = self.file.data.meta()?;
|
with_bytes!(self.file.data.meta()?, |d| group_v2::resolve_child_in(
|
||||||
group_v2::resolve_child_in(data, &self.file.superblock, self.address, name)
|
d,
|
||||||
.map_err(Error::Format)
|
&self.file.superblock,
|
||||||
|
self.address,
|
||||||
|
name
|
||||||
|
))
|
||||||
|
.map_err(Error::Format)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This group's children that can be opened, as `(name, object header
|
/// This group's children that can be opened, as `(name, object header
|
||||||
@@ -757,9 +838,10 @@ impl<'f> Group<'f> {
|
|||||||
/// [`group_v2::resolve_group_children`]); dangling, external and
|
/// [`group_v2::resolve_group_children`]); dangling, external and
|
||||||
/// user-defined links are left out.
|
/// user-defined links are left out.
|
||||||
fn children(&self) -> Result<Vec<GroupEntry>, Error> {
|
fn children(&self) -> Result<Vec<GroupEntry>, Error> {
|
||||||
let data = self.file.data.meta()?;
|
with_bytes!(self.file.data.meta()?, |d| {
|
||||||
group_v2::resolve_group_children_in(data, &self.file.superblock, self.address)
|
group_v2::resolve_group_children_in(d, &self.file.superblock, self.address)
|
||||||
.map_err(Error::Format)
|
})
|
||||||
|
.map_err(Error::Format)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1332,13 +1414,12 @@ impl<'f> Dataset<'f> {
|
|||||||
pub fn attrs_with_errors(
|
pub fn attrs_with_errors(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<(HashMap<String, AttrValue>, Vec<FormatError>), Error> {
|
) -> Result<(HashMap<String, AttrValue>, Vec<FormatError>), Error> {
|
||||||
let data = &self.file.data;
|
with_bytes!(&self.file.data, |d| read_attrs(
|
||||||
read_attrs(
|
d,
|
||||||
data,
|
|
||||||
&self.header,
|
&self.header,
|
||||||
self.file.offset_size(),
|
self.file.offset_size(),
|
||||||
self.file.length_size(),
|
self.file.length_size(),
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The attribute called `name`, or `None` if it has none by that name
|
/// The attribute called `name`, or `None` if it has none by that name
|
||||||
@@ -1346,14 +1427,13 @@ impl<'f> Dataset<'f> {
|
|||||||
/// that name, found without reading the other attributes when they are
|
/// that name, found without reading the other attributes when they are
|
||||||
/// stored densely.
|
/// stored densely.
|
||||||
pub fn attr(&self, name: &str) -> Result<Option<AttrValue>, Error> {
|
pub fn attr(&self, name: &str) -> Result<Option<AttrValue>, Error> {
|
||||||
let data = &self.file.data;
|
with_bytes!(&self.file.data, |d| read_attr(
|
||||||
read_attr(
|
d,
|
||||||
data,
|
|
||||||
&self.header,
|
&self.header,
|
||||||
name,
|
name,
|
||||||
self.file.offset_size(),
|
self.file.offset_size(),
|
||||||
self.file.length_size(),
|
self.file.length_size(),
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify this dataset's content against its stored provenance hash
|
/// Verify this dataset's content against its stored provenance hash
|
||||||
@@ -1393,12 +1473,14 @@ impl<'f> Dataset<'f> {
|
|||||||
.iter()
|
.iter()
|
||||||
.find(|m| m.msg_type == msg_type)
|
.find(|m| m.msg_type == msg_type)
|
||||||
.map(|msg| {
|
.map(|msg| {
|
||||||
clawhdf5_format::shared_message::message_data_in(
|
with_bytes!(&self.file.data, |d| {
|
||||||
&self.file.data,
|
clawhdf5_format::shared_message::message_data_in(
|
||||||
msg,
|
d,
|
||||||
self.file.offset_size(),
|
msg,
|
||||||
self.file.length_size(),
|
self.file.offset_size(),
|
||||||
)
|
self.file.length_size(),
|
||||||
|
)
|
||||||
|
})
|
||||||
.map_err(Error::Format)
|
.map_err(Error::Format)
|
||||||
})
|
})
|
||||||
.transpose()
|
.transpose()
|
||||||
|
|||||||
Reference in New Issue
Block a user