Memoize proxy locations in produce() with epoch invalidation#54
Merged
Conversation
Korijn
force-pushed
the
claude/produce-hotpath
branch
2 times, most recently
from
July 11, 2026 10:46
1d172c7 to
044f769
Compare
Collaborator
Author
|
@berendkleinhaneveld I'll let you review & approve this one |
Korijn
force-pushed
the
claude/produce-hotpath
branch
from
July 11, 2026 10:54
044f769 to
f5e4fe8
Compare
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
force-pushed
the
claude/produce-hotpath
branch
from
July 11, 2026 11:00
f5e4fe8 to
fcd34e0
Compare
berendkleinhaneveld
approved these changes
Jul 13, 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.
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)_adopt(move semantics)_shift_cache(list index shifts from insert/del/slice ops)sort()/reverse()re-keyingfinalize's root detach (so a leaked proxy can't record through a stale cache)Proxy chains are strongly held through the
_proxiesregistries 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-deepgroup): 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: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