Skip to content

Commit 992b90b

Browse files
committed
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 plain literals; every field is an own property, so the prototype is never consulted. 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>
1 parent 3d85c94 commit 992b90b

3 files changed

Lines changed: 35 additions & 5 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict';
2+
const common = require('../common.js');
3+
const assert = require('node:assert');
4+
const { WritableStream } = require('node:stream/web');
5+
6+
const bench = common.createBenchmark(main, {
7+
n: [1e5],
8+
type: ['await', 'queued'],
9+
});
10+
11+
async function main({ n, type }) {
12+
let count = 0;
13+
const ws = new WritableStream({
14+
write() {
15+
count++;
16+
},
17+
}, { highWaterMark: type === 'queued' ? n : 1 });
18+
const writer = ws.getWriter();
19+
bench.start();
20+
if (type === 'await') {
21+
for (let i = 0; i < n; i++)
22+
await writer.write('a');
23+
} else {
24+
for (let i = 1; i < n; i++)
25+
writer.write('a');
26+
await writer.write('a');
27+
}
28+
bench.end(n);
29+
await writer.close();
30+
assert.strictEqual(count, n);
31+
}

‎lib/internal/webstreams/readablestream.js‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,7 +1140,6 @@ ObjectDefineProperties(ReadableStreamBYOBReader.prototype, {
11401140

11411141
class ReadableStreamDefaultController {
11421142
[kType] = 'ReadableStreamDefaultController';
1143-
[kState] = {};
11441143

11451144
constructor(skipThrowSymbol = undefined) {
11461145
if (skipThrowSymbol !== kSkipThrow) {
@@ -1203,7 +1202,6 @@ ObjectDefineProperties(ReadableStreamDefaultController.prototype, {
12031202

12041203
class ReadableByteStreamController {
12051204
[kType] = 'ReadableByteStreamController';
1206-
[kState] = {};
12071205

12081206
constructor(skipThrowSymbol = undefined) {
12091207
if (skipThrowSymbol !== kSkipThrow) {

‎lib/internal/webstreams/writablestream.js‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,16 @@ const kSkipThrow = Symbol('kSkipThrow');
105105
// Shared sentinels for the "no pending request" state records. These
106106
// records are only ever replaced wholesale and never mutated in place,
107107
// so single shared instances are safe and avoid an allocation on every
108-
// state reset (one per write on the hot path).
108+
// state reset (one per write on the hot path). They are plain literals:
109+
// a `__proto__: null` literal is created in dictionary mode, and these
110+
// fields are loaded several times per write. Every field is an own
111+
// property, so Object.prototype is never consulted.
109112
const kNilRequest = {
110-
__proto__: null,
111113
promise: undefined,
112114
resolve: undefined,
113115
reject: undefined,
114116
};
115117
const kNilPendingAbortRequest = {
116-
__proto__: null,
117118
abort: kNilRequest,
118119
reason: undefined,
119120
wasAlreadyErroring: false,

0 commit comments

Comments
 (0)