Repository navigation
Raise CACHE_LIMIT to 4096 - #186
Merged
Merged
Conversation
jdalton
force-pushed
the
perf/cache-limit
branch
2 times, most recently
from
September 5, 2026 02:48
a412975 to
55addec
Compare
Collaborator
Author
This was referenced Sep 5, 2026
jdalton
force-pushed
the
perf/cache-limit
branch
from
September 6, 2026 02:30
55addec to
afe391f
Compare
Sweeping the limit over three workloads, with retained heap alongside throughput, because the question is a trade rather than a maximum:
workload 1000 2048 4096 8192 30 selectors, all hit 26.18us 26.93us 26.80us 27.27us 2000 selectors 50.75ms 50.36ms 4.12ms 4.15ms heap, cache full 2.99mb 3.86mb 6.87mb 14.05mb
A working set that fits costs the same at any limit. One that fits 4096 and not 1000 is worth 12x. 8192 buys nothing further and doubles the worst case, which is only reached by a caller that has that many distinct selectors, since the caches grow lazily.
The cliff sits between 2048 and 4096 for a set of 2000 selectors because a selector is not one cache entry: ':not(.x)' compiles to a run-time s.match('.x', e), so the argument takes an entry of its own. 100 such selectors leave 200 entries in the match caches, measured.
4096 is also the value the fork of this engine inside jsdom's current selector implementation uses.
References:
- MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
- Reading: https://zod.dev/blog/reducing-memory-footprint — why a cache size is measured rather than chosen
jdalton
force-pushed
the
perf/cache-limit
branch
from
September 6, 2026 11:40
afe391f to
c135980
Compare
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.
Tooling update
Rebased onto merged #212 as one commit. Sources and tests use
.mts, Node tests use Vitest, and the published JavaScript paths stay unchanged. The original engine changes are preserved.Local lint, formatting, Node and packed-package checks pass. All 41 WPT pages pass against both generated builds.
Change
A working set that fits costs the same at any limit, one that fits 4096 and not 1000 is worth 12x, and 8192 buys nothing further while doubling the worst-case heap. 4096 is also what the fork of this engine inside jsdom uses.
Detail, and how it was checked
Sweeping the limit over three workloads, with retained heap alongside throughput, because the question is a trade rather than a maximum:
Read it a row at a time. A working set small enough to fit costs the same at every limit, which is the first row. A working set that fits in 4096 entries but not in 1000 is the second row, where raising the limit is worth 12x. Going on to 8192 buys nothing further and doubles the retained heap, and only a caller holding that many distinct selectors ever reaches that heap, because the caches grow as they fill rather than being allocated up front. 4096 is also what the fork of this engine inside jsdom settled on, in
@asamuzakjp/dom-selector8.3.0, which setsCACHE_SIZE = 4096.The cliff sits between 2048 and 4096 for a set of 2000 selectors because a selector is not one cache entry:
:not(.x)compiles to a run-time s.match(.x, e), so the argument takes an entry of its own. 100 such selectors leave 200 entries in the match caches, measured.4096 is also the value the fork of this engine inside jsdom's current selector implementation uses.
Extracted from #167 as a standalone change: one file, applies to master on its own, and checked against the benchmark fixture to confirm every selector still agrees with the native engine.
References: the spec, the browser source, and what each part was reasoned from
This patch applies to master on its own. The sixteen in this series were checked by cherry-picking them onto master one after another, in this order and in reverse, and all sixteen land without a conflict.
Map.