Skip to content

stream: speed up WHATWG web streams - #65273

Open
anonrig wants to merge 2 commits into
nodejs:mainfrom
anonrig:stream-speed-up-webstreams
Open

stream: speed up WHATWG web streams#65273
anonrig wants to merge 2 commits into
nodejs:mainfrom
anonrig:stream-speed-up-webstreams

Conversation

@anonrig

@anonrig anonrig commented Aug 13, 2026

Copy link
Copy Markdown
Member

Behavior-preserving performance work on node:stream/web. Spec
orchestration and brand checks stay in JS; the per-chunk data plane
drops Promise/microtask churn for the common sync pull/write case
and gets a small native helper on the hot path.

  • Skip async wrappers on sync source/sink algorithms.
    createPromiseCallback* now calls the user function and returns
    the raw result. Non-thenable results settle via queueMicrotask
    (same position as Promise.resolve().then) instead of allocating
    a Promise per pull/write/start.
  • pipeTo fills a default readable queue from sync pulls and
    still batches already-queued chunks into the destination. Further
    spec pull-fulfillment (tee, WPT) stays one pull per microtask.
  • pipeTo's shared write tracker completes sync sink writes in
    the fulfillment turn. Regular writer.write() keeps the spec
    one-completion-per-microtask order.
  • Native internalBinding('webstreams'): Fast API
    isNonThenable() on every pull/write/start result, and
    cloneAsUint8Array() as a single memcpy for byte-stream / tee
    clones.
  • Cheaper empty construction. new ReadableStream() /
    new WritableStream() skip validateObject on the shared empty
    sentinels. The writable AbortController is created lazily and
    materialized on abort(), so controller.signal observed after
    abort is still aborted with that reason.

Public constructors, methods, and WHATWG Streams behavior
(backpressure, BYOB, pipeTo, tee, errors, transfer) are unchanged.

Benchmarks

benchmark/compare.js --runs 10 of the in-repo webstreams/ suite
on the same machine, same out/Release/node family (pre-change
binary vs this tree). Rates are ops/sec.

Hot-path geometric mean of new/old across all configs of
pipe-to.js, readable-read.js, readable-read-buffered.js,
creation.js, readable-async-iterator.js, and tee.js:
1.94x (32 configs). Full suite including js_transfer.js:
1.84x (35 configs). No config has mean(new)/mean(old) < 1.0
(min 1.03 on js_transfer ReadableStream).

script config old new ratio
pipe-to HWM 512/512 2.15M 4.83M 2.25
pipe-to HWM 4096/4096 2.17M 4.85M 2.24
tee normal 1.12M 2.56M 2.29
readable-async-iterator normal 3.77M 8.59M 2.28
creation WritableStream 0.67M 1.37M 2.05
readable-read-buffered bufferSize=1000 5.61M 12.17M 2.17
readable-read normal 3.99M 5.37M 1.35
creation ReadableStream 1.35M 2.01M 1.49
js_transfer ReadableStream 59.0k 61.0k 1.03

Tests

  • test/wpt/test-streams.js
  • test/parallel/test-whatwg-readable*, writable*, transform*,
    webstreams*, test-webstreams*, test-global-webstreams.js
  • new test/parallel/test-whatwg-webstreams-hotpath.js (public
    read() / pipeTo, native helpers, abort-before-signal)
  • abort-before-signal case in test-whatwg-writablestream.js

AI assistance

This change was developed with assistance from Grok. I reviewed,
tested, and take responsibility for the submitted code.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/gyp

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Aug 13, 2026
Avoid per-chunk async wrappers for sync pull/write/start, fill
default readable queues in pipeTo, and complete pipeTo writes
without one microtask per chunk. Add a native webstreams binding
with a Fast API isNonThenable check on the data plane and a
memcpy clone for byte views.

Empty stream construction skips redundant validation and lazily
creates the writable AbortController, materializing it on abort()
so controller.signal still reflects the abort reason.

Assisted-by: Grok
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
Declare isNonThenable and cloneAsUint8Array on the new webstreams
binding and register it in InternalBindingMap.

Assisted-by: Grok
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
@anonrig
anonrig force-pushed the stream-speed-up-webstreams branch from 6a3f89b to c796760 Compare August 13, 2026 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants