read: look names up through the dense name indexes

Finding one link or attribute by name read every entry: Group::dataset and
Group::group (File, MmapFile, LazyFile) listed the whole group per call, and
path resolution scanned each group's links. Opening every child of a
35 001-link group by name decoded ~1.2e9 links.

Now a dense group's v2 B-tree name index (type 5, lookup3 hash of the
name) is descended to the records with the name's hash
(btree_v2::find_btree_v2_records reads only the nodes whose key interval
overlaps), and only those links are read and compared; all hash-equal
records are compared, so libhdf5's tie order does not matter. Dense
attributes the same through their type 8 index
(attribute::find_attribute_in_file, facade attr(name)); huge heap objects
through their ID-ordered index. group_v2::resolve_child returns what the
listing has under a name (soft links followed, dangling/external ones not
found). Group::entries and File::group_at hand out a listing's addresses.

The lookup-stats feature counts heap objects read. Tests: one lookup in
an h5py-written 35 001-link group with colliding hashes reads at most two
links (before: 35 001, failing), attribute lookups likewise (before: 3 000,
failing), every child opens through all three readers and matches h5py,
every link kind resolves as h5py resolves it in dense and compact groups,
300 huge attributes are found, and a range search matches a full scan at
every tree depth.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-26 13:33:19 -05:00
co-authored by Claude Opus 5.5
parent 6248b411f0
commit 02e89c1d2d
15 changed files with 1245 additions and 159 deletions
@@ -0,0 +1,31 @@
//! Work counters for tests of lookup cost (feature `lookup-stats`).
//!
//! Counts fractal-heap objects read — each is one link or attribute message
//! decoded out of a dense group or dense attribute storage — so a test can
//! check that finding one name reads a handful of them, not the whole group.
//! Per thread, so tests running in parallel do not see each other's reads.
//! Without the feature the counting compiles to nothing.
#[cfg(feature = "lookup-stats")]
std::thread_local! {
static HEAP_OBJECTS: core::cell::Cell<u64> = const { core::cell::Cell::new(0) };
}
/// Record one heap object read.
#[inline(always)]
pub(crate) fn heap_object_read() {
#[cfg(feature = "lookup-stats")]
HEAP_OBJECTS.with(|c| c.set(c.get() + 1));
}
/// Heap objects read on this thread since the last [`reset`].
#[cfg(feature = "lookup-stats")]
pub fn heap_objects_read() -> u64 {
HEAP_OBJECTS.with(core::cell::Cell::get)
}
/// Zero this thread's counters.
#[cfg(feature = "lookup-stats")]
pub fn reset() {
HEAP_OBJECTS.with(|c| c.set(0));
}