perf(streaming): memoize per-chunk metadata on the read hot path#848
Merged
Conversation
Profiling the streaming read loop (local dataset, num_workers=0) showed two
per-item costs that are actually invariant per chunk:
- `ChunksConfig.__getitem__` rebuilt the local chunk path with `os.path.join`
(+ decompression-suffix stripping) on *every* item, even though the path,
begin offset and byte size depend only on `chunk_index`.
- `PyTreeLoader.deserialize` did a serializer dict lookup per leaf and a
`config["data_spec"]` lookup per item.
Memoize the `__getitem__` tuple per `chunk_index` (invariant once the config is
loaded) and precompute the per-leaf serializer list + data spec in
`BaseItemLoader.setup`. No behavioural change.
Measured on a local dataset (40k items, dict of a float32 array + int, chunk_size
2000), median over 5 runs, num_workers=0:
before: ~104,800 items/s
after: ~110,600 items/s (~+6%)
The win is per-item decode CPU, so it helps most when decode-bound (num_workers=0,
or many workers saturating cores). Verified no regressions across
test_reader / test_config / test_item_loader / test_serializer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #848 +/- ##
===================================
Coverage 81% 81%
===================================
Files 54 54
Lines 7683 7736 +53
===================================
+ Hits 6210 6262 +52
- Misses 1473 1474 +1 🚀 New features to boost your workflow:
|
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.
Summary
Small, safe read-path speedup found by profiling the streaming iteration loop. Two per-item costs turned out to be invariant per chunk and are now memoized. No behavioural change.
How it was found
Profiled a pure local read (no network,
num_workers=0) with cProfile overStreamingDataset+StreamingDataLoaderiteration. The hottest per-item entries that weren't intrinsic work (deserialize / file IO / pytree) were:posixpath.join— 40k calls (one per item) fromChunksConfig.__getitem__, even though the chunk path depends only onchunk_index.config["data_spec"]lookup per item inPyTreeLoader.deserialize.Changes
ChunksConfig.__getitem__memoizes its(local_path, begin, filesize)tuple perchunk_index. These are all fixed once the config is loaded (_cache_dir,_chunks,_intervals,_compressornever change), so this is a pure lookup cache — no invalidation needed.BaseItemLoader.setupprecomputes the per-leaf serializer list and the data spec;deserializeuses them instead of re-looking-up a dict per leaf and the config per item.Benchmark
Local dataset, 40k items (
{"x": float32[128], "y": int}),chunk_size=2000,num_workers=0, median of 5 runs:The gain is per-item decode CPU, so it helps most when decode-bound (
num_workers=0, or many workers saturating cores); it won't move an I/O- or GPU-bound run. Reproduced repeatedly; theposixpath.joinper-item calls disappear from the profile after the change.Follow-ups (not in this PR)
The remaining per-item hot spots are the vendored pytree
unflatten(~16%, deliberately left alone — it mirrors torch's pytree) and the 2seek+2readper item in_load_data. A future change could cache each chunk's offset table on open to halve the per-item file syscalls; it touches the core read path so it deserves its own reviewed PR.Verification
No regressions:
test_reader.py,test_config.py,test_item_loader.py,test_serializer.py(48 passed). ruff + mypy clean.🤖 Generated with Claude Code