From bf4aefcd00abd5629701a207fc42f0fb9d494973 Mon Sep 17 00:00:00 2001 From: osobh Date: Sat, 26 Sep 2026 11:22:07 -0500 Subject: [PATCH] format: a one-thread pool decodes on the caller only; keep 1 MiB scratch Review follow-ups. With run_with_helpers a one-thread rayon pool gave each read a second core (the caller plus the worker), so --decode-threads 1 no longer matched h5py's one core per call; such a pool now adds no helper. Per-thread decode scratch is kept up to 1 MiB per buffer (was 4 MiB), bounding what never-exiting pool workers hold. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/clawhdf5-format/src/filters.rs | 7 +++++-- crates/clawhdf5-format/src/parallel_read.rs | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/clawhdf5-format/src/filters.rs b/crates/clawhdf5-format/src/filters.rs index 872b8d8..266e530 100644 --- a/crates/clawhdf5-format/src/filters.rs +++ b/crates/clawhdf5-format/src/filters.rs @@ -183,8 +183,11 @@ enum Stage { } impl DecodeScratch { - /// Largest buffer [`trim`](Self::trim) keeps (4 MiB). - pub const RETAIN_BYTES: usize = 4 << 20; + /// Largest buffer [`trim`](Self::trim) keeps (1 MiB): enough for common + /// chunk sizes (a 256 x 256 `f32` chunk is 256 KiB) while bounding what + /// every long-lived thread (rayon's workers never exit) holds on to, at + /// two buffers each. + pub const RETAIN_BYTES: usize = 1 << 20; /// An empty scratch; buffers are allocated on first use. pub fn new() -> Self { diff --git a/crates/clawhdf5-format/src/parallel_read.rs b/crates/clawhdf5-format/src/parallel_read.rs index 61d06cd..d9927c5 100644 --- a/crates/clawhdf5-format/src/parallel_read.rs +++ b/crates/clawhdf5-format/src/parallel_read.rs @@ -47,6 +47,12 @@ pub fn pool_can_parallelise() -> bool { /// item beyond the caller's first. pub(crate) fn helper_count(items: usize) -> usize { let pool = rayon::current_num_threads(); + // A one-thread pool means "decode on the calling thread" (the setting + // benchmarks use to compare with h5py, where each call decodes on its + // caller): no helper, so one read never uses two cores. + if pool <= 1 { + return 0; + } let others = if rayon::current_thread_index().is_some() { pool.saturating_sub(1) } else {