Skip to content

Commit 43335b2

Browse files
committed
Alternative iteration function on Readable that don't rely on Async Iteration
1 parent c5635b8 commit 43335b2

6 files changed

Lines changed: 813 additions & 177 deletions

File tree

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const common = require('../common');
5+
const { Readable } = require('stream');
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [2e5],
9+
operation: [
10+
'reduce',
11+
'reduce-async',
12+
'map-sync-1',
13+
'map-sync-8',
14+
'map-async-1',
15+
'map-async-8',
16+
'find-sync-1',
17+
'find-sync-8',
18+
'find-async-1',
19+
'find-async-8',
20+
'filter',
21+
'forEach',
22+
'toArray',
23+
],
24+
source: ['readable', 'iterator', 'array'],
25+
});
26+
27+
function createSource(type, n) {
28+
if (type === 'array') {
29+
return Readable.from(Array.from({ length: n }, (_, i) => i));
30+
}
31+
let i = 0;
32+
33+
if (type === 'readable') {
34+
return new Readable({
35+
objectMode: true,
36+
read() {
37+
this.push(i === n ? null : i++);
38+
},
39+
});
40+
}
41+
42+
return Readable.from((function* generate() {
43+
while (i < n) {
44+
yield i++;
45+
}
46+
})());
47+
}
48+
49+
async function main({ n, operation, source }) {
50+
const readable = createSource(source, n);
51+
let result;
52+
53+
bench.start();
54+
55+
switch (operation) {
56+
case 'reduce':
57+
result = await readable.reduce((sum, value) => sum + value, 0);
58+
break;
59+
case 'reduce-async':
60+
result = await readable.reduce(async (sum, value) => sum + value, 0);
61+
break;
62+
case 'map-sync-1':
63+
result = await readable
64+
.map((value) => value + 1, { concurrency: 1 })
65+
.reduce((sum, value) => sum + value, 0);
66+
break;
67+
case 'map-sync-8':
68+
result = await readable
69+
.map((value) => value + 1, { concurrency: 8 })
70+
.reduce((sum, value) => sum + value, 0);
71+
break;
72+
case 'map-async-1':
73+
result = await readable
74+
.map(async (value) => value + 1, { concurrency: 1 })
75+
.reduce((sum, value) => sum + value, 0);
76+
break;
77+
case 'map-async-8':
78+
result = await readable
79+
.map(async (value) => value + 1, { concurrency: 8 })
80+
.reduce((sum, value) => sum + value, 0);
81+
break;
82+
case 'find-sync-1':
83+
result = await readable.find((value) => value === n - 1, { concurrency: 1 });
84+
break;
85+
case 'find-sync-8':
86+
result = await readable.find((value) => value === n - 1, { concurrency: 8 });
87+
break;
88+
case 'find-async-1':
89+
result = await readable.find(async (value) => value === n - 1, { concurrency: 1 });
90+
break;
91+
case 'find-async-8':
92+
result = await readable.find(async (value) => value === n - 1, { concurrency: 8 });
93+
break;
94+
case 'filter':
95+
result = await readable
96+
.filter((value) => (value & 1) === 0)
97+
.reduce((sum, value) => sum + value, 0);
98+
break;
99+
case 'forEach':
100+
result = 0;
101+
await readable.forEach((value) => { result += value; });
102+
break;
103+
case 'toArray':
104+
result = await readable.toArray();
105+
break;
106+
default:
107+
throw new Error(`Unknown operation: ${operation}`);
108+
}
109+
110+
bench.end(n);
111+
112+
const sum = n * (n - 1) / 2;
113+
switch (operation) {
114+
case 'map-sync-1':
115+
case 'map-sync-8':
116+
case 'map-async-1':
117+
case 'map-async-8':
118+
assert.strictEqual(result, sum + n);
119+
break;
120+
case 'find-sync-1':
121+
case 'find-sync-8':
122+
case 'find-async-1':
123+
case 'find-async-8':
124+
assert.strictEqual(result, n - 1);
125+
break;
126+
case 'filter': {
127+
const evenCount = Math.ceil(n / 2);
128+
assert.strictEqual(result, evenCount * (evenCount - 1));
129+
break;
130+
}
131+
case 'toArray':
132+
assert.strictEqual(result.length, n);
133+
assert.strictEqual(result[n - 1], n - 1);
134+
break;
135+
default:
136+
assert.strictEqual(result, sum);
137+
}
138+
}

lib/internal/streams/from.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const {
4+
ArrayIsArray,
45
PromisePrototypeThen,
56
SymbolAsyncIterator,
67
SymbolIterator,
@@ -15,6 +16,10 @@ const {
1516
},
1617
} = require('internal/errors');
1718

19+
const {
20+
getDefaultHighWaterMark,
21+
} = require('internal/streams/state');
22+
1823
function from(Readable, iterable, opts) {
1924
let iterator;
2025
if (typeof iterable === 'string' || iterable instanceof Buffer) {
@@ -26,6 +31,8 @@ function from(Readable, iterable, opts) {
2631
this.push(null);
2732
},
2833
});
34+
} else if (iterable instanceof Readable) {
35+
return iterable;
2936
}
3037

3138
let isAsync;
@@ -39,10 +46,9 @@ function from(Readable, iterable, opts) {
3946
throw new ERR_INVALID_ARG_TYPE('iterable', ['Iterable'], iterable);
4047
}
4148

42-
4349
const readable = new Readable({
4450
objectMode: true,
45-
highWaterMark: 1,
51+
highWaterMark: ArrayIsArray(iterable) ? getDefaultHighWaterMark(true) : 1,
4652
// TODO(ronag): What options should be allowed?
4753
...opts,
4854
});

0 commit comments

Comments
 (0)