test: paired hot-path benchmark for createSelector - #770
Open
veksa wants to merge 1 commit into
Open
Conversation
✅ Deploy Preview for reselect-docs canceled.
|
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. |
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.
Our benchmarks run on tinybench, which is good at "which memoizer is faster" but can't answer "did this commit make
createSelector6% slower". That's the number I kept wanting while poking at the hot path, so this adds a harness for it:yarn bench:hot-path.It builds
srctwice — the working tree and a baseline git ref — and measures both in the same process, back to back. That part turned out to matter: when I ran them as two processes, a 10% swing got credited to a commit that hadn't touched the code being measured. Rounds are interleaved across cases, the statistic is the minimum, and the noise floor is measured per case (rounds split in half, gap between their minima) rather than assumed.Every case also carries a recomputation counter checked against expectation, so a variant that looks fast because it quietly skipped work can't pass as a win. It caught three bugs in my own cases while I was writing them.
Identical code on both sides, as a sanity check:
(state)(state, props)(state, props)So about ±1.5% on a quiet machine. On a busy one the gate widens on its own and refuses to call anything a result, which is the failure mode I wanted.
Where we actually are (ns/call, production):
(state)(state, props)argsMemoize: lruMemoizeThe gap between those first two rows is the interesting bit. A slice selector called with just the state hits the argument cache on all but the tick's first call. A parametric one never hits it — new state every tick, different props every call — so it allocates a cache node and does a
WeakMap.setper call, and ends up 16x above a hand-written keyed memo.yarn bench:hot-path:devruns the same thing with the dev checks left in, since they sit in the hot path and a production build can't show what they cost.Two things I left out on purpose. There's no allocation column:
heapUseddeltas measure what the collector hasn't freed yet, and on a change that removed two allocations per call it went up. And the clock covers the state-producing step as well as the calls, which dilutes every difference — excluding it looked obviously right and reported one case 22% slower that the same code had just measured 5% faster, because GC pauses land wherever the allocation threshold falls.bench/profile.mjsis in here too. It's what showed me the array allocation and arity dispatch I'd assumed were the problem were 1.3% of the call.