Commit 7ff62671a58 for nodejs
commit 7ff62671a58c2756cee05db1d87e2e0bfed6991f
Author: Matteo Collina <hello@matteocollina.com>
Date: Sat Sep 26 23:43:46 2026 +0200
stream: keep consumer state in fast mode
Create null-prototype share and broadcast consumer state with fast
properties instead of V8 dictionary properties.
Assisted-by: Pi
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/66266
Reviewed-By: Mattias Buelens <mattias@buelens.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
diff --git a/benchmark/streams/iter-throughput-share-sync.js b/benchmark/streams/iter-throughput-share-sync.js
new file mode 100644
index 00000000000..3a5de56b592
--- /dev/null
+++ b/benchmark/streams/iter-throughput-share-sync.js
@@ -0,0 +1,37 @@
+'use strict';
+
+const common = require('../common.js');
+
+const bench = common.createBenchmark(main, {
+ consumers: [2, 8, 32],
+ batches: [1e4],
+ n: [5],
+}, {
+ flags: ['--experimental-stream-iter'],
+});
+
+function main({ consumers, batches, n }) {
+ const { shareSync } = require('stream/iter');
+ const chunk = Buffer.alloc(1024);
+ let bytes = 0;
+
+ function* source() {
+ for (let i = 0; i < batches; i++) yield [chunk];
+ }
+
+ bench.start();
+ for (let run = 0; run < n; run++) {
+ const shared = shareSync(source(), { budget: 65536 });
+ const readers = Array.from({ length: consumers }, () =>
+ shared.pull()[Symbol.iterator]());
+ for (let i = 0; i < batches; i++) {
+ for (let j = 0; j < consumers; j++) {
+ bytes += readers[j].next().value[0].byteLength;
+ }
+ }
+ }
+ if (bytes !== batches * consumers * n * chunk.byteLength) {
+ throw new Error('Incorrect byte count');
+ }
+ bench.end(batches * consumers * n);
+}
diff --git a/lib/internal/streams/iter/broadcast.js b/lib/internal/streams/iter/broadcast.js
index 5d85af8b7da..e32f8aa544d 100644
--- a/lib/internal/streams/iter/broadcast.js
+++ b/lib/internal/streams/iter/broadcast.js
@@ -11,6 +11,7 @@ const {
ArrayPrototypePush,
ArrayPrototypeShift,
FunctionPrototypeCall,
+ ObjectSetPrototypeOf,
PromisePrototypeThen,
PromiseReject,
PromiseResolve,
@@ -185,8 +186,7 @@ class BroadcastImpl {
}
#createRawConsumer() {
- const state = {
- __proto__: null,
+ const state = ObjectSetPrototypeOf({
// Start at the oldest buffered entry so late-joining consumers
// can read data already in the buffer.
cursor: this.#bufferStart,
@@ -195,7 +195,7 @@ class BroadcastImpl {
pending: [],
detached: false,
error: kNoBroadcastError,
- };
+ }, null);
this.#consumers.add(state);
if (this.#consumers.size === 1) {
diff --git a/lib/internal/streams/iter/share.js b/lib/internal/streams/iter/share.js
index 57e449f9e1c..b53da4608b2 100644
--- a/lib/internal/streams/iter/share.js
+++ b/lib/internal/streams/iter/share.js
@@ -8,6 +8,7 @@
const {
ArrayPrototypePush,
FunctionPrototypeCall,
+ ObjectSetPrototypeOf,
PromisePrototypeThen,
PromiseResolve,
PromiseWithResolvers,
@@ -143,15 +144,14 @@ class ShareImpl {
}
#createRawConsumer() {
- const state = {
- __proto__: null,
+ const state = ObjectSetPrototypeOf({
cursor: this.#bufferStart,
resolve: null,
reject: null,
detached: false,
error: kNoShareError,
pendingNext: PromiseResolve(),
- };
+ }, null);
this.#consumers.add(state);
if (this.#consumers.size === 1) {
@@ -549,12 +549,11 @@ class SyncShareImpl {
}
#createRawConsumer() {
- const state = {
- __proto__: null,
+ const state = ObjectSetPrototypeOf({
cursor: this.#bufferStart,
detached: false,
error: kNoShareError,
- };
+ }, null);
this.#consumers.add(state);
if (this.#consumers.size === 1) {