Repository navigation
Conversation
|
Review requested:
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #66424 +/- ##
========================================
Coverage 90.40% 90.41%
========================================
Files 791 791
Lines 276120 276496 +376
Branches 53032 53099 +67
========================================
+ Hits 249634 249995 +361
- Misses 16896 16902 +6
- Partials 9590 9599 +9
🚀 New features to boost your workflow:
|
Failed to resume CIFull Auto Start CI output |
|
This pull request has conflicts with its base branch, removing the |
A source without pull() has nothing observable left to do in its post-start step, since the started flag only gates calls into the pull algorithm. Set the flag right away instead of from a microtask, so a push-style ReadableStream allocates neither the closure nor the task and is not kept alive until the next microtask checkpoint. pipeTo and tee hold the only references to their reader and writer, so their [[closedPromise]] records are never observed as promises. Install the watchers as the records themselves, as pipeTo's ready hook already does, instead of materializing a promise plus reaction per side, and hand the erroring/release probes one shared pending promise. The tee's cancel promise is likewise materialized by the first branch cancel. Microtask ordering is unchanged: each hook enqueues its watcher at the position the promise reaction would have had. node benchmark/compare.js --runs 20 over benchmark/webstreams (46 rows, all others within the confidence interval): webstreams/creation.js kind='ReadableStream' *** +172.40% webstreams/creation.js kind='ReadableStream.tee' *** +20.47% webstreams/creation.js kind='ReadableStreamBYOBReader' *** +15.66% webstreams/creation.js kind='ReadableStreamDefaultReader' *** +14.53% webstreams/lifecycle.js kind='pipe-to' (40 runs) ** +10.98% Signed-off-by: Matteo Collina <hello@matteocollina.com>
56a3708 to
9605461
Compare
|
Benchmark GHA (webstreams): https://github.com/nodejs/node/actions/runs/37592351706 Results
Benchmark results:
|
A source without pull() has nothing observable left to do in its post-start step, since the started flag only gates calls into the pull algorithm. Set the flag right away instead of from a microtask, so a push-style ReadableStream allocates neither the closure nor the task and is not kept alive until the next microtask checkpoint. pipeTo and tee hold the only references to their reader and writer, so their [[closedPromise]] records are never observed as promises. Install the watchers as the records themselves, as pipeTo's ready hook already does, instead of materializing a promise plus reaction per side, and hand the erroring/release probes one shared pending promise. The tee's cancel promise is likewise materialized by the first branch cancel. Microtask ordering is unchanged: each hook enqueues its watcher at the position the promise reaction would have had. node benchmark/compare.js --runs 20 over benchmark/webstreams (46 rows, all others within the confidence interval): webstreams/creation.js kind='ReadableStream' *** +172.40% webstreams/creation.js kind='ReadableStream.tee' *** +20.47% webstreams/creation.js kind='ReadableStreamBYOBReader' *** +15.66% webstreams/creation.js kind='ReadableStreamDefaultReader' *** +14.53% webstreams/lifecycle.js kind='pipe-to' (40 runs) ** +10.98% Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #66424 Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day> Reviewed-By: James M Snell <jasnell@gmail.com>
|
Landed in 29bd740 |
Round 21 of the webstreams performance work (follows #66392). It targets what a stream costs before its first chunk and after its last one: construction,
tee()andpipeTo()setup and shutdown.No post-start microtask for sources without
pull()setupReadableStreamDefaultControllerqueued a microtask after everystart()to flip the controller'sstartedflag and callpull()if needed. That flag only gates calls into the pull algorithm, so for a source withoutpull()(push-style sources that enqueue fromstart(), ornew ReadableStream()) the step has nothing observable left to do. The flag is now set synchronously in that case and no closure or task is allocated. Sources withpull()keep the microtask, at the same position. Byte streams get the same treatment.This also stops a burst of short-lived streams from being kept alive until the next microtask checkpoint, which is where most of the time in the creation benchmark went.
Closed watchers as records instead of promises
pipeTo()holds the only references to its reader and writer, andtee()to its reader, so their[[closedPromise]]records are never observed as promises. Each pipe still materialized both records plus a reaction to watch for close and error, and each tee one record plus a reaction. The watchers are now installed as the records themselves (ClosedPromiseHook), the way the pipe's ready hook already worked: the settle sites resolve or reject the record where they would have settled the promise, and the hook enqueues the watcher at the microtask position its reaction would have had. The erroring and release paths probe the record'spromise, so every hook carries one shared forever-pending promise, whichsetPromiseHandled()skips.The tee's cancel promise is materialized by the first branch cancel only.
Tests
test/parallel/test-whatwg-readablestream-tee-cancel-settle.jscovers the cancel promise settling before and after materialization (cancel one branch then close or error the source, close then cancel both, cancel both) and a tee of an already errored source, for default and byte streams.A 53-scenario microtask-ordering stress (pull, push, iterators, tee incl. byte tee, pipeTo incl. every shutdown path, transform backpressure,
ReadableStream.from, writers) logs identically againstmain, also with--no-async-context-frame. WPT streams and the webstreams parallel batch are green.Benchmark
node benchmark/compare.js --runs 20overbenchmark/webstreams:The
lifecycle.jsrows re-run with 40 samples, since the pipe-to row has a wide interval:The long-running rows (pipe-to, pipe-through, tee, read, from) are flat as expected: the savings are per stream, per pipe and per tee, not per chunk. A local harness that creates and drains a 4-chunk push-style stream measured +22 %, and a 4-chunk pipe +10 %.
AI generated, humanly reviewed.