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:
@@ -6,7 +6,8 @@ use alloc::{format, vec::Vec};
|
||||
#[cfg(feature = "checksum")]
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
|
||||
use crate::btree_v2::{BTreeV2Header, collect_btree_v2_records};
|
||||
use crate::addr::to_usize;
|
||||
use crate::btree_v2::{BTreeV2Header, find_btree_v2_records};
|
||||
use crate::error::FormatError;
|
||||
use crate::filter_pipeline::FilterPipeline;
|
||||
|
||||
@@ -354,6 +355,7 @@ impl FractalHeapHeader {
|
||||
id_bytes: &[u8],
|
||||
offset_size: u8,
|
||||
) -> Result<Vec<u8>, FormatError> {
|
||||
crate::lookup_stats::heap_object_read();
|
||||
let Some(&first) = id_bytes.first() else {
|
||||
return Err(FormatError::UnexpectedEof {
|
||||
expected: 1,
|
||||
@@ -448,7 +450,7 @@ impl FractalHeapHeader {
|
||||
}
|
||||
let hdr = BTreeV2Header::parse(
|
||||
file_data,
|
||||
self.huge_btree_address as usize,
|
||||
to_usize(self.huge_btree_address)?,
|
||||
self.offset_size,
|
||||
self.length_size,
|
||||
)?;
|
||||
@@ -463,8 +465,12 @@ impl FractalHeapHeader {
|
||||
if hdr.tree_type != expected_type || usize::from(hdr.record_size) < rec_len {
|
||||
return Err(heap_error("unexpected huge-object B-tree record type"));
|
||||
}
|
||||
let records =
|
||||
collect_btree_v2_records(file_data, &hdr, self.offset_size, self.length_size)?;
|
||||
// Records are ordered by ID (the last field): descend to the ones
|
||||
// equal to `key` instead of reading the whole index.
|
||||
let id_at = rec_len - ls;
|
||||
let records = find_btree_v2_records(file_data, &hdr, self.offset_size, &mut |r| {
|
||||
le_uint(&r[id_at..id_at + ls]).cmp(&key)
|
||||
})?;
|
||||
for rec in &records {
|
||||
let d = &rec.data;
|
||||
if d.len() < rec_len {
|
||||
@@ -533,24 +539,24 @@ impl FractalHeapHeader {
|
||||
self.read_from_direct_block(
|
||||
file_data,
|
||||
DirectBlock {
|
||||
addr: self.root_block_address as usize,
|
||||
addr: to_usize(self.root_block_address)?,
|
||||
size: self.starting_block_size,
|
||||
heap_offset: 0,
|
||||
filtered_size: self.root_direct_block_filtered_size,
|
||||
filter_mask: self.root_direct_block_filter_mask,
|
||||
},
|
||||
heap_offset,
|
||||
obj_len as usize,
|
||||
to_usize(obj_len)?,
|
||||
)
|
||||
} else {
|
||||
// Root is an indirect block — limit recursion to 64 levels
|
||||
self.read_from_indirect_block(
|
||||
file_data,
|
||||
self.root_block_address as usize,
|
||||
to_usize(self.root_block_address)?,
|
||||
self.current_rows_in_root_indirect_block,
|
||||
0, // block offset
|
||||
heap_offset,
|
||||
obj_len as usize,
|
||||
to_usize(obj_len)?,
|
||||
offset_size,
|
||||
64, // max recursion depth
|
||||
)
|
||||
@@ -572,11 +578,11 @@ impl FractalHeapHeader {
|
||||
) -> Result<Vec<u8>, FormatError> {
|
||||
if target_offset < block.heap_offset {
|
||||
return Err(FormatError::UnexpectedEof {
|
||||
expected: block.heap_offset as usize,
|
||||
available: target_offset as usize,
|
||||
expected: to_usize(block.heap_offset)?,
|
||||
available: to_usize(target_offset)?,
|
||||
});
|
||||
}
|
||||
let local_offset = (target_offset - block.heap_offset) as usize;
|
||||
let local_offset = to_usize(target_offset - block.heap_offset)?;
|
||||
if let Some(pipeline) = &self.filter_pipeline {
|
||||
let stored_len = usize::try_from(block.filtered_size)
|
||||
.map_err(|_| heap_error("direct block size"))?;
|
||||
@@ -673,7 +679,7 @@ impl FractalHeapHeader {
|
||||
return self.read_from_direct_block(
|
||||
file_data,
|
||||
DirectBlock {
|
||||
addr: child_addr as usize,
|
||||
addr: to_usize(child_addr)?,
|
||||
size: block_size,
|
||||
heap_offset: current_heap_offset,
|
||||
filtered_size,
|
||||
@@ -705,7 +711,7 @@ impl FractalHeapHeader {
|
||||
{
|
||||
return self.read_from_indirect_block(
|
||||
file_data,
|
||||
child_addr as usize,
|
||||
to_usize(child_addr)?,
|
||||
child_nrows,
|
||||
current_heap_offset,
|
||||
target_offset,
|
||||
@@ -719,7 +725,7 @@ impl FractalHeapHeader {
|
||||
}
|
||||
|
||||
Err(FormatError::UnexpectedEof {
|
||||
expected: target_offset as usize + length,
|
||||
expected: to_usize(target_offset)?.saturating_add(length),
|
||||
available: file_data.len(),
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user