Skip to content

Commit 6c0fb9a

Browse files
mcollinaaduh95
authored andcommitted
stream: avoid per-chunk promises in webstream adapters
Readable.fromWeb() and the read side of Duplex.fromWeb() allocated a promise, a read-result object, and two reaction closures for every chunk through reader.read(); a single reused read request now delivers chunks through readableStreamDefaultReaderRead() instead, forwarding each chunk in a microtask to preserve the delivery order relative to errors and destroy. Writable.fromWeb() and the write side of Duplex.fromWeb() paid two derived promises off writer.ready plus the writer.write() promise and a fresh closure pair per chunk; a single shared write request now dispatches chunks directly and settles the node callback, with failures delivered in a microtask. Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #65548 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 992c5f6 commit 6c0fb9a

3 files changed

Lines changed: 266 additions & 85 deletions

File tree

benchmark/webstreams/adapters.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
'use strict';
2+
const common = require('../common.js');
3+
const {
4+
Readable,
5+
Writable,
6+
} = require('node:stream');
7+
const {
8+
ReadableStream,
9+
WritableStream,
10+
} = require('node:stream/web');
11+
12+
const bench = common.createBenchmark(main, {
13+
n: [1e5],
14+
kind: [
15+
'readable-to-web',
16+
'readable-from-web',
17+
'writable-to-web',
18+
'writable-from-web',
19+
],
20+
});
21+
22+
async function readableToWeb(n) {
23+
const chunk = Buffer.alloc(1024);
24+
let i = 0;
25+
const streamReadable = new Readable({
26+
read() {
27+
if (i++ < n)
28+
this.push(chunk);
29+
else
30+
this.push(null);
31+
},
32+
});
33+
const reader = Readable.toWeb(streamReadable).getReader();
34+
bench.start();
35+
while (!(await reader.read()).done);
36+
bench.end(n);
37+
}
38+
39+
function readableFromWeb(n) {
40+
const chunk = Buffer.alloc(1024);
41+
let i = 0;
42+
const readableStream = new ReadableStream({
43+
pull(controller) {
44+
if (i++ < n)
45+
controller.enqueue(chunk);
46+
else
47+
controller.close();
48+
},
49+
});
50+
const streamReadable = Readable.fromWeb(readableStream);
51+
bench.start();
52+
streamReadable.on('data', () => {});
53+
streamReadable.on('end', () => bench.end(n));
54+
}
55+
56+
async function writableToWeb(n) {
57+
const chunk = Buffer.alloc(1024);
58+
const streamWritable = new Writable({
59+
write(chunk, encoding, callback) {
60+
callback();
61+
},
62+
});
63+
const writer = Writable.toWeb(streamWritable).getWriter();
64+
bench.start();
65+
for (let i = 0; i < n; i++)
66+
await writer.write(chunk);
67+
await writer.close();
68+
bench.end(n);
69+
}
70+
71+
function writableFromWeb(n) {
72+
const chunk = Buffer.alloc(1024);
73+
const writableStream = new WritableStream({
74+
write() {},
75+
});
76+
const streamWritable = Writable.fromWeb(writableStream);
77+
bench.start();
78+
let i = 0;
79+
function writeLoop() {
80+
while (i++ < n) {
81+
if (!streamWritable.write(chunk)) {
82+
streamWritable.once('drain', writeLoop);
83+
return;
84+
}
85+
}
86+
streamWritable.end(() => bench.end(n));
87+
}
88+
writeLoop();
89+
}
90+
91+
function main({ n, kind }) {
92+
switch (kind) {
93+
case 'readable-to-web':
94+
readableToWeb(n);
95+
break;
96+
case 'readable-from-web':
97+
readableFromWeb(n);
98+
break;
99+
case 'writable-to-web':
100+
writableToWeb(n);
101+
break;
102+
case 'writable-from-web':
103+
writableFromWeb(n);
104+
break;
105+
}
106+
}

0 commit comments

Comments
 (0)