Chunked reads beat an h5py process pool; unlimited writer B-trees; Blosc2; 599/697 conformance #16

Merged
osobh merged 48 commits from feat/p2b-scale into main 2026-09-26 17:42:16 +00:00
2 changed files with 57 additions and 2 deletions
Showing only changes of commit d9e4dfb6e6 - Show all commits
@@ -218,12 +218,20 @@ pub(crate) fn bitshuffle_decode(
}
/// Decode Zstandard frames into exactly `dst`, failing if they hold more.
///
/// ruzstd reserves a frame's declared window (by default up to 100 MiB)
/// before decoding it, so the window is capped at what the output could
/// need: twice `dst` (window sizes are rounded up), and at least 128 KiB.
/// The encoders behind these filters (c-blosc, c-blosc2, bitshuffle)
/// compress each block in one call with its size known, so libzstd's
/// window never exceeds the block.
#[cfg(any(feature = "bitshuffle", feature = "blosc"))]
pub(crate) fn zstd_decode_into(
decoder: &mut ruzstd::decoding::FrameDecoder,
frames: &[u8],
dst: &mut [u8],
) -> Result<usize, FormatError> {
decoder.set_max_window_size((2 * dst.len()).max(1 << 17) as u64);
decoder
.decode_all(frames, dst)
.map_err(|e| FormatError::DecompressionError(format!("zstd: {e}")))
@@ -61,9 +61,11 @@ fn peak_during<T>(f: impl FnOnce() -> T) -> (T, usize) {
/// What decoding one HDF5 chunk of `limit` bytes from `input` may hold at
/// once: the output, a few blocks of scratch (each no larger than the
/// output), the offsets table, and the Zstandard decoder's state.
/// output), the offsets table, and the Zstandard decoder's state, which has
/// a fixed ceiling: a window of at most 128 KiB (or twice the stream) and a
/// block's table of sequences (up to 98,303 of 12 bytes, 1.2 MB).
fn bound(limit: usize, input: &[u8]) -> usize {
6 * limit + 2 * input.len() + (1 << 20)
6 * limit + 2 * input.len() + (2 << 20)
}
fn lock() -> std::sync::MutexGuard<'static, ()> {
@@ -348,3 +350,48 @@ fn b2nd_chunk_larger_than_the_array_is_not_allocated() {
r.map(|v| v.len())
);
}
/// ruzstd reserves a frame's declared window (up to 100 MiB) before it
/// decodes a frame with a decoder it has used before: a Blosc2 chunk of
/// two 16-byte Zstandard streams, each declaring a 96 MiB window,
/// allocated 96 MiB. c-blosc2 compresses each block with its size known,
/// so its windows never exceed the block.
#[test]
fn zstd_window_is_bounded_by_the_output() {
let _g = lock();
let mut z = 0xfd2f_b528u32.to_le_bytes().to_vec();
// No single segment, no checksum; window 2^26 + 4/8 of it = 96 MiB.
z.extend_from_slice(&[0x00, (16 << 3) | 4]);
// One raw block, last, of 16 bytes.
let h = 1 | (16 << 3);
z.extend_from_slice(&[h as u8, (h >> 8) as u8, 0]);
z.extend_from_slice(&[7; 16]);
// Two blocks of 16 bytes, one stream each (not split), Zstandard
// (codec 4).
let chunk = |z: &[u8]| {
let mut c = vec![5u8, 1, 0x10 | (4 << 5), 1];
for v in [32i32, 16, 0] {
c.extend_from_slice(&v.to_le_bytes());
}
let first = 24 + 4 + z.len();
c.extend_from_slice(&24i32.to_le_bytes());
c.extend_from_slice(&(first as i32).to_le_bytes());
for _ in 0..2 {
c.extend_from_slice(&(z.len() as i32).to_le_bytes());
c.extend_from_slice(z);
}
let n = c.len() as i32;
c[12..16].copy_from_slice(&n.to_le_bytes());
c
};
let c = chunk(&z);
let (r, peak) = peak_during(|| blosc2_decompress_chunk(&c, 32));
assert!(peak <= bound(32, &c), "peak {peak} bytes ({r:?})");
assert!(r.is_err(), "{r:?}");
// The same streams with a window they can use read.
z[5] = 0;
assert_eq!(
blosc2_decompress_chunk(&chunk(&z), 32).unwrap(),
vec![7; 32]
);
}