Commit cff7b12df1f for nodejs
commit cff7b12df1f9898a6614ee9a3c4506337cf0305b
Author: Matteo Collina <hello@matteocollina.com>
Date: Mon Sep 28 09:51:16 2026 +0200
stream: keep webstreams nil requests in fast mode
The shared "no pending request" records in the writable stream were
`__proto__: null` literals, which V8 creates in dictionary mode. They
sit in inFlightWriteRequest, closeRequest and pendingAbortRequest
whenever nothing is pending, and their promise field is checked several
times per write, so those loads did a hash lookup on every write and
every pipe. They are now built as plain literals and get their null
prototype afterwards, which keeps them in fast mode.
The readable controllers also initialized their state slot with an
empty object that setup replaced immediately. That throwaway allocation
is gone, matching the writable and transform controllers.
Add a writable-write benchmark: nothing in benchmark/webstreams drove
WritableStreamDefaultWriter.write() directly.
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/66230
Reviewed-By: Mattias Buelens <mattias@buelens.com>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
diff --git a/benchmark/webstreams/writable-write.js b/benchmark/webstreams/writable-write.js
new file mode 100644
index 00000000000..9cc6a6af242
--- /dev/null
+++ b/benchmark/webstreams/writable-write.js
@@ -0,0 +1,31 @@
+'use strict';
+const common = require('../common.js');
+const assert = require('node:assert');
+const { WritableStream } = require('node:stream/web');
+
+const bench = common.createBenchmark(main, {
+ n: [1e5],
+ type: ['await', 'queued'],
+});
+
+async function main({ n, type }) {
+ let count = 0;
+ const ws = new WritableStream({
+ write() {
+ count++;
+ },
+ }, { highWaterMark: type === 'queued' ? n : 1 });
+ const writer = ws.getWriter();
+ bench.start();
+ if (type === 'await') {
+ for (let i = 0; i < n; i++)
+ await writer.write('a');
+ } else {
+ for (let i = 1; i < n; i++)
+ writer.write('a');
+ await writer.write('a');
+ }
+ bench.end(n);
+ await writer.close();
+ assert.strictEqual(count, n);
+}
diff --git a/lib/internal/webstreams/readablestream.js b/lib/internal/webstreams/readablestream.js
index 3af9fd24b66..db3a13fef1c 100644
--- a/lib/internal/webstreams/readablestream.js
+++ b/lib/internal/webstreams/readablestream.js
@@ -1140,7 +1140,7 @@ ObjectDefineProperties(ReadableStreamBYOBReader.prototype, {
class ReadableStreamDefaultController {
[kType] = 'ReadableStreamDefaultController';
- [kState] = {};
+ [kState];
constructor(skipThrowSymbol = undefined) {
if (skipThrowSymbol !== kSkipThrow) {
@@ -1203,7 +1203,7 @@ ObjectDefineProperties(ReadableStreamDefaultController.prototype, {
class ReadableByteStreamController {
[kType] = 'ReadableByteStreamController';
- [kState] = {};
+ [kState];
constructor(skipThrowSymbol = undefined) {
if (skipThrowSymbol !== kSkipThrow) {
diff --git a/lib/internal/webstreams/transformstream.js b/lib/internal/webstreams/transformstream.js
index 25771fb6a55..81f60b284f5 100644
--- a/lib/internal/webstreams/transformstream.js
+++ b/lib/internal/webstreams/transformstream.js
@@ -303,6 +303,7 @@ TransferredTransformStream.prototype[kDeserialize] = () => {};
class TransformStreamDefaultController {
[kType] = 'TransformStreamDefaultController';
+ [kState];
constructor(skipThrowSymbol = undefined) {
if (skipThrowSymbol !== kSkipThrow) {
diff --git a/lib/internal/webstreams/writablestream.js b/lib/internal/webstreams/writablestream.js
index 73d7ed0fbf8..31f2a1f875d 100644
--- a/lib/internal/webstreams/writablestream.js
+++ b/lib/internal/webstreams/writablestream.js
@@ -105,19 +105,19 @@ const kSkipThrow = Symbol('kSkipThrow');
// Shared sentinels for the "no pending request" state records. These
// records are only ever replaced wholesale and never mutated in place,
// so single shared instances are safe and avoid an allocation on every
-// state reset (one per write on the hot path).
-const kNilRequest = {
- __proto__: null,
+// state reset (one per write on the hot path). The prototype is nulled
+// after creation: a `__proto__: null` literal is created in dictionary
+// mode, and these fields are loaded several times per write.
+const kNilRequest = ObjectSetPrototypeOf({
promise: undefined,
resolve: undefined,
reject: undefined,
-};
-const kNilPendingAbortRequest = {
- __proto__: null,
+}, null);
+const kNilPendingAbortRequest = ObjectSetPrototypeOf({
abort: kNilRequest,
reason: undefined,
wasAlreadyErroring: false,
-};
+}, null);
let releasedError;
@@ -502,6 +502,7 @@ ObjectDefineProperties(WritableStreamDefaultWriter.prototype, {
class WritableStreamDefaultController {
[kType] = 'WritableStreamDefaultController';
+ [kState];
constructor(skipThrowSymbol = undefined) {
if (skipThrowSymbol !== kSkipThrow) {