Repository navigation
perf(io): open the HDF5 file once per DataStub read - #910
Merged
Merged
Conversation
2 of 3 tasks
ehennestad
marked this pull request as ready for review
September 30, 2026 19:36
ehennestad
enabled auto-merge
September 30, 2026 19:36
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #910 +/- ##
=======================================
Coverage 95.30% 95.30%
=======================================
Files 239 239
Lines 8798 8802 +4
=======================================
+ Hits 8385 8389 +4
Misses 413 413 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
HDF5LazyArray.load_mat_style opened the file and dataset to get the dataspace, closed them, and opened both again to read. One open and close of the file and dataset costs about 0.24 ms, against about 1.2 ms for a whole small read. The dataspace now comes from the dataset that is opened for the read. The empty-selection path returns before anything is opened, because it reads through a nested load_mat_style call. Reading an 8x3 block from an 8x15000 dataset goes from 1.29 ms to 0.89 ms per call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
load_mat_style turned every single-subscript selection into a point selection with one point per element. Selecting all 211,722 elements of a 1-D dataset took 22 ms as points and 4 ms as one hyperslab. A selection that is one contiguous run of a one-dimensional dataset is now read as a hyperslab. Any other selection keeps the point selection, which is the faster one when there are gaps: for 2,500 short runs a point selection took 1 ms and a union of 2,500 hyperslabs 74 ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ehennestad
force-pushed
the
hdf5-lazy-array-open-once-per-read
branch
from
October 6, 2026 05:40
16a316b to
d3c3a53
Compare
1 task done
bendichter
approved these changes
Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Speeds up small
DataStubreads by opening the HDF5 file once per read instead of twice: 0.89 ms instead of 1.29 ms per read in the example.Problem — Code that reads a dataset from file in many small pieces pays a fixed cost on every read, and part of that cost is opening the file twice. In the example below, reading an 8x3 block from an 8x15000 dataset 1000 times takes 1.29 ms per read. Each read opens the file and the dataset to look up the dataspace, closes them, and then opens both again to read the data. Nothing fails, so the only symptom is that small reads are slower than they need to be. This showed up while profiling ragged-column reads for #909.
Solution — Each read now opens the file and the dataset once and uses them both to look up the dataspace and to read. The same reads take 0.89 ms each.
Additional change: read a contiguous range of a one-dimensional dataset as one block
Problem — Reading a range of a one-dimensional dataset through a
DataStub, such asstub(1:211722), builds one HDF5 point per element, and the selection is slow to build and walk. In the second example below, reading all 211,722 elements of a 1-D dataset takes 28.5 ms per read. The cause is thatload_mat_styleturns every single-subscript selection into a point selection, also when the selection is one contiguous range.Solution — A selection that is one contiguous range of a one-dimensional dataset is now read as one block (a hyperslab). The same read takes 5.6 ms. A selection with gaps is still read as points, which is the faster choice there.
Why in this PR — Both changes reduce the cost of a
load_mat_stylecall and edit the same function.What changed
DataStubread that selects data by index, such asstub(:, 1:3)orstub.load(1:10), opens the file once instead of twice. The data it returns is unchanged.stub(1:1000)or the index reads of a ragged column ingetRow, selects one block instead of one point per element. The data it returns is unchanged.Implementation notes
HDF5LazyArray.load_mat_stylegets the dataspace withH5D.get_spaceon the dataset it reads from, instead of calling the privategetSpacemethod, which opens and closes the file itself.refreshSizeInfostill usesgetSpace.load_mat_style(1)call.io.space.segmentSelection,io.space.findShapes), type handling inhdf2mat, and cleanup ofH5ML.idhandles. perf(io): read several DataStub selections in one pass #912 avoids that work forgetRowby reading several selections in one pass.H5S.select_hyperslab; any other single subscript is selected withH5S.select_elementsas before. On a 211,722-element dataset, selecting all elements took 22 ms as points and 4 ms as one hyperslab. For 2,500 short runs with gaps, points took 1 ms and a union of 2,500 hyperslabs 74 ms, so selections with gaps stay as points.H5S.select_hyperslabtakes only double-precision arguments, and a selection can arrive as another integer class:getRowonmainpassesuint64indices. The start is therefore converted to double.Examples
Many small reads from one dataset
The snippet writes an 8x15000 dataset, then reads a random 8x3 block from it 1000 times through a
DataStuband reports the time per read.Before — Each read opens the file twice and takes 1.29 ms.
After — Each read opens the file once and takes 0.89 ms.
Timings are from one machine and vary with platform and disk.
One long read of a one-dimensional dataset
The snippet writes a 1-D dataset of 211,722 values, then reads all of it 20 times through a
DataStuband reports the time per read.Before — The range is read as 211,722 separate points, and each read takes 28.5 ms.
After — The range is read as one block, and each read takes 5.6 ms.
How to test
Run the snippets above on
mainand on this branch and compare thetime per readlines.tests.unit.io.backend.HDF5LazyArrayTest/loadMatStyleReadsOneDimensionalSelectionschecks one-dimensional reads with and without gaps, including auint64selection, against the same indexing in memory. The other read paths are covered by the existing tests, which pass unchanged:Related pull requests
perf-find-shapes-contiguous-runs, in stack #925) rewrites the read path this PR changes. This PR opens the file and dataset once at the top ofload_mat_style; perf(io): read gapped multi-dimensional selections in one call #923 moves the read into a local functionreadSelectionand addsreadInGroups. Both PRs also add tests at the end ofHDF5LazyArrayTest. Whichever of the two merges second gets a conflict inload_mat_style.mandHDF5LazyArrayTest.m.Checklist
fix #XXwhereXXis the issue number?🤖 Generated with Claude Code