stream: keep webstream stream states in fast-mode objects · nodejs/node@3216bc3 · GitHub
Skip to content

Commit 3216bc3

Browse files
mcollinaaduh95
authored andcommitted
stream: keep webstream stream states in fast-mode objects
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. Assisted-by: ChatGPT Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #65625 Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
1 parent 9cfbfae commit 3216bc3

4 files changed

Lines changed: 172 additions & 75 deletions

File tree

‎benchmark/webstreams/lifecycle.js‎

Lines changed: 76 additions & 0 deletions

‎lib/internal/webstreams/readablestream.js‎

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,22 +1419,30 @@ const isReadableStreamBYOBReader =
14191419

14201420
// ---- ReadableStream Implementation
14211421

1422+
// The state record classes null out their prototype chain to keep
1423+
// Object.prototype out of property lookups; every field ever assigned
1424+
// is declared so the shape never transitions.
1425+
class ReadableStreamTransferState {
1426+
writable = undefined;
1427+
port1 = undefined;
1428+
port2 = undefined;
1429+
promise = undefined;
1430+
}
1431+
ObjectSetPrototypeOf(ReadableStreamTransferState.prototype, null);
1432+
1433+
class ReadableStreamState {
1434+
closedPromise = undefined;
1435+
disturbed = false;
1436+
reader = undefined;
1437+
state = 'readable';
1438+
storedError = undefined;
1439+
controller = undefined;
1440+
transfer = new ReadableStreamTransferState();
1441+
}
1442+
ObjectSetPrototypeOf(ReadableStreamState.prototype, null);
1443+
14221444
function createReadableStreamState() {
1423-
return {
1424-
__proto__: null,
1425-
closedPromise: undefined,
1426-
disturbed: false,
1427-
reader: undefined,
1428-
state: 'readable',
1429-
storedError: undefined,
1430-
transfer: {
1431-
__proto__: null,
1432-
writable: undefined,
1433-
port1: undefined,
1434-
port2: undefined,
1435-
promise: undefined,
1436-
},
1437-
};
1445+
return new ReadableStreamState();
14381446
}
14391447

14401448
function readableStreamFromIterable(iterable) {

‎lib/internal/webstreams/transformstream.js‎

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -253,21 +253,38 @@ ObjectDefineProperties(TransformStream.prototype, {
253253
[SymbolToStringTag]: getNonWritablePropertyDescriptor(TransformStream.name),
254254
});
255255

256+
// The state record classes null out their prototype chain to keep
257+
// Object.prototype out of property lookups; every field ever assigned
258+
// is declared so the shape never transitions.
259+
class TransformStreamState {
260+
readable = undefined;
261+
writable = undefined;
262+
controller = undefined;
263+
backpressure = undefined;
264+
// Continuation slots replacing the spec's
265+
// [[backpressureChangePromise]]; see transformStreamSetBackpressure.
266+
pullPending = false;
267+
pendingWrite = undefined;
268+
pendingWriteChunk = undefined;
269+
writeContinuation = undefined;
270+
}
271+
ObjectSetPrototypeOf(TransformStreamState.prototype, null);
272+
273+
class TransformStreamControllerState {
274+
stream = undefined;
275+
transformAlgorithm = undefined;
276+
flushAlgorithm = undefined;
277+
cancelAlgorithm = undefined;
278+
performTransformRejected = undefined;
279+
finishPromise = undefined;
280+
}
281+
ObjectSetPrototypeOf(TransformStreamControllerState.prototype, null);
282+
256283
function InternalTransferredTransformStream() {
257284
ObjectSetPrototypeOf(this, TransformStream.prototype);
258285
markTransferMode(this, false, true);
259286
this[kType] = 'TransformStream';
260-
this[kState] = {
261-
__proto__: null,
262-
readable: undefined,
263-
writable: undefined,
264-
backpressure: undefined,
265-
pullPending: false,
266-
pendingWrite: undefined,
267-
pendingWriteChunk: undefined,
268-
writeContinuation: undefined,
269-
controller: undefined,
270-
};
287+
this[kState] = new TransformStreamState();
271288
}
272289

273290
ObjectSetPrototypeOf(InternalTransferredTransformStream.prototype, TransformStream.prototype);
@@ -388,19 +405,10 @@ function initializeTransformStream(
388405
readableSizeAlgorithm,
389406
);
390407

391-
stream[kState] = {
392-
__proto__: null,
393-
readable,
394-
writable,
395-
controller: undefined,
396-
backpressure: undefined,
397-
// Continuation slots replacing the spec's
398-
// [[backpressureChangePromise]]; see transformStreamSetBackpressure.
399-
pullPending: false,
400-
pendingWrite: undefined,
401-
pendingWriteChunk: undefined,
402-
writeContinuation: undefined,
403-
};
408+
const state = new TransformStreamState();
409+
state.readable = readable;
410+
state.writable = writable;
411+
stream[kState] = state;
404412

405413
transformStreamSetBackpressure(stream, true);
406414
}
@@ -470,14 +478,12 @@ function setupTransformStreamDefaultController(
470478
cancelAlgorithm) {
471479
assert(isTransformStream(stream));
472480
assert(stream[kState].controller === undefined);
473-
controller[kState] = {
474-
__proto__: null,
475-
stream,
476-
transformAlgorithm,
477-
flushAlgorithm,
478-
cancelAlgorithm,
479-
performTransformRejected: undefined,
480-
};
481+
const controllerState = new TransformStreamControllerState();
482+
controllerState.stream = stream;
483+
controllerState.transformAlgorithm = transformAlgorithm;
484+
controllerState.flushAlgorithm = flushAlgorithm;
485+
controllerState.cancelAlgorithm = cancelAlgorithm;
486+
controller[kState] = controllerState;
481487
stream[kState].controller = controller;
482488
}
483489

‎lib/internal/webstreams/writablestream.js‎

Lines changed: 35 additions & 28 deletions

0 commit comments

Comments
 (0)