Skip to content

stream: reduce per-chunk work in WHATWG tee and BYOB reads - #64818

Open
mcollina wants to merge 2 commits into
nodejs:mainfrom
mcollina:webstream-perf-round10
Open

stream: reduce per-chunk work in WHATWG tee and BYOB reads#64818
mcollina wants to merge 2 commits into
nodejs:mainfrom
mcollina:webstream-perf-round10

Conversation

@mcollina

Copy link
Copy Markdown
Member

Two per-chunk reductions in the WHATWG streams implementation, continuing the series from #64431 / #64451.

1. Ring buffer for pending BYOB pull-into descriptors

The byte controller's [[pendingPullIntos]] list was the last per-chunk queue still backed by a plain array consumed with ArrayPrototypeShift. BYOB reads push and shift one descriptor per read, and Array.prototype.shift has real per-call cost even at length 1. This backs the list with the same lazily materialized Queue ring buffer used for the request queues in #64431, so constructing a byte stream still allocates no descriptor storage.

2. Reused tee read request

Both ReadableStreamDefaultTee and the byte tee's default-reader pull path allocated a fresh read request object (three computed-symbol methods) plus a fresh forwarding microtask closure for every chunk. Only one read is ever in flight per tee (guarded by the reading flag), so a single read request and forwarding function are now shared across all chunks, with the chunk handed over through a captured slot. The request is materialized lazily on the first pull so tee() itself stays allocation-light.

A steady-state tee benchmark is added (benchmark/webstreams/tee.js); tee previously only had creation coverage.

Benchmarks (benchmark/compare.js, 12–20 runs each, quiet machine):

                                                            confidence improvement accuracy
webstreams/tee.js type='normal' n=100000                           ***    +79.78 %  ±11.02%
webstreams/tee.js type='bytes' n=100000                            ***    +29.38 %   ±5.05%
webstreams/readable-read.js type='byob' n=100000                    **     +2.45 %   ±3.06%

All other rows (readable-read normal, read-buffered, async-iterator, creation incl. ReadableStream.tee, js_transfer) are neutral; the eager-request version of the tee change initially regressed tee creation by 24 % which is what motivated the lazy materialization (row now +0.49 % n.s.).

Gates: WPT streams/compression/encoding plus the whatwg/webstreams parallel suites all pass. One white-box test that planted a plain array as [[pendingPullIntos]] was updated to build the real Queue representation.

mcollina added 2 commits July 29, 2026 15:26
The byte controller's [[pendingPullIntos]] list was still a plain array
consumed with ArrayPrototypeShift, while every other per-chunk queue in
the WHATWG streams implementation has moved to the Queue ring buffer.
BYOB reads push and shift one descriptor per read, and Array.prototype
shift has real per-call cost even at length 1.

Back the descriptor list with the same lazily materialized Queue used
for the request queues, so constructing a byte stream still allocates
no descriptor storage.

Signed-off-by: Matteo Collina <hello@matteocollina.com>
Both readableStreamDefaultTee and the byte tee's default-reader pull
path allocated a fresh read request object (three closures) plus a
fresh forwarding microtask closure for every chunk. Only one read is
ever in flight per tee (guarded by the `reading` flag), so a single
read request and forwarding function can be shared across all chunks,
with the chunk handed over through a captured slot.

Add a steady-state tee benchmark covering the default and byte paths.

Signed-off-by: Matteo Collina <hello@matteocollina.com>
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/performance

@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. web streams labels Jul 29, 2026
@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. label Jul 29, 2026
@mcollina mcollina assigned aduh95 and unassigned aduh95 Jul 29, 2026
@mcollina
mcollina requested a review from aduh95 July 29, 2026 14:51
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. request-ci Add this label to start a Jenkins CI on a PR. web streams

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants