Repository navigation
perf(router): copy new tree nodes from one empty node - #196
Conversation
An object literal with `undefined` values is not a constant V8 can clone: it creates the object, then defines each property with its own IC, seven per node. Spreading one empty node copies it in one step instead. The nodes keep their fields and `undefined`, so the code that reads them, and the steady state, stay the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7Sutbw6xMTMyDFeNfw
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance1 warning
Comparing Footnotes
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — reviewed the single-commit change to packages/router/src/index.ts (the only file touched), which replaces per-node object-literal construction with copies of one shared empty node:
createNoderemoved,EMPTY_NODEadded — the module-privatecreateNode<T>()is gone; a module-levelconst EMPTY_NODE: Readonly<Node<never>>with the same sevenundefinedfields replaces it (packages/router/src/index.ts:99).- All four construction sites now spread
EMPTY_NODE—routesOfroot (index.ts:247), theexpandparam child (index.ts:439), and bothliteralChildpaths (index.ts:489,index.ts:503).grep createNodereturns no hits, so no construction path was missed.
The spread produces the same seven own enumerable properties with the same undefined values in the same order, so every read of them and the optimized code paths are unchanged. EMPTY_NODE is only ever spread, never returned or mutated, and its fields are all undefined (no nested objects to alias), so the shared singleton introduces no aliasing hazard. I re-ran the router suite (73/73, 100% coverage) and uncheck packages/router/src/index.ts (oxlint 0/0, oxfmt, tsc) — both green. The new Stryker survivors the author notes (replacing a node copy or EMPTY_NODE with {}) are equivalent mutants, since a plain {} reads the same undefined fields; they are expected, not a coverage gap.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Requested by dinwwwh · project thread
Summary
createNodereturned an object literal whose seven fields areundefined. V8 does not treatundefined(orvoid 0) as a constant in an object literal, so the interpreter creates the object and then runsDefineNamedOwnPropertyfor each field, seven ICs per node (node --print-bytecodeshows it;nullvalues would be oneCreateObjectLiteral). A cold start creates a node for most routes while building the tree.Nodes are now copies of one module-level
EMPTY_NODE({ ...EMPTY_NODE }), which V8 makes in one step with a single clone IC, andcreateNodeis gone. The fields and theirundefinedvalues stay the same, so every read of them, and the optimized code, is unchanged.I also tried switching the fields to
null: about the same cold gain, but 10 to 20 more instructions per request in steady state (+0.3%), so not that.Measurements
Callgrind instructions,
--single-threaded, hash seeds 1 and 2 (averaged), main vs this PR:The import grows because
EMPTY_NODEis now created there; the firstaddused to compilecreateNodeand build the first literal, so RPC import plus add is still 30k lower. REST cold start plus 3 requests is about 258k lower overall.--minify, gzip -9): 5076 B to 5071 B minified, 2114 B to 2105 B gzip.Checks
pnpm run checkgreen; router tests 73/73 with 100% coverage.EMPTY_NODEitself, with{}, which reads the sameundefinedfields and only gives nodes other shapes; they replace the onereturn {}survivorcreateNodehad.Generated by Claude Code