Repository navigation
perf(arrays): inline compact primitives - #77
Merged
Merged
Conversation
BridgeAR
force-pushed
the
BridgeAR/2026-10-13-stringify-primitives
branch
from
October 13, 2026 05:51
f86ee76 to
39cbfad
Compare
A four-number array uses 101 ns instead of 133 ns on Node 26.3.1 (V8 14.6.202.34-node.20), a 24% CPU reduction. Measurements use 50,000 warmup calls and seven alternating trials, with the fastest and slowest removed. Four-object arrays use about 5% more CPU in both confirmation runs. Safe mode retains per-element exception handling. Drive-by fix: Mark intentional invalid-option assertions for TypeScript.
Compact object arrays pay primitive checks before recursive serialization, which adds overhead to the existing object path. Four-number arrays use 69.8 ns instead of 75.4 ns on Node 26.3.1 (V8 14.6.202.34-node.20), a 7.4% reduction. Measurements use 100,000 warmup calls and seven alternating trials, with the fastest and slowest removed. Safe mode must catch primitive failures per value so an oversized string does not replace its containing array.
On Node 26.3.1 (V8 14.6.202.34-node.20), a four-number array with indentation takes 82.8 ns instead of 100.7 ns, an 18% CPU reduction. Measurements use 100,000 warmup calls and seven alternating trials of 200,000 calls, with the fastest and slowest results removed. Function replacers already catch failures per value in safe mode. Reusing that boundary avoids a second wrapper around primitive formatting.
BridgeAR
force-pushed
the
BridgeAR/2026-10-13-stringify-primitives
branch
from
October 13, 2026 06:35
39cbfad to
dc389b9
Compare
TAP 21 and newer processinfo releases cannot support the Node.js 18 test matrix. TypeScript 6 retains legacy Node resolution and works with TAP's current TypeScript loader.
Against main, the comparison fixture increases from 68,305 to 76,684 ops/sec on Node.js 26.3.1 (V8 14.6.202.34-node.20), with 50,000 warmup calls and seven two-second trials excluding the fastest and slowest results. Some deep-object benchmark cases measure up to 3.1% slower. Shared primitive handling reduces published source size by 5.2% while preventing the implementations from diverging.
The primitive error matrix encodes a maximum-length string 18 times. On Node.js 18.20.8 (V8 10.2.154.26-node.39), that takes 22.1 seconds. Slower CI runners reach the 30-second timeout before the suite completes. The mode matrix uses a controlled RangeError at the native escaping boundary. Existing tests still exercise the real string limit. The full Node.js 18 suite takes 4.1 seconds after a clean install.
Array replacers cannot change a primitive root's output. Skip their preprocessing, including getter and iterator reads, for these inputs. A numeric root with an eight-entry array replacer takes 8.4 ns instead of 97.4 ns on Node 26.3.1 (V8 14.6.202.34-node.20). This averages two fresh processes with seven alternating trials after 50,000 warmup calls, discarding the fastest and slowest trials.
BridgeAR
marked this pull request as ready for review
October 15, 2026 07:55
kitschpatrol
pushed a commit
to kitschpatrol/safe-stable-stringify
that referenced
this pull request
Nov 2, 2026
* fix: do not throw on a negative number `space` (BridgeAR#65) `JSON.stringify` treats any numeric `space` below 1 as no indentation, but `stringify` threw on it: JSON.stringify({ a: 1 }, null, -1) // '{"a":1}' stringify({ a: 1 }, null, -1) // RangeError: Invalid count value: -1 The README documents the spacer as behaving "the same as with JSON.stringify()". `space` was clamped to a maximum of 10 but not a minimum of 0, so a negative count reached `String.prototype.repeat`, which throws for counts <= -1. Clamp the lower bound to 0. * Fix merge conflict in readme (BridgeAR#63) Looks like this was accidentally introduced in BridgeAR@132e76c * fix: enforce maximumDepth for nested objects with array replacers (BridgeAR#64) * feat(circular)!: include ancestor depth in default markers (BridgeAR#70) Circular references to different ancestors currently produce the same marker, which hides where each cycle closes. The new marker identifies the referenced ancestor's depth, starting at 0 for the root. BREAKING CHANGE: The default marker changes from `[Circular]` to `[Circular: N]`. Set `circularValue: '[Circular]'` to retain the previous output. * fix(typed-array): serialize non-finite floating-point values (BridgeAR#73) * Serialize non-finite typed-array values consistently Fixes: BridgeAR#68 * perf(typed-array): inline floating-point checks A formatter closure adds finite checks to integer arrays and an extra call for floating-point values. Inline checks retain direct conversion for integer and BigInt arrays. --------- Co-authored-by: Ferit <frtcm11@gmail.com> * docs(circular): clarify JSON escaping for custom markers (BridgeAR#74) * perf(arrays): defer index string conversion (BridgeAR#75) Array indexes only need strings when they reach toJSON or function replacer callbacks. A four-number array uses 86 ns instead of 107 ns on Node 26.3.1 (V8 14.6.202.34-node.20), a 20% CPU reduction. Measurements use 50,000 warmup calls and seven alternating trials, with the fastest and slowest removed. Drive-by fix: Mark intentional invalid-option assertions for TypeScript. * perf(sort): bound insertion sort work (BridgeAR#76) Disordered objects with up to 200 keys can spend most of their serialization time shifting keys. A shuffled 200-key object uses 16.6 us instead of 45.6 us on Node 26.3.1 (V8 14.6.202.34-node.20), a 64% CPU reduction. Measurements use 50,000 warmup calls and seven alternating trials, with the fastest and slowest removed. Nearly ordered keys retain the insertion path. Drive-by fix: Mark intentional invalid-option assertions for TypeScript. * feat(modules)!: share ESM and CommonJS exports (BridgeAR#72) The implementation is cached after the first load, so removing its global export leaves the other wrapper without an export. Retain a version-specific symbol so ESM imports, CommonJS requires, and independent copies of one version receive the same function. BREAKING CHANGE: Require Node.js 18 or later. * perf: reduce primitive serialization overhead (BridgeAR#77) Primitive array elements and object properties enter recursive serializers even though they need no traversal. Share primitive formatting across serialization modes while preserving toJSON ordering and per-value error handling in safe mode. Native string escaping becomes faster at shorter string lengths on Node.js 22 and later. Select the cutoff once at module load so older runtimes retain their existing fast path. --------- Co-authored-by: Spoko Dev <spoko.dev@gmail.com> Co-authored-by: Andy Trevorah <andy@trevorah.net> Co-authored-by: Chamath Adithya <achamath1@gmail.com> Co-authored-by: Ruben Bridgewater <ruben@bridgewater.de> Co-authored-by: Ferit <frtcm11@gmail.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
Compact arrays pay recursive dispatch and index conversion for strings, finite numbers, and booleans.
Safe mode retains per-element exception handling.
A four-number array uses 101 ns instead of 133 ns, a 24% CPU reduction on Node 26.3.1 (V8 14.6.202.34-node.20).
Measurements use 50,000 warmup calls and seven alternating trials, with the fastest and slowest trials removed.
Four-object arrays use about 5% more CPU in both confirmation runs.
Drive-by fix: Mark intentional invalid-option assertions for TypeScript.