Skip to content

perf(streaming): memoize per-chunk metadata on the read hot path#848

Merged
tchaton merged 1 commit into
mainfrom
perf/read-hot-path
Jul 24, 2026
Merged

perf(streaming): memoize per-chunk metadata on the read hot path#848
tchaton merged 1 commit into
mainfrom
perf/read-hot-path

Conversation

@tchaton

@tchaton tchaton commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

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 over StreamingDataset + StreamingDataLoader iteration. The hottest per-item entries that weren't intrinsic work (deserialize / file IO / pytree) were:

  • posixpath.join40k calls (one per item) from ChunksConfig.__getitem__, even though the chunk path depends only on chunk_index.
  • serializer dict lookups per leaf and a config["data_spec"] lookup per item in PyTreeLoader.deserialize.

Changes

  • ChunksConfig.__getitem__ memoizes its (local_path, begin, filesize) tuple per chunk_index. These are all fixed once the config is loaded (_cache_dir, _chunks, _intervals, _compressor never change), so this is a pure lookup cache — no invalidation needed.
  • BaseItemLoader.setup precomputes the per-leaf serializer list and the data spec; deserialize uses 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:

items/s
before ~104,800
after ~110,600
~+6%

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; the posixpath.join per-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 2 seek+2 read per 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

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>
@tchaton
tchaton requested a review from justusschock as a code owner July 24, 2026 12:16
@codecov-commenter

codecov-commenter commented Jul 24, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 87.50000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 81%. Comparing base (ac1b064) to head (c9a0f6e).
⚠️ Report is 2 commits behind head on main.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

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:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tchaton
tchaton merged commit ecbb64d into main Jul 24, 2026
35 checks passed
@tchaton
tchaton deleted the perf/read-hot-path branch July 24, 2026 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants