Skip to content

Commit bc320cd

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 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>
1 parent 3d85c94 commit bc320cd

3 files changed

Lines changed: 38 additions & 9 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: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,19 +105,19 @@ 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).
109-
const kNilRequest = {
110-
__proto__: null,
108+
// state reset (one per write on the hot path). The prototype is nulled
109+
// after creation: a `__proto__: null` literal is created in dictionary
110+
// mode, and these fields are loaded several times per write.
111+
const kNilRequest = ObjectSetPrototypeOf({
111112
promise: undefined,
112113
resolve: undefined,
113114
reject: undefined,
114-
};
115-
const kNilPendingAbortRequest = {
116-
__proto__: null,
115+
}, null);
116+
const kNilPendingAbortRequest = ObjectSetPrototypeOf({
117117
abort: kNilRequest,
118118
reason: undefined,
119119
wasAlreadyErroring: false,
120-
};
120+
}, null);
121121

122122
let releasedError;
123123

0 commit comments

Comments
 (0)