Skip to content

Memoize proxy locations in produce() with epoch invalidation#54

Merged
berendkleinhaneveld merged 1 commit into
masterfrom
claude/produce-hotpath
Jul 13, 2026
Merged

Memoize proxy locations in produce() with epoch invalidation#54
berendkleinhaneveld merged 1 commit into
masterfrom
claude/produce-hotpath

Conversation

@Korijn

@Korijn Korijn commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

Track E, PR 10 (foundational) — the produce() hot-path design pass.

What

produce()'s proxies compute their path by walking parent links to the root on every recorded write, so writes into deep trees paid an O(depth) walk plus a list build/reverse/tuple conversion each time.

_location() now memoizes its result per proxy and reuses the parent's memoized location recursively (so ancestors' caches are shared across siblings). Repeated writes into a stable tree pay O(1) per write.

Why it stays correct

The whole point of parent-link paths is that proxies move (list shifts, adoption). Correctness is preserved by an epoch counter on the PatchRecorder: every structural change that can move or orphan a proxy bumps it, invalidating all memoized locations at once —

  • _detach / _detach_all (removals, replacements)
  • re-attach through _adopt (move semantics)
  • _shift_cache (list index shifts from insert/del/slice ops)
  • sort() / reverse() re-keying
  • finalize's root detach (so a leaked proxy can't record through a stale cache)

Proxy chains are strongly held through the _proxies registries while the draft is alive, so a parent weakref can only die after finalize — which always bumps the epoch first. The produce test suite (live-paths, detach/adopt, proxy-leak modules) exercises all of these invalidation paths; 405 tests pass at 100% coverage, ruff and ty clean.

Benchmarks

The suite had no deep-tree produce cases, so this PR adds two (produce-deep group): repeated leaf writes at depth 12, and field updates across a list of nested dicts. Interleaved medians vs master, 2 runs each, PYTHONHASHSEED=0, GC off:

benchmark master this PR change
produce_deep_leaf_writes[copy] 173.9µs 109.1µs −36.3%
produce_deep_leaf_writes[in_place] 167.3µs 103.6µs −37.4%
produce_deep_nested_mutation (1 write/call) 35.6µs 36.8µs +2.4% (pays the cache store without reusing it)
flat dict / list / set / observ workloads within noise (±2%, worst single case +3.8%)

Docs

The Internals produce section now describes the memoization and its invalidation rule.

🤖 Generated with Claude Code

https://claude.ai/code/session_016Mu9vEBwU4fQLi8ZgkgdS2


Generated by Claude Code

@Korijn
Korijn force-pushed the claude/produce-hotpath branch 2 times, most recently from 1d172c7 to 044f769 Compare July 11, 2026 10:46
@Korijn

Korijn commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator Author

@berendkleinhaneveld I'll let you review & approve this one

@Korijn
Korijn force-pushed the claude/produce-hotpath branch from 044f769 to f5e4fe8 Compare July 11, 2026 10:54
produce()'s proxies compute their path by walking parent links to the
root on every recorded write, so writes into deep trees paid O(depth)
walks plus a list build/reverse/tuple conversion each time.

_location() now memoizes its result per proxy and reuses the parent's
memoized location recursively, so repeated writes into a stable tree
pay O(1) per write. Correctness is preserved by an epoch counter on
the PatchRecorder: every structural change that can move or orphan a
proxy — detach, detach_all, re-attach through _adopt, list index
shifts (_shift_cache), sort/reverse re-keying, and finalize's root
detach — bumps the epoch, which invalidates every memoized location
at once. Proxy chains are strongly held through the _proxies
registries while the draft is alive, so a parent weakref can only die
after finalize, which always bumps the epoch first.

Adds two benchmarks for the shapes this targets: repeated leaf writes
at depth 12, and field updates across a list of nested dicts.

Interleaved benchmark medians vs master (2 runs each):

  produce_deep_leaf_writes[copy]      173.9us -> 109.1us  (-36.3%)
  produce_deep_leaf_writes[in_place]  167.3us -> 103.6us  (-37.4%)
  single deep write per call          ~+2% (pays the cache store
                                      without reusing it)
  flat dict/list/set workloads        within noise (+/-2%)

The internals docs (produce section) now describe the memoization.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Mu9vEBwU4fQLi8ZgkgdS2
@Korijn
Korijn force-pushed the claude/produce-hotpath branch from f5e4fe8 to fcd34e0 Compare July 11, 2026 11:00
@berendkleinhaneveld
berendkleinhaneveld merged commit 0e9346b into master Jul 13, 2026
11 checks passed
@berendkleinhaneveld
berendkleinhaneveld deleted the claude/produce-hotpath branch July 13, 2026 07:12
@Korijn Korijn mentioned this pull request Jul 13, 2026
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.

3 participants