perf(py): read an index list one group of chunks at a time
Each run of consecutive indices was its own uncached hyperslab read, so a list over a compressed chunked dataset decoded the same chunk once per run (d[range(0, 200000, 40)] over 20 gzip chunks: 8 s, h5py 0.014 s). Plan::reads now groups the indices — a group ends only where a whole chunk holds no selected index, or, unchunked, at a gap over 64 KiB — and the selected rows are gathered from each group's block in Rust. Now 3.8 ms (h5py 4.1 ms, release, tank). The new test (1-D, 2-D and contiguous, compared with h5py, 2 s bound) took 5.8 s before. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -31,6 +31,8 @@ pub struct PyDataset {
|
||||
path: String,
|
||||
/// `None` for a dataset with a null dataspace (h5py's `Empty`).
|
||||
shape: Option<Vec<u64>>,
|
||||
/// The chunk shape, for a chunked dataset.
|
||||
chunks: Option<Vec<u64>>,
|
||||
datatype: Datatype,
|
||||
/// Why the datatype cannot be read into numpy, if it cannot.
|
||||
conv: Result<Converter, String>,
|
||||
@@ -56,10 +58,14 @@ impl PyDataset {
|
||||
};
|
||||
let conv = Converter::new(py, &datatype, file.superblock().offset_size)
|
||||
.map_err(|e| e.value(py).to_string());
|
||||
let chunks = shape
|
||||
.as_ref()
|
||||
.and_then(|s| node::chunk_shape(&file, &hdr, s.len()));
|
||||
Ok(Self {
|
||||
file,
|
||||
path,
|
||||
shape,
|
||||
chunks,
|
||||
datatype,
|
||||
conv,
|
||||
})
|
||||
@@ -81,17 +87,23 @@ impl PyDataset {
|
||||
let arr = if plan.is_empty() {
|
||||
conv.empty(py, &out_shape)?
|
||||
} else {
|
||||
let (reads, list_axis) = plan.reads(dims);
|
||||
let (vl, elem_size, unit) = (conv.is_vl(), conv.elem_size, conv.vl_unit);
|
||||
let list_axis = plan.list_axis();
|
||||
let chunk_len = match (&self.chunks, list_axis) {
|
||||
(Some(c), Some(a)) => c.get(a).copied(),
|
||||
_ => None,
|
||||
};
|
||||
let (reads, list_axis) = plan.reads(dims, chunk_len, elem_size);
|
||||
let read_shape = plan.read_shape();
|
||||
let file = &*self.file;
|
||||
let path = self.path.as_str();
|
||||
let (vl, elem_size, unit) = (conv.is_vl(), conv.elem_size, conv.vl_unit);
|
||||
// Everything below touches only Rust data: release the GIL.
|
||||
let read = || -> Result<Elements, ReadError> {
|
||||
let ds = file.dataset(path)?;
|
||||
let mut blocks = Vec::with_capacity(reads.len());
|
||||
for (sel, shape) in reads {
|
||||
let raw = ds.read_selection(&sel)?;
|
||||
for read in reads {
|
||||
let raw = ds.read_selection(&read.sel)?;
|
||||
let mut shape = read.shape;
|
||||
let want = shape.iter().product::<usize>() * elem_size;
|
||||
if raw.len() != want {
|
||||
return Err(ReadError::Other(format!(
|
||||
@@ -99,6 +111,14 @@ impl PyDataset {
|
||||
raw.len()
|
||||
)));
|
||||
}
|
||||
let raw = match (&read.pick, list_axis) {
|
||||
(Some(pick), Some(axis)) => {
|
||||
let kept = select::gather_along(&raw, &shape, axis, pick, elem_size);
|
||||
shape[axis] = pick.len();
|
||||
kept
|
||||
}
|
||||
_ => raw,
|
||||
};
|
||||
blocks.push((raw, shape));
|
||||
}
|
||||
// Several blocks only for a list index: join their bytes
|
||||
|
||||
Reference in New Issue
Block a user