Skip to content

Raise CACHE_LIMIT to 4096 - #186

Open
jdalton wants to merge 1 commit into
dperini:masterfrom
jdalton:perf/cache-limit
Open

Raise CACHE_LIMIT to 4096#186
jdalton wants to merge 1 commit into
dperini:masterfrom
jdalton:perf/cache-limit

Conversation

@jdalton

@jdalton jdalton commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

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:

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.

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.

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
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