Skip to content

perf: improve Headers class performance - #5699

Open
anonrig wants to merge 3 commits into
nodejs:mainfrom
anonrig:cursor/headers-class-performance-859a
Open

perf: improve Headers class performance#5699
anonrig wants to merge 3 commits into
nodejs:mainfrom
anonrig:cursor/headers-class-performance-859a

Conversation

@anonrig

@anonrig anonrig commented Aug 18, 2026

Copy link
Copy Markdown
Member

This relates to...

N/A

Rationale

Headers is on the fetch / Request / Response hot path. The current implementation pays for WebIDL conversion, token validation, and toLowerCase() as separate passes, and stores every entry as a { name, value } object.

Changes

  • Combine ByteString conversion, HTTP token validation, and lowercasing into a single pass.
  • Fast-path common lowercase names (content-type, accept, authorization, …) on get / has / delete.
  • Store values as strings in the internal Map instead of { name, value } objects. Original casing is kept in a side table only when it differs from the lowercase name.
  • Copy an existing Headers instance by cloning the list instead of re-validating every entry.
  • Keep the existing fetch spec comments next to the new hot-path code. No changes outside lib/web/fetch/headers.js.

No intended behavioral change. Guard handling, set-cookie / getSetCookie(), iteration order, and original-name casing on the wire are preserved.

Features

N/A

Bug Fixes

N/A

Breaking Changes and Deprecations

N/A

Benchmark

Measured with the source Headers class (Node v22.14.0, 200k iterations, paired old vs new):

Operation Before After Change
get (custom name) 18.4M ops/s 77.9M ops/s 4.2x
get (content-type) 17.6M ops/s 56.2M ops/s 3.2x
has 16.7M ops/s 80.7M ops/s 4.8x
set 15.4M ops/s 25.2M ops/s +64%
new Headers(headers) 1.12M ops/s 1.72M ops/s +54%
new Headers() 23.7M ops/s 23.3M ops/s ~same
append 4.36M ops/s 4.33M ops/s ~same
iterate 5.05M ops/s 5.15M ops/s ~same

Status

Tested locally:

node --test test/fetch/headers.js test/fetch/headers-inspect-custom.js test/fetch/headerslist-sortedarray.js test/fetch/headers-case.js

All 70 tests passed, including the 4000-iteration toSortedArray fast/slow paths and the fetch cloning cases.

cursor Bot pushed a commit to anonrig/node that referenced this pull request Aug 18, 2026
Add WHATWG Headers unit tests and a fetch/headers benchmark so Node
can track the API after the implementation change lands in undici.

Refs: nodejs/undici#5699
Co-authored-by: Yagiz Nizipli <yagiz@nizipli.com>
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
@codecov-commenter

codecov-commenter commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.51100% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.48%. Comparing base (f387057) to head (ab9bd62).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
lib/web/fetch/headers.js 99.51% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5699      +/-   ##
==========================================
+ Coverage   93.47%   93.48%   +0.01%     
==========================================
  Files         110      110              
  Lines       38846    39104     +258     
==========================================
+ Hits        36310    36557     +247     
- Misses       2536     2547      +11     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

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

@mcollina mcollina left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the benchmarks somewhere will be good.

Also, lint is failing.

@KhafraDev I'm generically ok with these improvements. However it's quite substantial in the code changes. Take a look.

cursor Bot pushed a commit to anonrig/node that referenced this pull request Aug 18, 2026
Add WHATWG Headers unit tests and a fetch/headers benchmark so Node
can track the API after the implementation change lands in undici.

Refs: nodejs/undici#5699
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
@anonrig

anonrig commented Aug 18, 2026

Copy link
Copy Markdown
Member Author

Addressed the review feedback:

  • Lint: removed unused appendHeader (no-unused-vars).
  • Benchmarks: added benchmarks/fetch/headers-methods.mjs for construct / get / set / append / has / delete / iterate. Existing iterator benches now clear sortedMap instead of the old private symbol.

@anonrig
anonrig requested a review from KhafraDev August 18, 2026 15:06
@mcollina

Copy link
Copy Markdown
Member

Can you fix the metadata of the commits? Cursor cannot signoff commits.

@anonrig
anonrig force-pushed the cursor/headers-class-performance-859a branch from 2899626 to 89e1b3f Compare August 18, 2026 17:08
@anonrig

anonrig commented Aug 18, 2026

Copy link
Copy Markdown
Member Author

Rewrote the commits so author, committer, and Signed-off-by are Yagiz Nizipli yagiz@nizipli.com.

@anonrig
anonrig force-pushed the cursor/headers-class-performance-859a branch from 89e1b3f to cb75094 Compare August 18, 2026 18:06
Speed up the WHATWG Headers hot path:

- Combine ByteString conversion, token validation, and lowercasing
  into a single pass, with a fast path for common lowercase names.
- Store header values as strings instead of {name, value} objects.
- Copy an existing Headers list without re-validating every entry.
- Skip ToString in the ByteString converter when the value is already
  a string.

Assisted by Cursor
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
Keep the Headers hot-path changes, but put the fetch spec comments
back and drop the unrelated ByteString converter change.

Assisted by Cursor
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
Address review feedback:
- remove unused appendHeader (lint)
- add get/set/append/has/construct benches
- clear sortedMap instead of the old private symbol

Assisted by Cursor
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
@anonrig
anonrig force-pushed the cursor/headers-class-performance-859a branch from cb75094 to ab9bd62 Compare August 18, 2026 18:45

@mcollina mcollina left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mcollina
mcollina requested a review from tsctx August 21, 2026 01:11
@mcollina

Copy link
Copy Markdown
Member

@tsctx @KhafraDev ptal

@tsctx tsctx left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the main advantages of the original implementation is that it closely follows the specification, with each step represented explicitly in the code. This keeps the maintenance cost low, makes it straightforward to adapt the implementation to future specification changes, and makes it easier to trace errors back to the relevant step.

The proposed changes also introduce a separate implementation of logic that is currently shared. This duplicates the same behavior and error messages across multiple places, increasing the maintenance burden and creating a risk of inconsistencies over time.

Maintaining a close correspondence with the specification is particularly important because the specification is a living standard. Although Headers is relatively stable compared with other areas, it may still evolve over time.

While performance improvements are certainly appealing, I believe the overall ROI of these changes is low. For Headers, iteration performance appears to be more important in typical usage. Operations such as get and append are generally performed only a limited number of times per instance, so optimizing them does not appear to provide much practical benefit, especially when weighed against the additional complexity and maintenance cost.

There are also several behavioral regressions that could result in unexpected behavior. Some of the proposed changes are valuable. However, given these trade-offs, I do not think the changes as a whole are worthwhile.

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.

4 participants