Skip to content

perf(store): O(1) ensure_metadata, staleness-gated upserts, parallel batch extraction - #192

Closed
nwaughachukwuma wants to merge 1 commit into
add-report-flag-for-cat-commandfrom
claude/core-perf-optimization-s230yb
Closed

perf(store): O(1) ensure_metadata, staleness-gated upserts, parallel batch extraction#192
nwaughachukwuma wants to merge 1 commit into
add-report-flag-for-cat-commandfrom
claude/core-perf-optimization-s230yb

Conversation

@nwaughachukwuma

@nwaughachukwuma nwaughachukwuma commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Base PR of the RSI batch, stacked on #181. Storage write path only (12 files); the query-plan and grep/cat/cold-start work land as two child PRs into this branch. Hashing-policy changes are excluded from the batch.

  • ensure_metadata is O(1): single-file Rust scan_one + extract_metadata_one replaces a full parent-directory Scanner walk + Arrow round-trip per file (it ran twice per mm cat file).
  • upsert_files staleness gate: snapshots (modified, size, content_hash) and re-extracts only new/changed rows.
  • Scanner.extract_metadata_batch: rayon-parallel extraction with the GIL released, one FFI crossing instead of N; O(1) path index replaces the per-call linear entry scan (O(n²) across a save).

Speed gains

End-to-end (4-core VM; 340-file fixture: 300 × 200-line .py + 40 PNGs; baseline = #181 head):

Metric Baseline This PR Speedup
Context.save() warm 212 ms 8 ms 26x
Context.save() cold 1,220 ms 175 ms 7x
cat 1 fresh file, 340-sibling dir 96 ms 18 ms 5.5x (O(dirsize) → O(1))
serial 100-file cat extract loop 736 ms 162 ms cold / 5 ms warm 4.5x / 147x

Per-change A/B (new vs replaced path, same build):

Change Old New Speedup
scan_single vs parent-dir walk (Criterion, 50 siblings) 1.49 ms 1.37 µs ~1,090x
scan_one vs Scanner + Arrow (1,000 siblings) 2.41 ms 2.9 µs ~830x
batch vs serial extraction (200 × 8 KB .py) 4.39 ms 1.37 ms 3.2x
batch vs serial (300 realistic .py / 40 PNGs) 57 / 189 ms 3.4 / 51 ms 17x / 3.7x

Documented tradeoff (surfaced by the A/B benches): extract_metadata_batch has ~3-4 ms fixed rayon dispatch per call, so trees of near-empty files favor the serial loop in isolation; it's a per-save constant and end-to-end saves still win.

Validation

  • 1,208 Python + 106 Rust tests pass; clippy/ruff clean (2 pre-existing TestRecordingFile env failures fail identically on the base commit).
  • New: Criterion scan_single_vs_dir_walk; pytest-benchmarks for warm/cold save, ensure_metadata, and the batch/scan A/B pairs. Full tables in benchmarks/mm-bench-260803.md.
cargo bench -p mm-core --bench metadata_extract -- scan_single_vs_dir_walk
uv run pytest tests/python/test_benchmark.py -m slow -k "save or ensure_metadata or ab_" --benchmark-only

🤖 Generated with Claude Code

https://claude.ai/code/session_012GnYsrLTmdJphKkm7yjWDV

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

…batch extraction

- ensure_metadata: single-file Rust scan_one + extract_metadata_one instead
  of a full parent-directory scan + Arrow round-trip per file (hit twice per
  mm cat file). 96ms → 18ms per fresh file at 340 siblings; sibling scaling
  removed entirely.
- upsert_files: snapshot (modified, size, content_hash) before upserting and
  re-extract only new/changed rows. Warm Context.save() 212ms → 8ms (26x),
  cold save 1220ms → 175ms (7x) on the 340-file fixture.
- New Scanner.extract_metadata_batch: rayon-parallel extraction with the GIL
  released, one FFI crossing instead of N (3.2-17x vs the serial loop); O(1)
  path index replaces the per-call linear entry scan (O(n²) across a save).
- Benchmarks: Criterion scan_single_vs_dir_walk group; pytest-benchmarks for
  warm/cold save, ensure_metadata, and batch-vs-serial / scan_one-vs-walk
  A/B pairs. Numbers in benchmarks/mm-bench-260803.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GnYsrLTmdJphKkm7yjWDV
@nwaughachukwuma
nwaughachukwuma force-pushed the claude/core-perf-optimization-s230yb branch from efb91ce to 9ce1d7e Compare August 3, 2026 12:49
@nwaughachukwuma nwaughachukwuma changed the title perf: RSI batch — storage write path, query plans, grep+cat overhead, cold start perf(store): O(1) ensure_metadata, staleness-gated upserts, parallel batch extraction Aug 3, 2026

@nwaughachukwuma nwaughachukwuma left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude, please follow-up

Comment thread python/mm/commands/grep.py Outdated
Comment on lines +104 to +130
import json as json_mod

from mm._mm import Scanner

scanner = Scanner(str(scan_root), None, no_ignore=no_ignore)
scanner.scan()
exts = [e.strip() for e in ext.split(",")] if ext else []
rows = json_mod.loads(
scanner.to_json_fast(kind=kind, ext=exts[0] if len(exts) == 1 else None)
)
for row in rows:
if row["path"].startswith("."):
continue
if len(exts) > 1 and row["ext"] not in exts:
continue
resolved = str((_directory.resolve() / f.path).resolve())
resolved = str(scan_root / row["path"])
if resolved not in seen_paths:
seen_paths.add(resolved)
files_to_search.append(f)
files_to_search.append(
FileEntry(
row=dict(
path=row["path"],
kind=row["kind"],
is_binary=row["is_binary"],
)
)
)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude, I hope you benchmarked this replacement and the gain is both significant, stable and reliable.

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