writer: v2 B-trees with internal nodes (no 65 535-record limit)
Dense link and attribute indexes and the chunk index for several unlimited dimensions were single leaves, capping them at 65 535 records. btree_v2_write builds trees of any depth, with node capacities and pointer widths from libhdf5's H5B2__hdr_init arithmetic (now shared with the reader as btree_v2::node_info) and libhdf5's node sizes (512 dense, 2048 chunks). Indexes that fit the old one-leaf layout are written byte for byte as before (compared for 10..65 535 links, attrs and chunks, tracked and filtered). Tests: 100 000 links (short names; long names with creation order), 70 000 attributes, 200 000 chunks (and 80 000 deflated), read by h5py, h5dump and clawhdf5 and edited by h5py r+; h5rs check on the same shapes, asserting depths 2-3. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -6,6 +6,7 @@ extern crate alloc;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{format, vec, vec::Vec};
|
||||
|
||||
use crate::btree_v2_write::{BTreeV2Params, build_btree_v2};
|
||||
use crate::checksum::jenkins_lookup3;
|
||||
use crate::chunk_cache::{CACHE_LINE_SIZE, align_to_cache_line};
|
||||
use crate::chunk_grid::ChunkGrid;
|
||||
@@ -1105,11 +1106,12 @@ const BT2_CHUNK_FILTERED: u8 = 11;
|
||||
///
|
||||
/// `records` are `(scaled coordinates, chunk)` in lexicographic order of the
|
||||
/// coordinates, which is the order the library's comparator
|
||||
/// (`H5VM_vector_cmp_u`) keeps them in. The tree is a single leaf: the
|
||||
/// library's 2048-byte node when the records fit, otherwise a leaf node
|
||||
/// sized to hold them all (the root's record count is 16-bit, so at most
|
||||
/// 65535 chunks). Returns the bytes and the node size the layout message
|
||||
/// must record.
|
||||
/// (`H5VM_vector_cmp_u`) keeps them in. Up to 65 535 chunks go in a single
|
||||
/// leaf: the library's 2048-byte node when the records fit, otherwise a leaf
|
||||
/// node sized to hold them all (the layout the writer has always used, kept
|
||||
/// so those files do not change). More chunks get the library's 2048-byte
|
||||
/// nodes with internal nodes above the leaves. Returns the bytes and the
|
||||
/// node size the layout message must record.
|
||||
fn build_btree_v2_chunk_index_at(
|
||||
rank: usize,
|
||||
records: &[(Vec<u64>, &WrittenChunk)],
|
||||
@@ -1119,73 +1121,49 @@ fn build_btree_v2_chunk_index_at(
|
||||
base_address: u64,
|
||||
) -> Result<(Vec<u8>, u32), FormatError> {
|
||||
let os = offset_size as usize;
|
||||
let nrec = u16::try_from(records.len()).map_err(|_| {
|
||||
FormatError::ChunkedReadError(
|
||||
"more than 65535 chunks with more than one unlimited dimension: \
|
||||
use larger chunks"
|
||||
.into(),
|
||||
)
|
||||
})?;
|
||||
let chunk_size_bytes = has_filters.then(|| {
|
||||
let slots: Vec<Option<WrittenChunk>> =
|
||||
records.iter().map(|(_, c)| Some((*c).clone())).collect();
|
||||
filtered_chunk_size_len(&slots)
|
||||
});
|
||||
let record_size = os + chunk_size_bytes.map_or(0, |n| n + 4) + 8 * rank;
|
||||
// Leaf: signature, version, type, records, checksum.
|
||||
let leaf_len = 4 + 1 + 1 + records.len() * record_size + 4;
|
||||
let node_size = u32::try_from(leaf_len)
|
||||
.map_err(|_| FormatError::Overflow("B-tree v2 leaf size".into()))?
|
||||
.max(BT2_NODE_SIZE);
|
||||
let record_size_u16 = u16::try_from(record_size)
|
||||
.map_err(|_| FormatError::Overflow("B-tree v2 record size".into()))?;
|
||||
let node_size = if records.len() <= usize::from(u16::MAX) {
|
||||
// Leaf: signature, version, type, records, checksum.
|
||||
let leaf_len = 4 + 1 + 1 + records.len() * record_size + 4;
|
||||
u32::try_from(leaf_len)
|
||||
.map_err(|_| FormatError::Overflow("B-tree v2 leaf size".into()))?
|
||||
.max(BT2_NODE_SIZE)
|
||||
} else {
|
||||
BT2_NODE_SIZE
|
||||
};
|
||||
let tree_type = if has_filters {
|
||||
BT2_CHUNK_FILTERED
|
||||
} else {
|
||||
BT2_CHUNK_UNFILTERED
|
||||
};
|
||||
|
||||
let hdr_len = 4 + 1 + 1 + 4 + 2 + 2 + 1 + 1 + os + 2 + length_size as usize + 4;
|
||||
let leaf_address = base_address + hdr_len as u64;
|
||||
|
||||
let mut out = Vec::with_capacity(hdr_len + node_size as usize);
|
||||
out.extend_from_slice(b"BTHD");
|
||||
out.push(0); // version
|
||||
out.push(tree_type);
|
||||
out.extend_from_slice(&node_size.to_le_bytes());
|
||||
out.extend_from_slice(&(record_size as u16).to_le_bytes());
|
||||
out.extend_from_slice(&0u16.to_le_bytes()); // depth
|
||||
out.push(BT2_SPLIT_PERCENT);
|
||||
out.push(BT2_MERGE_PERCENT);
|
||||
if records.is_empty() {
|
||||
out.extend(core::iter::repeat_n(0xFF, os));
|
||||
} else {
|
||||
push_addr(&mut out, leaf_address, offset_size);
|
||||
}
|
||||
out.extend_from_slice(&nrec.to_le_bytes());
|
||||
match length_size {
|
||||
4 => out.extend_from_slice(&(records.len() as u32).to_le_bytes()),
|
||||
_ => out.extend_from_slice(&(records.len() as u64).to_le_bytes()),
|
||||
}
|
||||
let sum = jenkins_lookup3(&out);
|
||||
out.extend_from_slice(&sum.to_le_bytes());
|
||||
debug_assert_eq!(out.len(), hdr_len);
|
||||
if records.is_empty() {
|
||||
return Ok((out, node_size));
|
||||
}
|
||||
|
||||
let leaf_start = out.len();
|
||||
out.extend_from_slice(b"BTLF");
|
||||
out.push(0); // version
|
||||
out.push(tree_type);
|
||||
let mut flat = Vec::with_capacity(records.len() * record_size);
|
||||
for (scaled, chunk) in records {
|
||||
push_index_element(&mut out, Some(chunk), offset_size, chunk_size_bytes);
|
||||
push_index_element(&mut flat, Some(chunk), offset_size, chunk_size_bytes);
|
||||
for &c in scaled {
|
||||
out.extend_from_slice(&c.to_le_bytes());
|
||||
flat.extend_from_slice(&c.to_le_bytes());
|
||||
}
|
||||
}
|
||||
let sum = jenkins_lookup3(&out[leaf_start..]);
|
||||
out.extend_from_slice(&sum.to_le_bytes());
|
||||
// The library reads whole nodes; pad the leaf out to the node size.
|
||||
out.resize(leaf_start + node_size as usize, 0);
|
||||
let out = build_btree_v2(
|
||||
BTreeV2Params {
|
||||
tree_type,
|
||||
node_size,
|
||||
record_size: record_size_u16,
|
||||
split_percent: BT2_SPLIT_PERCENT,
|
||||
merge_percent: BT2_MERGE_PERCENT,
|
||||
},
|
||||
&flat,
|
||||
base_address,
|
||||
offset_size,
|
||||
length_size,
|
||||
)?;
|
||||
Ok((out, node_size))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user