Skip to content

url: create URLSearchParams collision map lazily - #65801

Open
RomainLanz wants to merge 1 commit into
nodejs:mainfrom
RomainForks:perf/urlsearchparams-lazy-map
Open

url: create URLSearchParams collision map lazily#65801
RomainLanz wants to merge 1 commit into
nodejs:mainfrom
RomainForks:perf/urlsearchparams-lazy-map

Conversation

@RomainLanz

Copy link
Copy Markdown
Contributor

Note

AI assistance was used to investigate this change and draft parts of the
implementation, tests, benchmarks, and PR description. I reviewed and
verified the final changes and benchmark results.

Hey! 👋

new URLSearchParams(record) currently creates and updates a SafeMap for every record.

Most property names do not need this map. JavaScript object keys are already unique, so no collision is possible while toUSVString(key) leaves the key unchanged.

A map is only needed when a property name contains malformed UTF-16. toUSVString() replaces the invalid part with (U+FFFD), which can make two different property names identical:

'\uFFFDx' // remains '\uFFFDx'
'\uD835x' // becomes '\uFFFDx'

This change appends entries directly while keys remain unchanged. When the first key changes during conversion, it creates the SafeMap, adds the entries already processed, and continues with the existing collision handling. The later value still replaces the earlier one without changing its position.

A test covers a collision occurring after regular keys have already been processed.

Benchmark

Node v27.0.0-pre, V8 14.6.202.34-node.29, 15 runs with 4 million constructions per run:

node benchmark/compare.js \
  --old ./node-before \
  --new ./node-after \
  --runs 15 \
  --filter url-searchparams-creation \
  --set inputType=object \
  --set type=noencode \
  --set type=multiprimitives \
  --set type=array \
  --set n=4000000 \
  url
Input Before After Improvement
String values 2.925M ops/s 3.270M ops/s +11.80%
Mixed primitive values 2.840M ops/s 3.093M ops/s +8.89%
Array values 2.055M ops/s 2.250M ops/s +9.50%

For ten-property records, a separate GC diagnostic measured:

  • allocated memory: 3,016.5 → 2,076.1 bytes/op (-31.2%)
  • scavenges: 724 → 498 (-31.2%)
  • cumulative GC pause: 32.29 → 22.50 ms (-30.3%)

A conversion-heavy TCP loopback benchmark improved from 857k to 1.14M frames/s (+33%). This only demonstrates the cumulative effect when URLSearchParams construction is hot; it is not a general TCP performance claim.

The malformed-key fallback measured 544.4 → 550.9 ns/op, around 1.2% slower and within the observed noise. This rare path now pays for creating and backfilling the map.

Tests

The focused constructor test, the URLSearchParams fast-path test, and the URL
WPT suite pass.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/url

@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. whatwg-url Issues and PRs related to the WHATWG URL implementation. labels Sep 4, 2026
Object property keys are already unique unless USVString normalization
changes a key. Avoid allocating and updating a SafeMap for ordinary
record keys. Create and backfill it when normalization first changes a
key.

This improves object construction by 10% to 25% in the measured cases
and reduces allocation by about 31% for a ten-property record.

Assisted-by: Amp
Signed-off-by: Romain Lanz <romain.lanz@pm.me>
@RomainLanz
RomainLanz force-pushed the perf/urlsearchparams-lazy-map branch from 3f30ad2 to 2fb7b16 Compare September 4, 2026 19:06
@codecov

codecov Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.13%. Comparing base (1e9fd95) to head (2fb7b16).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #65801      +/-   ##
==========================================
- Coverage   90.17%   90.13%   -0.04%     
==========================================
  Files         769      769              
  Lines      261448   261638     +190     
  Branches    49674    49673       -1     
==========================================
+ Hits       235759   235831      +72     
- Misses      16736    16812      +76     
- Partials     8953     8995      +42     
Files with missing lines Coverage Δ
lib/internal/url.js 93.31% <100.00%> (+0.04%) ⬆️

... and 39 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-ci PRs that need a full CI run. whatwg-url Issues and PRs related to the WHATWG URL implementation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants