Commit 82563d8c395 for nodejs
commit 82563d8c3959e6b4b845b42073b4bd2c0dc3abfd
Author: James M Snell <jasnell@gmail.com>
Date: Sun Oct 4 01:33:51 2026 +0000
stream: keep long-lived stream/iter objects in fast mode
Object literals with `__proto__: null` are created in V8 dictionary
mode. Create the objects that live as long as a stream and are used for
every chunk with ObjectSetPrototypeOf() instead, as was done for the
share and broadcast consumer state, so that they keep fast properties:
- the iterators returned by push(), pull(), share(), shareSync() and
broadcast() consumers, and the pull() consumer-cleanup wrapper,
- the iterators and the cancellation context used by from()
normalization,
- the async wrapper share() uses for sync sources.
Objects created per call or per chunk (iterator results, options bags,
promise resolver records, single-use iterables) keep the literal form:
for those, setting the prototype after creation costs more than it
saves, about 2x slower in a create-and-read microbenchmark. The
fromWritable() writer is also unchanged, since V8 keeps object literals
with accessors in dictionary mode regardless.
With 200,000 16-byte chunks, pipeTo() is about 3.5% faster and pull()
with a transform or a signal about 1-1.5% faster. In
benchmark/streams/iter-throughput-share*.js, share() and shareSync()
improve by 1-4.5%; no benchmark regressed significantly.
Assisted-by: OpenCode
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66648
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
diff --git a/lib/internal/streams/iter/broadcast.js b/lib/internal/streams/iter/broadcast.js
index cdf25021bbc..e862f8d2a7a 100644
--- a/lib/internal/streams/iter/broadcast.js
+++ b/lib/internal/streams/iter/broadcast.js
@@ -225,8 +225,7 @@ class BroadcastImpl {
return {
__proto__: null,
[SymbolAsyncIterator]() {
- return {
- __proto__: null,
+ return ObjectSetPrototypeOf({
next() {
if (state.detached) {
if (state.error !== kNoBroadcastError) {
@@ -284,7 +283,7 @@ class BroadcastImpl {
detach();
return kDone;
},
- };
+ }, null);
},
};
}
diff --git a/lib/internal/streams/iter/from.js b/lib/internal/streams/iter/from.js
index e434ff9c253..9f39e0f614a 100644
--- a/lib/internal/streams/iter/from.js
+++ b/lib/internal/streams/iter/from.js
@@ -15,6 +15,7 @@ const {
DataViewPrototypeGetByteLength,
DataViewPrototypeGetByteOffset,
FunctionPrototypeCall,
+ ObjectSetPrototypeOf,
PromisePrototypeThen,
PromiseResolve,
PromiseWithResolvers,
@@ -68,13 +69,12 @@ const kNormalizationCancelled = Symbol('kNormalizationCancelled');
const kFlushBatch = Symbol('kFlushBatch');
function createNormalizationContext() {
- return {
- __proto__: null,
+ return ObjectSetPrototypeOf({
cancelled: false,
reason: undefined,
resolve: null,
suppressCleanup: false,
- };
+ }, null);
}
function cancelNormalization(context, reason, suppressCleanup = false) {
@@ -112,8 +112,7 @@ async function waitForNormalization(value, context) {
function createNormalizationIterator(createIterator) {
const context = createNormalizationContext();
const iterator = createIterator(context);
- return {
- __proto__: null,
+ return ObjectSetPrototypeOf({
next(value) {
return FunctionPrototypeCall(iterator.next, iterator, value);
},
@@ -129,7 +128,7 @@ function createNormalizationIterator(createIterator) {
[SymbolAsyncIterator]() {
return this;
},
- };
+ }, null);
}
function createNormalizationSource(createIterator) {
@@ -386,8 +385,7 @@ function yieldNormalizationAbortable(source, context) {
}
}
- return {
- __proto__: null,
+ return ObjectSetPrototypeOf({
async next() {
if (completed) {
return { __proto__: null, done: true, value: undefined };
@@ -433,7 +431,7 @@ function yieldNormalizationAbortable(source, context) {
[SymbolAsyncIterator]() {
return this;
},
- };
+ }, null);
},
};
}
diff --git a/lib/internal/streams/iter/pull.js b/lib/internal/streams/iter/pull.js
index 176d8dbfc83..6dff97e42be 100644
--- a/lib/internal/streams/iter/pull.js
+++ b/lib/internal/streams/iter/pull.js
@@ -11,6 +11,7 @@ const {
ArrayPrototypePush,
ArrayPrototypeSlice,
FunctionPrototypeCall,
+ ObjectSetPrototypeOf,
PromisePrototypeThen,
PromiseReject,
PromiseResolve,
@@ -861,8 +862,7 @@ function pull(source, ...args) {
yield* createAsyncPipeline(normalized, transforms, controller.signal);
}
const iterator = pipeline();
- return {
- __proto__: null,
+ return ObjectSetPrototypeOf({
next(value) {
return iterator.next(value);
},
@@ -877,7 +877,7 @@ function pull(source, ...args) {
[SymbolAsyncIterator]() {
return this;
},
- };
+ }, null);
}
return createAbortablePullIterator(normalized, transforms, signal);
},
@@ -907,8 +907,7 @@ function createAbortablePullIterator(source, transforms, signal) {
throw error;
}
- return {
- __proto__: null,
+ return ObjectSetPrototypeOf({
next(value) {
if (aborted) return PromiseReject(signal.reason);
return PromisePrototypeThen(iterator.next(value), undefined, onRejected);
@@ -929,7 +928,7 @@ function createAbortablePullIterator(source, transforms, signal) {
[SymbolAsyncIterator]() {
return this;
},
- };
+ }, null);
}
// Keep ownership of a bonded consumer outside the transform pipeline so it can
@@ -986,8 +985,7 @@ function pullWithConsumerCleanup(source, transforms, signal) {
__proto__: null,
[SymbolAsyncIterator]() {
const iterator = pipeline[SymbolAsyncIterator]();
- return {
- __proto__: null,
+ return ObjectSetPrototypeOf({
next(value) {
return PromisePrototypeThen(
iterator.next(value),
@@ -1011,7 +1009,7 @@ function pullWithConsumerCleanup(source, transforms, signal) {
[SymbolAsyncIterator]() {
return this;
},
- };
+ }, null);
},
};
}
diff --git a/lib/internal/streams/iter/push.js b/lib/internal/streams/iter/push.js
index 764a3afac92..039e6b9f39d 100644
--- a/lib/internal/streams/iter/push.js
+++ b/lib/internal/streams/iter/push.js
@@ -7,6 +7,7 @@
const {
ArrayPrototypePush,
+ ObjectSetPrototypeOf,
PromisePrototypeThen,
PromiseReject,
PromiseResolve,
@@ -733,8 +734,7 @@ function createReadable(queue) {
return {
__proto__: null,
[SymbolAsyncIterator]() {
- return {
- __proto__: null,
+ return ObjectSetPrototypeOf({
async next() {
return queue.read();
},
@@ -746,7 +746,7 @@ function createReadable(queue) {
queue.consumerThrow(error);
throw error;
},
- };
+ }, null);
},
};
}
diff --git a/lib/internal/streams/iter/share.js b/lib/internal/streams/iter/share.js
index 6e9881b682a..a61d4627ad5 100644
--- a/lib/internal/streams/iter/share.js
+++ b/lib/internal/streams/iter/share.js
@@ -235,8 +235,7 @@ class ShareImpl {
}
};
- return {
- __proto__: null,
+ return ObjectSetPrototypeOf({
next() {
const next = PromisePrototypeThen(
state.pendingNext,
@@ -266,7 +265,7 @@ class ShareImpl {
}
return { __proto__: null, done: true, value: undefined };
},
- };
+ }, null);
},
};
}
@@ -401,8 +400,7 @@ class ShareImpl {
} else if (isSyncIterable(this.#source)) {
const syncIterator =
this.#source[SymbolIterator]();
- this.#sourceIterator = {
- __proto__: null,
+ this.#sourceIterator = ObjectSetPrototypeOf({
async next() {
return syncIterator.next();
},
@@ -410,7 +408,7 @@ class ShareImpl {
return syncIterator.return?.() ??
{ __proto__: null, done: true, value: undefined };
},
- };
+ }, null);
} else {
throw new ERR_INVALID_ARG_TYPE(
'source', ['AsyncIterable', 'Iterable'], this.#source);
@@ -592,8 +590,7 @@ class SyncShareImpl {
return {
__proto__: null,
[SymbolIterator]() {
- return {
- __proto__: null,
+ return ObjectSetPrototypeOf({
next() {
if (state.detached) {
if (state.error !== kNoShareError) throw state.error;
@@ -710,7 +707,7 @@ class SyncShareImpl {
}
return { __proto__: null, done: true, value: undefined };
},
- };
+ }, null);
},
};
}