Skip to content

stream: reduce allocations on WHATWG streams hot paths#63876

Open
mcollina wants to merge 1 commit into
nodejs:mainfrom
mcollina:webstream-js-perf-squashed
Open

stream: reduce allocations on WHATWG streams hot paths#63876
mcollina wants to merge 1 commit into
nodejs:mainfrom
mcollina:webstream-js-perf-squashed

Conversation

@mcollina

@mcollina mcollina commented Jun 12, 2026

Copy link
Copy Markdown
Member

Pure-JavaScript allocation reductions on the WHATWG streams hot paths partially based on the findings of #63872 : reused promise-reaction closures per controller (pull/write), a buffered fast path in the async iterator, queueMicrotask() for non-thenable start results, arity-specialized algorithm wrappers, shared nil state records, and removal of several dead per-instance allocations. No observable behavior change: WPT streams/compression/encoding results are identical to main (same subtests passing, same 8 expected failures by name).

Benchmark results (benchmark/compare.js --runs 10, both binaries built the same day from the same toolchain):

                                                                       confidence improvement accuracy (*)    (**)    (***)
webstreams/creation.js kind='ReadableStream.tee' n=50000                      ***     29.74 %       ±4.57%  ±6.28%   ±8.58%
webstreams/creation.js kind='ReadableStream' n=50000                          ***     21.46 %       ±6.77%  ±9.51%  ±13.48%
webstreams/creation.js kind='ReadableStreamBYOBReader' n=50000                ***    -51.78 %       ±3.80%  ±5.21%   ±7.10%
webstreams/creation.js kind='ReadableStreamDefaultReader' n=50000              **    102.49 %      ±62.86% ±90.21% ±132.47%
webstreams/creation.js kind='TransformStream' n=50000                         ***     33.00 %       ±6.62%  ±9.22%  ±12.90%
webstreams/creation.js kind='WritableStream' n=50000                          ***    102.28 %      ±13.80% ±19.67%  ±28.56%
webstreams/js_transfer.js n=10000 payload='ReadableStream'                     **      2.97 %       ±1.97%  ±2.74%   ±3.82%
webstreams/js_transfer.js n=10000 payload='TransformStream'                   ***      6.29 %       ±1.97%  ±2.75%   ±3.87%
webstreams/js_transfer.js n=10000 payload='WritableStream'                    ***      7.66 %       ±1.58%  ±2.19%   ±3.01%
webstreams/pipe-to.js highWaterMarkW=1024 highWaterMarkR=1024 n=500000                 1.27 %       ±2.50%  ±3.48%   ±4.85%
webstreams/pipe-to.js highWaterMarkW=1024 highWaterMarkR=2048 n=500000          *      2.61 %       ±2.34%  ±3.22%   ±4.40%
webstreams/pipe-to.js highWaterMarkW=1024 highWaterMarkR=4096 n=500000         **      3.47 %       ±1.93%  ±2.70%   ±3.77%
webstreams/pipe-to.js highWaterMarkW=1024 highWaterMarkR=512 n=500000         ***      3.13 %       ±1.61%  ±2.21%   ±3.03%
webstreams/pipe-to.js highWaterMarkW=2048 highWaterMarkR=1024 n=500000        ***      4.46 %       ±2.23%  ±3.05%   ±4.16%
webstreams/pipe-to.js highWaterMarkW=2048 highWaterMarkR=2048 n=500000        ***      3.71 %       ±1.89%  ±2.60%   ±3.54%
webstreams/pipe-to.js highWaterMarkW=2048 highWaterMarkR=4096 n=500000        ***      4.33 %       ±1.65%  ±2.27%   ±3.09%
webstreams/pipe-to.js highWaterMarkW=2048 highWaterMarkR=512 n=500000         ***      3.80 %       ±1.81%  ±2.48%   ±3.38%
webstreams/pipe-to.js highWaterMarkW=4096 highWaterMarkR=1024 n=500000                 1.20 %       ±1.53%  ±2.10%   ±2.88%
webstreams/pipe-to.js highWaterMarkW=4096 highWaterMarkR=2048 n=500000         **      4.01 %       ±2.18%  ±3.01%   ±4.15%
webstreams/pipe-to.js highWaterMarkW=4096 highWaterMarkR=4096 n=500000        ***      4.17 %       ±2.16%  ±2.99%   ±4.15%
webstreams/pipe-to.js highWaterMarkW=4096 highWaterMarkR=512 n=500000         ***      3.34 %       ±1.51%  ±2.10%   ±2.92%
webstreams/pipe-to.js highWaterMarkW=512 highWaterMarkR=1024 n=500000         ***      2.91 %       ±1.39%  ±1.93%   ±2.67%
webstreams/pipe-to.js highWaterMarkW=512 highWaterMarkR=2048 n=500000         ***      3.50 %       ±1.86%  ±2.56%   ±3.49%
webstreams/pipe-to.js highWaterMarkW=512 highWaterMarkR=4096 n=500000           *      2.82 %       ±2.25%  ±3.13%   ±4.36%
webstreams/pipe-to.js highWaterMarkW=512 highWaterMarkR=512 n=500000          ***      4.30 %       ±1.45%  ±2.00%   ±2.74%
webstreams/readable-async-iterator.js n=100000                                ***     38.07 %       ±5.69%  ±7.82%  ±10.69%
webstreams/readable-read-buffered.js bufferSize=1 n=100000                             2.94 %       ±6.05%  ±8.29%  ±11.30%
webstreams/readable-read-buffered.js bufferSize=10 n=100000                            2.50 %       ±7.03%  ±9.76%  ±13.61%
webstreams/readable-read-buffered.js bufferSize=100 n=100000                          -2.42 %       ±6.57%  ±9.01%  ±12.29%
webstreams/readable-read-buffered.js bufferSize=1000 n=100000                         -3.28 %       ±6.07%  ±8.42%  ±11.68%
webstreams/readable-read.js type='byob' n=100000                                      -0.25 %       ±1.73%  ±2.41%   ±3.36%
webstreams/readable-read.js type='normal' n=100000                                     4.12 %       ±6.84%  ±9.41%  ±12.91%

The creation.js rows at the stock n=50000 measure a 20-40ms window and are unreliable; re-run at --set n=500000:

                                                                   confidence improvement accuracy (*)   (**)   (***)
webstreams/creation.js kind='ReadableStream.tee' n=500000                          2.31 %       ±2.49% ±3.50%  ±4.95%
webstreams/creation.js kind='ReadableStream' n=500000                     ***     13.87 %       ±2.38% ±3.26%  ±4.46%
webstreams/creation.js kind='ReadableStreamBYOBReader' n=500000           ***     12.98 %       ±4.95% ±6.82%  ±9.40%
webstreams/creation.js kind='ReadableStreamDefaultReader' n=500000         **      9.82 %       ±6.90% ±9.61% ±13.42%
webstreams/creation.js kind='TransformStream' n=500000                    ***     50.30 %       ±2.23% ±3.07%  ±4.19%
webstreams/creation.js kind='WritableStream' n=500000                     ***     97.09 %       ±6.55% ±9.18% ±12.95%

@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. web streams labels Jun 12, 2026
@mcollina mcollina requested review from MattiasBuelens, aduh95 and jasnell and removed request for aduh95 June 12, 2026 14:04

@MattiasBuelens MattiasBuelens left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks pretty sensible to me! 👍

Comment thread lib/internal/webstreams/readablestream.js
Comment thread lib/internal/webstreams/readablestream.js
Comment thread lib/internal/webstreams/readablestream.js
@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. label Jun 13, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Jun 13, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@mcollina

mcollina commented Jun 13, 2026

Copy link
Copy Markdown
Member Author

Updated benchmarks after the regression fix:

                                                                       confidence improvement accuracy (*)    (**)   (***)
webstreams/creation.js kind='ReadableStream.tee' n=50000                      ***     22.39 %       ±3.84%  ±5.29%  ±7.28%
webstreams/creation.js kind='ReadableStream' n=50000                          ***     41.12 %       ±4.41%  ±6.05%  ±8.27%
webstreams/creation.js kind='ReadableStreamBYOBReader' n=50000                ***     53.34 %       ±7.92% ±11.05% ±15.48%
webstreams/creation.js kind='ReadableStreamDefaultReader' n=50000             ***     68.90 %       ±9.00% ±12.58% ±17.70%
webstreams/creation.js kind='TransformStream' n=50000                         ***     31.64 %       ±4.60%  ±6.39%  ±8.91%
webstreams/creation.js kind='WritableStream' n=50000                          ***     88.35 %       ±6.68%  ±9.15% ±12.47%
webstreams/js_transfer.js n=10000 payload='ReadableStream'                    ***      6.16 %       ±1.95%  ±2.67%  ±3.65%
webstreams/js_transfer.js n=10000 payload='TransformStream'                   ***      5.74 %       ±1.37%  ±1.89%  ±2.61%
webstreams/js_transfer.js n=10000 payload='WritableStream'                    ***      9.38 %       ±1.87%  ±2.58%  ±3.54%
webstreams/pipe-to.js highWaterMarkW=1024 highWaterMarkR=1024 n=500000        ***      4.16 %       ±1.81%  ±2.48%  ±3.38%
webstreams/pipe-to.js highWaterMarkW=1024 highWaterMarkR=2048 n=500000        ***      8.13 %       ±2.31%  ±3.21%  ±4.48%
webstreams/pipe-to.js highWaterMarkW=1024 highWaterMarkR=4096 n=500000        ***      5.68 %       ±1.63%  ±2.26%  ±3.15%
webstreams/pipe-to.js highWaterMarkW=1024 highWaterMarkR=512 n=500000         ***      4.67 %       ±1.91%  ±2.63%  ±3.60%
webstreams/pipe-to.js highWaterMarkW=2048 highWaterMarkR=1024 n=500000        ***      5.06 %       ±1.84%  ±2.55%  ±3.55%
webstreams/pipe-to.js highWaterMarkW=2048 highWaterMarkR=2048 n=500000        ***      5.78 %       ±2.42%  ±3.31%  ±4.52%
webstreams/pipe-to.js highWaterMarkW=2048 highWaterMarkR=4096 n=500000        ***      6.86 %       ±1.53%  ±2.09%  ±2.85%
webstreams/pipe-to.js highWaterMarkW=2048 highWaterMarkR=512 n=500000         ***      4.90 %       ±1.46%  ±2.00%  ±2.73%
webstreams/pipe-to.js highWaterMarkW=4096 highWaterMarkR=1024 n=500000        ***      7.43 %       ±2.10%  ±2.88%  ±3.93%
webstreams/pipe-to.js highWaterMarkW=4096 highWaterMarkR=2048 n=500000         **      4.37 %       ±2.42%  ±3.36%  ±4.68%
webstreams/pipe-to.js highWaterMarkW=4096 highWaterMarkR=4096 n=500000        ***      7.44 %       ±2.05%  ±2.83%  ±3.90%
webstreams/pipe-to.js highWaterMarkW=4096 highWaterMarkR=512 n=500000         ***      4.21 %       ±1.92%  ±2.66%  ±3.70%
webstreams/pipe-to.js highWaterMarkW=512 highWaterMarkR=1024 n=500000         ***      3.29 %       ±1.47%  ±2.02%  ±2.75%
webstreams/pipe-to.js highWaterMarkW=512 highWaterMarkR=2048 n=500000         ***      6.69 %       ±2.94%  ±4.07%  ±5.63%
webstreams/pipe-to.js highWaterMarkW=512 highWaterMarkR=4096 n=500000         ***      7.74 %       ±1.76%  ±2.42%  ±3.33%
webstreams/pipe-to.js highWaterMarkW=512 highWaterMarkR=512 n=500000          ***      5.90 %       ±1.89%  ±2.60%  ±3.56%
webstreams/readable-async-iterator.js n=100000                                ***     36.65 %       ±6.43%  ±8.85% ±12.15%
webstreams/readable-read-buffered.js bufferSize=1 n=100000                     **      7.69 %       ±5.21%  ±7.15%  ±9.76%
webstreams/readable-read-buffered.js bufferSize=10 n=100000                    **      6.47 %       ±4.28%  ±5.86%  ±7.99%
webstreams/readable-read-buffered.js bufferSize=100 n=100000                          -1.00 %       ±4.53%  ±6.27%  ±8.67%
webstreams/readable-read-buffered.js bufferSize=1000 n=100000                          2.40 %       ±5.92%  ±8.11% ±11.07%
webstreams/readable-read.js type='byob' n=100000                                       0.08 %       ±2.07%  ±2.89%  ±4.07%
webstreams/readable-read.js type='normal' n=100000                                     3.62 %       ±7.05%  ±9.71% ±13.34%
                                                                 confidence improvement accuracy (*)    (**)   (***)
webstreams/creation.js kind='ReadableStream.tee' n=500000                 ***     60.86 %       ±2.08%  ±2.93%  ±4.15%
webstreams/creation.js kind='ReadableStream' n=500000                     ***     47.16 %       ±3.58%  ±4.93%  ±6.76%
webstreams/creation.js kind='ReadableStreamBYOBReader' n=500000           ***    157.82 %       ±8.38% ±11.95% ±17.35%
webstreams/creation.js kind='ReadableStreamDefaultReader' n=500000        ***    164.85 %       ±8.89% ±12.76% ±18.71%
webstreams/creation.js kind='TransformStream' n=500000                    ***     60.98 %       ±1.84%  ±2.52%  ±3.45%
webstreams/creation.js kind='WritableStream' n=500000                     ***     91.19 %       ±3.71%  ±5.23%  ±7.46%

@mcollina mcollina marked this pull request as ready for review June 13, 2026 07:51
Comment thread lib/internal/webstreams/util.js Outdated
@mcollina mcollina force-pushed the webstream-js-perf-squashed branch from 6d140aa to 92194f3 Compare June 13, 2026 09:22
@mcollina mcollina force-pushed the webstream-js-perf-squashed branch from 92194f3 to da5b0ee Compare June 14, 2026 12:16
@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. label Jun 14, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Jun 14, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Comment thread lib/internal/webstreams/util.js
@mcollina mcollina force-pushed the webstream-js-perf-squashed branch from 1d6ccc9 to 04558a1 Compare June 19, 2026 16:05
Pure-JavaScript optimizations to lib/internal/webstreams/* that reduce
per-chunk and per-construction allocations on hot paths without
observable behavior change.

Per-chunk: reuse promise-reaction closures per controller, add buffered
fast path for async iterator, specialize callback wrappers by arity,
and share immutable nil records for writable stream resets.

Per-construction: use queueMicrotask for non-object start results,
materialize reader/writer .closed and .ready records lazily, and remove
dead allocations.

Assisted-by: Claude Fable 5
Signed-off-by: Matteo Collina <hello@matteocollina.com>
@aduh95 aduh95 force-pushed the webstream-js-perf-squashed branch from 04558a1 to 67d17be Compare June 20, 2026 09:40
@nodejs-github-bot

nodejs-github-bot commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

CI: https://ci.nodejs.org/job/node-test-pull-request/74293/
Benchmark CI: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/1873/

Benchmark results
                                                                       confidence improvement accuracy (*)   (**)  (***)
webstreams/creation.js kind='ReadableStream.tee' n=50000                      ***     26.50 %       ±0.64% ±0.85% ±1.11%
webstreams/creation.js kind='ReadableStream' n=50000                          ***     46.29 %       ±5.15% ±6.93% ±9.17%
webstreams/creation.js kind='ReadableStreamBYOBReader' n=50000                ***     55.10 %       ±1.92% ±2.56% ±3.34%
webstreams/creation.js kind='ReadableStreamDefaultReader' n=50000             ***     78.50 %       ±2.70% ±3.59% ±4.69%
webstreams/creation.js kind='TransformStream' n=50000                         ***     32.31 %       ±1.08% ±1.45% ±1.88%
webstreams/creation.js kind='WritableStream' n=50000                          ***     90.83 %       ±2.69% ±3.58% ±4.66%
webstreams/js_transfer.js n=10000 payload='ReadableStream'                    ***      7.23 %       ±0.65% ±0.86% ±1.12%
webstreams/js_transfer.js n=10000 payload='TransformStream'                   ***      8.01 %       ±0.48% ±0.64% ±0.84%
webstreams/js_transfer.js n=10000 payload='WritableStream'                    ***     10.18 %       ±0.55% ±0.73% ±0.95%
webstreams/pipe-to.js highWaterMarkW=1024 highWaterMarkR=1024 n=500000        ***      5.02 %       ±1.10% ±1.47% ±1.91%
webstreams/pipe-to.js highWaterMarkW=1024 highWaterMarkR=2048 n=500000        ***      4.86 %       ±1.13% ±1.51% ±1.97%
webstreams/pipe-to.js highWaterMarkW=1024 highWaterMarkR=4096 n=500000        ***      4.99 %       ±1.05% ±1.40% ±1.82%
webstreams/pipe-to.js highWaterMarkW=1024 highWaterMarkR=512 n=500000         ***      4.94 %       ±0.99% ±1.31% ±1.71%
webstreams/pipe-to.js highWaterMarkW=2048 highWaterMarkR=1024 n=500000        ***      4.42 %       ±1.09% ±1.45% ±1.89%
webstreams/pipe-to.js highWaterMarkW=2048 highWaterMarkR=2048 n=500000        ***      5.60 %       ±1.42% ±1.89% ±2.46%
webstreams/pipe-to.js highWaterMarkW=2048 highWaterMarkR=4096 n=500000        ***      4.71 %       ±1.16% ±1.54% ±2.00%
webstreams/pipe-to.js highWaterMarkW=2048 highWaterMarkR=512 n=500000         ***      5.04 %       ±0.95% ±1.27% ±1.65%
webstreams/pipe-to.js highWaterMarkW=4096 highWaterMarkR=1024 n=500000        ***      6.10 %       ±1.08% ±1.44% ±1.88%
webstreams/pipe-to.js highWaterMarkW=4096 highWaterMarkR=2048 n=500000        ***      5.35 %       ±1.32% ±1.76% ±2.29%
webstreams/pipe-to.js highWaterMarkW=4096 highWaterMarkR=4096 n=500000        ***      4.22 %       ±1.00% ±1.33% ±1.73%
webstreams/pipe-to.js highWaterMarkW=4096 highWaterMarkR=512 n=500000         ***      4.90 %       ±1.08% ±1.44% ±1.87%
webstreams/pipe-to.js highWaterMarkW=512 highWaterMarkR=1024 n=500000         ***      4.58 %       ±0.96% ±1.28% ±1.67%
webstreams/pipe-to.js highWaterMarkW=512 highWaterMarkR=2048 n=500000         ***      4.86 %       ±1.12% ±1.49% ±1.94%
webstreams/pipe-to.js highWaterMarkW=512 highWaterMarkR=4096 n=500000         ***      5.25 %       ±1.00% ±1.33% ±1.73%
webstreams/pipe-to.js highWaterMarkW=512 highWaterMarkR=512 n=500000          ***      4.80 %       ±1.13% ±1.51% ±1.96%
webstreams/readable-async-iterator.js n=100000                                ***     35.36 %       ±2.09% ±2.79% ±3.64%
webstreams/readable-read-buffered.js bufferSize=1 n=100000                    ***      4.25 %       ±2.23% ±2.97% ±3.86%
webstreams/readable-read-buffered.js bufferSize=10 n=100000                            0.57 %       ±2.97% ±3.96% ±5.16%
webstreams/readable-read-buffered.js bufferSize=100 n=100000                          -0.69 %       ±3.41% ±4.53% ±5.90%
webstreams/readable-read-buffered.js bufferSize=1000 n=100000                   *     -2.84 %       ±2.67% ±3.55% ±4.62%
webstreams/readable-read.js type='byob' n=100000                                      -0.14 %       ±0.86% ±1.14% ±1.48%
webstreams/readable-read.js type='normal' n=100000                            ***      3.12 %       ±1.54% ±2.04% ±2.66%

Be aware that when doing many comparisons the risk of a false-positive
result increases. In this case, there are 32 comparisons, you can thus
expect the following amount of false-positive results:
  1.60 false positives, when considering a   5% risk acceptance (*, **, ***),
  0.32 false positives, when considering a   1% risk acceptance (**, ***),
  0.03 false positives, when considering a 0.1% risk acceptance (***)

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

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. web streams

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants