format: implicit chunk index addresses over the maximum chunk grid
libhdf5 allocates an implicit index's chunks for the whole maximum extent and places chunk `scaled` at its row-major position in the maximum chunk grid (H5D__none_idx_get_addr, max_down_chunks). The reader used the current grid, so a dataset below its maximum shape with more chunk columns at its maximum read other chunks' values from the second chunk row on (h5py early allocation, fixed maxshape). generate_implicit_chunks_in_grid takes the maximum dimensions; generate_implicit_chunks keeps its signature (grid = current extent). Regression: implicit_chunks_use_the_maximum_grid here, and implicit_index_below_its_maximum_reads_like_libhdf5 (h5py file) with the editor tests. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -933,16 +933,40 @@ pub fn generate_implicit_chunks(
|
|||||||
dataset_dims: &[u64],
|
dataset_dims: &[u64],
|
||||||
chunk_dimensions: &[u32],
|
chunk_dimensions: &[u32],
|
||||||
element_size: u32,
|
element_size: u32,
|
||||||
|
) -> Vec<ChunkInfo> {
|
||||||
|
generate_implicit_chunks_in_grid(
|
||||||
|
base_address,
|
||||||
|
dataset_dims,
|
||||||
|
dataset_dims,
|
||||||
|
chunk_dimensions,
|
||||||
|
element_size,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`generate_implicit_chunks`] for a dataset whose maximum dimensions
|
||||||
|
/// (`max_dims`) exceed its current ones: libhdf5 allocates the chunks of
|
||||||
|
/// the whole maximum extent and places chunk `scaled` at its row-major
|
||||||
|
/// position in the *maximum* chunk grid (`H5D__none_idx_get_addr`,
|
||||||
|
/// `max_down_chunks`), so the current extent's chunks are not contiguous.
|
||||||
|
/// Only the chunks of the current extent are listed.
|
||||||
|
pub fn generate_implicit_chunks_in_grid(
|
||||||
|
base_address: u64,
|
||||||
|
dataset_dims: &[u64],
|
||||||
|
max_dims: &[u64],
|
||||||
|
chunk_dimensions: &[u32],
|
||||||
|
element_size: u32,
|
||||||
) -> Vec<ChunkInfo> {
|
) -> Vec<ChunkInfo> {
|
||||||
let rank = chunk_dimensions.len();
|
let rank = chunk_dimensions.len();
|
||||||
let chunk_byte_size: u64 =
|
let chunk_byte_size: u64 =
|
||||||
chunk_dimensions.iter().map(|&d| d as u64).product::<u64>() * element_size as u64;
|
chunk_dimensions.iter().map(|&d| d as u64).product::<u64>() * element_size as u64;
|
||||||
|
|
||||||
let mut num_chunks_per_dim = Vec::with_capacity(rank);
|
let mut num_chunks_per_dim = Vec::with_capacity(rank);
|
||||||
|
let mut grid_per_dim = Vec::with_capacity(rank);
|
||||||
for d in 0..rank {
|
for d in 0..rank {
|
||||||
let ds = dataset_dims[d];
|
|
||||||
let ch = chunk_dimensions[d] as u64;
|
let ch = chunk_dimensions[d] as u64;
|
||||||
num_chunks_per_dim.push(ds.div_ceil(ch));
|
let n = dataset_dims[d].div_ceil(ch);
|
||||||
|
num_chunks_per_dim.push(n);
|
||||||
|
grid_per_dim.push(max_dims.get(d).map_or(n, |m| m.div_ceil(ch)).max(n));
|
||||||
}
|
}
|
||||||
let total_chunks: u64 = num_chunks_per_dim.iter().product();
|
let total_chunks: u64 = num_chunks_per_dim.iter().product();
|
||||||
|
|
||||||
@@ -951,18 +975,22 @@ pub fn generate_implicit_chunks(
|
|||||||
for linear_idx in 0..total_chunks {
|
for linear_idx in 0..total_chunks {
|
||||||
let mut offsets = vec![0u64; rank];
|
let mut offsets = vec![0u64; rank];
|
||||||
let mut remaining = linear_idx;
|
let mut remaining = linear_idx;
|
||||||
|
let mut grid_idx = 0u64;
|
||||||
|
let mut down = 1u64;
|
||||||
for d in (0..rank).rev() {
|
for d in (0..rank).rev() {
|
||||||
let nchunks = num_chunks_per_dim[d];
|
let nchunks = num_chunks_per_dim[d];
|
||||||
let chunk_idx = remaining % nchunks;
|
let chunk_idx = remaining % nchunks;
|
||||||
remaining /= nchunks;
|
remaining /= nchunks;
|
||||||
offsets[d] = chunk_idx * chunk_dimensions[d] as u64;
|
offsets[d] = chunk_idx * chunk_dimensions[d] as u64;
|
||||||
|
grid_idx = grid_idx.saturating_add(chunk_idx.saturating_mul(down));
|
||||||
|
down = down.saturating_mul(grid_per_dim[d]);
|
||||||
}
|
}
|
||||||
|
|
||||||
chunks.push(ChunkInfo {
|
chunks.push(ChunkInfo {
|
||||||
chunk_size: chunk_byte_size as u32,
|
chunk_size: chunk_byte_size as u32,
|
||||||
filter_mask: 0,
|
filter_mask: 0,
|
||||||
offsets,
|
offsets,
|
||||||
address: base_address + linear_idx * chunk_byte_size,
|
address: base_address.saturating_add(grid_idx.saturating_mul(chunk_byte_size)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1153,9 +1181,13 @@ pub fn list_chunks(
|
|||||||
(4, Some(2)) => {
|
(4, Some(2)) => {
|
||||||
// Implicit index — use spatial chunk dims only
|
// Implicit index — use spatial chunk dims only
|
||||||
let spatial_chunk_dims: &[u32] = &chunk_dimensions[..rank];
|
let spatial_chunk_dims: &[u32] = &chunk_dimensions[..rank];
|
||||||
generate_implicit_chunks(
|
generate_implicit_chunks_in_grid(
|
||||||
addr,
|
addr,
|
||||||
&dataspace.dimensions,
|
&dataspace.dimensions,
|
||||||
|
dataspace
|
||||||
|
.max_dimensions
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or(&dataspace.dimensions),
|
||||||
spatial_chunk_dims,
|
spatial_chunk_dims,
|
||||||
elem_size as u32,
|
elem_size as u32,
|
||||||
)
|
)
|
||||||
@@ -3015,6 +3047,17 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A dataset below its maximum extent: libhdf5 lays chunks out over the
|
||||||
|
/// maximum chunk grid, so row 1 starts after a whole maximum row (here
|
||||||
|
/// 4 chunks), not after the current row of 3.
|
||||||
|
#[test]
|
||||||
|
fn implicit_chunks_use_the_maximum_grid() {
|
||||||
|
let chunks = generate_implicit_chunks_in_grid(0x100, &[2, 3], &[4, 4], &[1, 1], 4);
|
||||||
|
let addrs: Vec<u64> = chunks.iter().map(|c| (c.address - 0x100) / 4).collect();
|
||||||
|
assert_eq!(addrs, vec![0, 1, 2, 4, 5, 6]);
|
||||||
|
assert_eq!(chunks[3].offsets, vec![1, 0]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn implicit_chunks_partial_last() {
|
fn implicit_chunks_partial_last() {
|
||||||
// 25 elements, chunk size 10 => 3 chunks (last partial)
|
// 25 elements, chunk size 10 => 3 chunks (last partial)
|
||||||
|
|||||||
Reference in New Issue
Block a user