Skip to content

perf: memoize wiring plans on the registry, shared tree-wide - #326

Merged
lesnik512 merged 1 commit into
mainfrom
wiring-plan-registry-memo
Jul 15, 2026
Merged

lesnik512 merged 1 commit into
mainfrom
wiring-plan-registry-memo

Conversation

@lesnik512

Copy link
Copy Markdown
Member

The WiringPlan memo lived on CacheItem, which is per-container, so a REQUEST-scoped Factory rebuilt an identical plan on every child container (measured: 101 WiringPlan.build calls across 100 request containers where 1 is correct), and validate()'s get_dependencies/iter_validation_issues rebuilt it unmemoized on every graph walk.

This moves the memo onto ProvidersRegistry — the object the plan is actually keyed to — behind a single plan_for(provider, build) accessor, keyed by provider_id and stamped with the registry version. Design rationale and the alternatives weighed live in the change bundle: planning/changes/2026-07-15.01-wiring-plan-registry-memo.md.

What changes

  • ProvidersRegistry.plan_for — version-stamped, snapshot-before-build memo shared tree-wide (a container and every child share one registry), so a deeper-scope factory plans once per registry version, not once per child container. Distinct roots keep separate memos, so the same Factory in two registries with different provider sets wires independently.
  • Factory._plan replaces _ensure_plan; the resolve path plus get_dependencies/iter_validation_issues all route through it, so validate() warms the memo instead of rebuilding on each walk.
  • CacheItem loses wiring_plan/wiring_plan_version (5 fields → 3); uncached factories skip fetch_cache_item entirely.
  • Invalidation is the version-stamped read-side check ported verbatim from CacheItem, preserving the resolve-vs-add_providers concurrency guarantee. No new lock — GIL-assuming, consistent with deferred.md A-1 (repointed to the registry location in this PR).

Behaviour and public API are unchanged.

Tests

  • RED-first perf guard: a REQUEST-scoped Factory across 50 child containers asserts one build per provider (fails at 55 on old code, passes at 2). Replaces the weaker single-container count test.
  • Correctness guard: same Factory, two registries, different provider sets → wires differently. Verified to fail under a naive version-only shared memo.
  • Existing add_providers-rebuilds-stale-plan tests stay green as invalidation guards.

just test-ci (400 passed, 100% coverage), just lint-ci, and just bench (22 passed) all green.

🤖 Generated with Claude Code

The WiringPlan memo lived on CacheItem, which is per-container, so a
REQUEST-scoped Factory rebuilt an identical plan on every child container
(measured: 101 WiringPlan.build calls across 100 request containers where 1
is correct), and validate()'s get_dependencies/iter_validation_issues rebuilt
it unmemoized on every graph walk.

Move the memo onto ProvidersRegistry behind a single plan_for(provider, build)
accessor, keyed by provider_id and stamped with the registry version. The
registry is shared by a container and every child, so the plan builds once per
registry version for the whole tree; distinct roots keep separate memos, so the
same Factory registered into two registries with different provider sets wires
independently. Invalidation is the version-stamped read-side check ported from
CacheItem (snapshot before build), preserving the resolve-vs-add_providers
concurrency guarantee; no new lock, GIL-assuming per deferred.md A-1.

CacheItem loses wiring_plan/wiring_plan_version, and uncached factories skip
fetch_cache_item entirely. Behaviour and public API are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lesnik512
lesnik512 merged commit 869ee62 into main Jul 15, 2026
7 checks passed
@lesnik512
lesnik512 deleted the wiring-plan-registry-memo branch July 15, 2026 09:39
lesnik512 added a commit that referenced this pull request Jul 17, 2026
Cover the 2.28.0->2.29.0 backlog: the single-path compiled resolver (#334)
and its measured perf, the wiring-plan memoization (#326), the three
correctness fixes (#340 ContextProvider-via-kwargs default, #321 NoneType
default/nullability, #320 validate traverses kwargs=), and the closed
provider set under an explicit breaking-change heading (per
planning/decisions/2026-07-17-custom-providers-retracted.md, the release
notes carry that warning, not the docs).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant