Repository navigation
Reuse the combined tag of a cache that reads the same tags again - #21664
Open
NullVoxPopuli-ai-agent wants to merge 5 commits into
Open
NullVoxPopuli-ai-agent wants to merge 5 commits into
NullVoxPopuli-ai-agent wants to merge 5 commits into
Conversation
This was referenced Oct 6, 2026
endTrackFrame takes the tag that the same frame produced the last time it ran. If the frame consumed the same tags again, the result is that tag: it keeps its memoized revision, and the frame allocates no tag and no array. getValue passes the tag of its cache. Split out of emberjs#21650. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
NullVoxPopuli-ai-agent
force-pushed
the
nvp/reuse-frame-tag
branch
from
October 7, 2026 17:17
1d7e696 to
8610111
Compare
With the check in `combine`, V8 did not inline the method into the end of the frame, and each small frame paid for a call. Against main, a frame with one tag was 4% to 7% slower, and `kairo: avoidable propagation` was 13% slower. The check is now in a function of its own, `combineOrReuse`, which only frames with two or more tags call. No case is more than 2% slower than main, and `kairo: mux` and `batch` keep their gain. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Only `getValue` passes the last tag, so only a cache can reuse it. The reuse is now in code that only `getValue` calls: `endCacheFrame` and `Tracker#combineForCache`. `endTrackFrame` and `Tracker#combine` are the code of main again, with no argument. The frames of the render VM and of the curly component manager end there, so this change cannot make them slower. The two tests that passed a tag to `endTrackFrame` are gone. The tests of `createCache` cover the reuse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tests of this change passed with the code of main too, because they only checked that a cache still follows its tags. The new tests read the tag of a cache through an outer frame. One of them fails on main: a cache that consumes the same tags again must keep its tag. Two more need a new tag: for other tags, and for the same tags in another order. Those two fail if the check says "same tags" for every combined tag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
Tests pass here: NullVoxPopuli/limber#2289 / https://test-ember-source-nvp-reuse.limber-glimdown.pages.dev/ however, the whole codebase only has 4 explicit (the rest of the createCache would be internals to ember) |
| */ | ||
| function combineOrReuse(previous: Tag | undefined, tags: (Tag | null)[], size: number): Tag { | ||
| if (previous !== undefined && isCombinationOf(previous, tags, size)) { | ||
| return previous; |
Contributor
There was a problem hiding this comment.
this is the reuse here -- no new tracked things were encountered, so re-calling combine is a little silly
| let second = tagOf(cache); | ||
|
|
||
| assert.strictEqual(count, 2, 'the cache ran again'); | ||
| assert.strictEqual(second, first, 'the cache has the tag of its first run'); |
Each cache now records a step with a text that says what it reads, for example "cache reads tag1 and tag3". A test checks the steps after each read, in place of a counter that went from 1 to 5. A read that must not run the cache checks for no step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NullVoxPopuli
marked this pull request as ready for review
October 7, 2026 22:58
This branch has not been deployed
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.
A cache that consumes the same tags as in its last run returns the same combined tag. It then allocates no tag and no array, and the tag keeps its memoized revision.
This helps
createCache,@cachedandinvokeHelper. A cache that reads two or more tracked values runs again 25% to 28% faster. The worst case found is 5% slower: a cache with 100 tags where only the last tag changes.main@cachedgetter runs again and reads the same tracked values.branchcases have a cache that reads other values in each run, so the check fails.branchrows are from a later run of 10 rounds.a || b || cis such a case.endTrackFrame, which is the code ofmain.The branch is five commits on
main(9bec1cb2a8), and it merges alone.What changes
getValueends its frame with a new function,endCacheFrame(previous). It passes the tag that the cache has from its last run.endTrackFrameandTracker#combinedo not change.What changes for callers
Nothing. No exported function changes its signature, and
endCacheFrameis not exported.Why the reuse is in code of its own
The first two versions of this PR had the reuse in
Tracker#combine, behind an argument ofendTrackFrame.combine, three cases with small frames were 4% to 13% slower thanmain. The method was too large for V8 to put it inline into the end of the frame, so each frame paid for a call.combineOrReuse, that loss went away, apart from 1% to 4% in two cases.getValuereaches the reuse, throughendCacheFrameandTracker#combineForCache. The small frames are level withmainagain, and all other frames run the code ofmain.Passing the last tag from the render VM and from the curly component manager was also tested. It showed no gain in
pnpm bench: comment on #21650.All 26 cases
mainThe benchmark is https://github.com/NullVoxPopuli-ai-agent/ember-reactivity-bench. One measurement is the writes of one frame, then one flush that brings every output up to date. Each case of each build runs in its own process, pinned to one core. The numbers are the median of 8 mirrored rounds on Node 24.20.
mainis9bec1cb2a8. This PR is6ab63a155e.createCache. No case measures rendering.widecases and the eightbranchcases are new. The table above has thebranchcases, and this page has their run.kairo: avoidable propagationis 1.01x, and slower in 7 of 8 rounds. That is 1.5 ns for 6 caches.kairo: repeated observershas two stable speeds in this benchmark, also between two runs of the same code.@cached, nocreateCacheand noinvokeHelper, so they cannot reach the new code.Tests
@glimmer/validator: trackingpass: 40 of 40. The full suite passed before the last two commits, which change tests only: 9525 pass, 18 skipped, 0 failed.type-check:internals, ESLint and Prettier pass.main: a cache that consumes the same tags in its next run keeps its tag.maintoo.Related
This is the last open part of #21650. The other parts are merged: #21663, #21665, #21666 and #21667. #21650 has the same commits as this PR now, and it can close when this PR merges.
🤖 Generated with Claude Code