Skip to content

Optimize stringify-cookie by ~90% for RFC-allowed values#269

Open
saripovdenis wants to merge 5 commits intojshttp:masterfrom
saripovdenis:perf/encode-fast-path
Open

Optimize stringify-cookie by ~90% for RFC-allowed values#269
saripovdenis wants to merge 5 commits intojshttp:masterfrom
saripovdenis:perf/encode-fast-path

Conversation

@saripovdenis
Copy link
Copy Markdown
Contributor

@saripovdenis saripovdenis commented Apr 28, 2026

stringifyCookie Optimization

Higher hz is better.

Measured on Node 24.15.0.

  • before: master with the same benchmark rows
  • after: perf/encode-fast-path

Before

case hz mean p99 p999 rme samples
empty 39,126,449.22 0.0000 0.0000 0.0001 ±1.25% 19,563,225
simple 10,800,676.25 0.0001 0.0001 0.0003 ±0.31% 5,400,339
rfc cookie-octets 7,640,036.95 0.0001 0.0002 0.0003 ±0.21% 3,820,019
encode 8,095,315.45 0.0001 0.0002 0.0003 ±0.19% 4,047,658
undefined values 5,599,293.66 0.0002 0.0002 0.0005 ±0.34% 2,799,647
mixed encode 3,718,376.33 0.0003 0.0003 0.0006 ±0.28% 1,859,189
10 cookies 1,255,643.08 0.0008 0.0010 0.0018 ±0.38% 627,822
100 cookies 130,390.29 0.0077 0.0082 0.0298 ±0.24% 65,196

After

case hz mean p99 p999 rme samples
empty 39,250,860.59 0.0000 0.0000 0.0001 ±0.81% 19,625,431
simple 16,217,012.96 0.0001 0.0001 0.0003 ±0.21% 8,108,507
rfc cookie-octets 14,452,166.79 0.0001 0.0001 0.0003 ±0.24% 7,226,084
encode 7,324,037.96 0.0001 0.0002 0.0003 ±0.13% 3,662,019
undefined values 7,781,837.11 0.0001 0.0002 0.0004 ±0.35% 3,890,919
mixed encode 4,236,634.53 0.0002 0.0003 0.0004 ±0.13% 2,118,318
10 cookies 1,912,914.48 0.0005 0.0006 0.0010 ±0.14% 956,458
100 cookies 175,245.81 0.0057 0.0063 0.0227 ±0.22% 87,623

Gain Summary

case before hz after hz delta hz speedup gain
empty 39,126,449.22 39,250,860.59 +124,411.37 1.00x +0.32%
simple 10,800,676.25 16,217,012.96 +5,416,336.71 1.50x +50.15%
rfc cookie-octets 7,640,036.95 14,452,166.79 +6,812,129.84 1.89x +89.16%
encode 8,095,315.45 7,324,037.96 -771,277.49 0.90x -9.53%
undefined values 5,599,293.66 7,781,837.11 +2,182,543.45 1.39x +38.98%
mixed encode 3,718,376.33 4,236,634.53 +518,258.20 1.14x +13.94%
10 cookies 1,255,643.08 1,912,914.48 +657,271.40 1.52x +52.35%
100 cookies 130,390.29 175,245.81 +44,855.52 1.34x +34.40%

Totals

group before hz after hz delta hz speedup gain
non-empty cases 37,239,732.01 52,099,849.64 +14,860,117.63 1.40x +39.90%
all cases 76,366,181.23 91,350,710.23 +14,984,529.00 1.20x +19.62%

Encoded Totals

group before hz after hz delta hz speedup gain
safe targeted cases 25,426,040.23 40,539,177.15 +15,113,136.92 1.59x +59.44%
encoded cases 11,813,691.78 11,560,672.49 -253,019.29 0.98x -2.14%
all measured cases 76,366,181.23 91,350,710.23 +14,984,529.00 1.20x +19.62%

Notes

This optimizes the common case where cookie values are already valid RFC 6265 cookie-octets.

The rfc cookie-octets case uses a value that the old noEncodeRegExp had to encode but the new cookieOctetRegExp can return as-is.

Values that need encoding can be slower because the new path does cookieOctetRegExp.test(str) before encodeURIComponent(str).

Summary:

  • RFC-only cookie-octet case improved by 89.16%
  • safe targeted cases improved by 59.44%
  • encoded-only case regressed by 9.53%
  • mixed encoded case improved by 13.94%
  • all measured cases improved by 19.62%

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (4898ba2) to head (f7e1bc9).
⚠️ Report is 25 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##            master      #269    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files            1         1            
  Lines          160       260   +100     
  Branches        69       116    +47     
==========================================
+ Hits           160       260   +100     

☔ View full report in Codecov by Sentry.
📢 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.

@saripovdenis
Copy link
Copy Markdown
Contributor Author

@blakeembrey what do you think about this tradeoff?

@blakeembrey
Copy link
Copy Markdown
Member

This looks pretty great! I think it's worth the trade-off.

One question, should we just allow all RFC valid values through without encoding? It may differ slightly from encodeURIComponent (haven't checked) but might allow more values to skip encoding altogether without any regression in behavior.

@saripovdenis saripovdenis changed the title Optimize stringify-cookie by ~38% for non-empty non-encoded values Optimize stringify-cookie by ~90% for RFC-allowed values May 2, 2026
@saripovdenis
Copy link
Copy Markdown
Contributor Author

saripovdenis commented May 2, 2026

This looks pretty great! I think it's worth the trade-off.

One question, should we just allow all RFC valid values through without encoding? It may differ slightly from encodeURIComponent (haven't checked) but might allow more values to skip encoding altogether without any regression in behavior.

Great idea!

Some cases would have different behaviour

stringifyCookie({ foo: "a=b" })
// before: foo=a%3Db
// after:  foo=a=b

But overall looks like a great win from perf side!

I pushed code accordingly

@saripovdenis
Copy link
Copy Markdown
Contributor Author

Hi @blakeembrey

Friendly ping~
Anything I can do to move this PR forward?

@blakeembrey
Copy link
Copy Markdown
Member

There's an existing regex cookieValueRegExp, and the difference is " and , support. Should we use the same regex or do you think those need to be encoded? And if we think they must be encoded, should we change cookieValueRegExp instead to be stricter? Otherwise a custom encoding can still use those values.

@blakeembrey
Copy link
Copy Markdown
Member

Also I'm planning to land this in a new major version to eliminate concerns around breaking changes.

If you want this in the current version I can cherry pick your original commit, just let me know.

});

bench("rfc cookie-octets", () => {
cookie.stringifyCookie({ foo: "a=b+c/d?x%20" });
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.

Actually, we'd 100% need to encode % to avoid ambiguity here. So I think the fast path is cookieValueRegexp minus %?

Otherwise the round trip won't be correct.

Comment thread src/index.ts
/**
* RegExp to match RFC 6265 cookie-octet values that need no URL encoding.
*/
const cookieOctetRegExp = /^[!#$%&'()*+\-.\/0-9:<=>?@A-Z[\]\^_`a-z{|}~]*$/;
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.

Suggested change
const cookieOctetRegExp = /^[!#$%&'()*+\-.\/0-9:<=>?@A-Z[\]\^_`a-z{|}~]*$/;
const cookieOctetRegExp = /^[!#$&'()*+\-.\/0-9:<=>?@A-Z[\]\^_`a-z{|}~]*$/;

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.

Make sure to add a test that %20 should round trip properly, e.g. decode(encode('%20')) should work.

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.

2 participants