stream: keep webstream stream states in fast-mode objects - #65625
Open
mcollina wants to merge 2 commits into
Open
stream: keep webstream stream states in fast-mode objects#65625mcollina wants to merge 2 commits into
mcollina wants to merge 2 commits into
Conversation
The per-stream state records were built as object literals with
__proto__: null, which V8 creates as dictionary-mode objects: roughly
50x slower to allocate, and every subsequent property load on them is a
dictionary lookup. These records back every hot path, so both stream
construction and per-chunk field accesses were paying for it.
Replace the literals with classes whose prototype has a null prototype,
so instances stay in fast mode while Object.prototype remains excluded
from the lookup chain. Every field ever assigned is declared up front
so the shape never transitions.
Also add benchmark/webstreams/lifecycle.js covering the short-lived
stream pattern (create, few chunks, close) that first exposed this.
confidence improvement accuracy (*) (**) (***)
webstreams/creation.js kind='ReadableStream' n=50000 *** 138.13 % ±14.29% ±19.14% ±25.17%
webstreams/creation.js kind='TransformStream' n=50000 *** 133.64 % ±8.61% ±11.46% ±14.94%
webstreams/creation.js kind='WritableStream' n=50000 *** 204.00 % ±7.70% ±10.26% ±13.37%
webstreams/lifecycle.js kind='pipe-through' n=50000 *** 96.41 % ±3.41% ±4.58% ±6.03%
webstreams/lifecycle.js kind='readable' n=50000 *** 80.04 % ±3.10% ±4.15% ±5.45%
webstreams/pipe-through.js kind='default' n=500000 *** 91.16 % ±2.56% ±3.43% ±4.52%
webstreams/pipe-to.js highWaterMarkW=1 highWaterMarkR=1 n=500000 *** 110.48 % ±2.91% ±3.89% ±5.11%
webstreams/readable-read.js type='normal' n=100000 *** 34.63 % ±4.79% ±6.38% ±8.31%
Signed-off-by: Matteo Collina <hello@matteocollina.com>
A transform sink write arriving under backpressure parked the chunk together with a PromiseWithResolvers record whose promise was returned to the writable controller and later resolved with the perform-transform promise. The writable's write reactions already exist before the write algorithm runs, so the parked write can instead return the parked-result sentinel and have the continuation wire the perform-transform promise directly to those reactions, dropping the per-chunk promise record and the thenable adoption hop. Failures while erroring are delivered in a microtask, preserving the old rejection position. Signed-off-by: Matteo Collina <hello@matteocollina.com>
Collaborator
|
Review requested:
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #65625 +/- ##
==========================================
- Coverage 90.13% 90.07% -0.07%
==========================================
Files 751 751
Lines 253639 255020 +1381
Branches 47790 48157 +367
==========================================
+ Hits 228618 229703 +1085
- Misses 16264 16482 +218
- Partials 8757 8835 +78
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
While profiling short-lived streams (the per-request create → few chunks → close pattern),
createReadableStreamStateshowed up at 20% self time. The cause: the per-stream state records are object literals with__proto__: null, which V8 creates as dictionary-mode objects — roughly 50x slower to allocate (~500-1500ns vs ~30ns), and every subsequent property load on them is a dictionary lookup. These records back every hot path, so both construction and per-chunk[kState]accesses have been paying for it.The first commit replaces the four per-stream state literals (and their nested transfer records) with classes whose prototype has a null prototype: instances stay in fast mode, while
Object.prototyperemains excluded from the lookup chain, preserving the pollution protection the literals were there for. Every field ever assigned is declared up front so the shape never transitions. (The controller and reader states were already plain literals and unaffected.) It also addsbenchmark/webstreams/lifecycle.jscovering the short-lived stream pattern that exposed this.The second commit removes the per-chunk
PromiseWithResolversfor transform sink writes parked on backpressure: the writable controller's write reactions exist before the write algorithm runs, so the parked-write continuation can wire the perform-transform promise directly to them via the parked-result sentinel introduced in #65143, dropping the promise record and the thenable adoption hop.Results (30 runs, all 36 rows significant at
***, no regressions — full table below):Validated with the full webstreams test suite, WPT streams/compression/encoding, and two differential stress harnesses (adapter scenarios and transform parked-write error/abort/terminate/reentrancy scenarios) whose observable event logs are byte-identical to the previous implementation.
Other
lib/modules use the same__proto__: nullliteral pattern on hot paths; I'll follow up separately after auditing them.