Skip to content

perf(runtime): memoize the subsetting walks a feature read repeats - #1626

Merged
HuiJun merged 1 commit into
developfrom
perf/runtime-relationship-index
Oct 28, 2026
Merged

HuiJun merged 1 commit into
developfrom
perf/runtime-relationship-index

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Every read of a feature value in the runtime repeats the same walks over the model's subsetting declarations: relationshipsOfKind rescans the declaration's relationships and allocates a filtered slice per query, relatedFeatures re-resolves the named targets per query, subsetsTransitively re-walks the subsetting chain with a fresh visited set and queue per question, and SubsettingFeatures(inst, …) copies the per-type list into a per-read slice even when the object holds every subsetter. None of these answers can change for a given Model, so they belong in the model's memo tables beside subsetters, subsetted and redefined.

The cost appeared with the implicit-specialization runtime objects (#1492) and grew with #1512, which route more reads through subsetsTransitively/relatedFeatures. This change memoizes the four walks per model:

  • relationshipsOfKind → Model.relationships keyed by (symbol, kind);
  • relatedFeatures → Model.related keyed by (symbol, owner, kind), stored with slices.Clip so a caller that appends to the result copies instead of writing into the shared slice;
  • subsetsTransitively → Model.subsetsTransitively keyed by (feature, owner, target); the walk no longer appends the three source lists into one slice;
  • SubsettingFeatures(inst, typ, name) returns the shared per-type slice when inst holds every subsetter and filters into a fresh slice only from the first feature it does not hold. The per-type slices are clipped when stored.

Measured (-benchtime 200x, Go 1.27.3 for every column, same machine; allocs/op and B/op are the reliable signal, sec/op is noisy on this VM and omitted):

bench v0.11.0 develop ddfbb9446 develop + this PR
tests/perf GRPCVerifyConstraint allocs/op 14 091 19 621 16 074
tests/perf GRPCVerifyConstraint B/op 849 347 1 564 432 822 706
internal/frontend/grpc InstantiateWarmModel allocs/op 4 001 4 548 4 164
tests/stressmodel FleetInstantiate/satellites=32 allocs/op 6 451 460 6 489 288 6 474 648

GRPCVerifyConstraint recovers 3 547 of the 5 530 allocations #1492/#1512 added per call and all of the bytes; the remaining 1 983 allocations/op are in the binding walks (bindingWitnesses, resolveBindings, unwrappedDeclMembers, siblingSubsetterValues) and are left to a separate change. A 6-round benchstat table (v0.11.0 | develop | develop+fix) follows in a comment once the quiet rerun finishes.

How it was verified

  • New TestRelationshipsOfKindAreCollectedOncePerFeature and TestSubsettingWalksAreAnsweredOncePerModel (internal/exec/runtime/relationships_memo_test.go): testing.AllocsPerRun over 100 vs 50 repeated walks, each must not allocate more than the other. Both fail with the memo bypassed (100 vs 50 allocations) and pass with it (0 vs 0).
  • go build ./..., go vet ./..., gofmt -l . empty, python3 scripts/changelog.py check.
  • go test -count=1 ./internal/exec/runtime (conformance, traces, robustness) passes; make lint, go test -count=1 ./internal/exec/runtime ./tests/perf ./tests/stressmodel ./internal/frontend/grpc ./internal/frontend/repl and go test -race ./internal/exec/runtime are running and will be reported in a comment.

Overlap with open PRs

Dry-merged in a scratch worktree from origin/develop ddfbb9446: this head, then each sibling that touches internal/exec/runtime.

PR head verdict
#1606 fix/sonar-runtime 9ed7ce5e3 clean
#1610 fix/sonar-internal 2dc10e486 clean

Both merge without conflict in either order; the merged tree's runtime and tests/perf tests are being run and the result will be posted in a comment. No landing-order constraint.

Checklist

  • make test and make lint pass locally (see above for what is still running)
  • Tests added or updated for the change
  • Documentation extended where it already covers the surface (none covers these memo tables)
  • Changelog entry added as changes/unreleased/runtime-subsetting-memo.changed.md
  • baselines regenerated and make docs-counts run if a gate count moved (no gate count moved)
  • No internal work-item labels (waves, slices, F4, K5) in the body, docs, or changelog

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Gate results for e1093ee (Go 1.27.3, same machine):

  • go build ./..., go vet ./... clean; gofmt -l . empty; make lint passed; python3 scripts/changelog.py check passed.
  • go test -count=1 ./internal/exec/runtime ./tests/perf ./tests/stressmodel ./internal/frontend/grpc ./internal/frontend/repl: all ok (runtime 177 s).
  • go test -count=1 -race ./internal/exec/runtime: ok.
  • Negative control: TestSubsettingWalksAreAnsweredOncePerModel and TestRelationshipsOfKindAreCollectedOncePerFeature fail with the memo lookups bypassed (100 vs 50 allocations) and pass with them (0 vs 0).

Overlap: origin/develop ddfbb94 + this head + #1606 (9ed7ce5) + #1610 (2dc10e4) merge clean in that order; the merged tree's go build ./..., go vet ./internal/exec/runtime and go test -count=1 ./internal/exec/runtime ./tests/perf pass.

@devin-ai-integration
devin-ai-integration Bot marked this pull request as ready for review October 28, 2026 11:32

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@HuiJun
HuiJun merged commit f055900 into develop Oct 28, 2026
53 checks passed
@HuiJun
HuiJun deleted the perf/runtime-relationship-index branch October 28, 2026 13:11
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