Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 5f35204

Browse files
mscdexFishrock123
authored andcommittedJun 27, 2016
stream: improve Readable.read() performance
read() performance is improved most by switching from an array to a linked list for storing buffered data. However, other changes that also contribute include: making some hot functions inlinable, faster read() argument checking, and misc code rearrangement to avoid unnecessary code execution. PR-URL: #7077 Reviewed-By: Calvin Metcalf <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent d8fee36 commit 5f35204

10 files changed

+420
-137
lines changed
 

‎benchmark/streams/readable-bigread.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const v8 = require('v8');
5+
const Readable = require('stream').Readable;
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [100e1]
9+
});
10+
11+
function main(conf) {
12+
const n = +conf.n;
13+
const b = new Buffer(32);
14+
const s = new Readable();
15+
function noop() {}
16+
s._read = noop;
17+
18+
// Force optimization before starting the benchmark
19+
s.push(b);
20+
v8.setFlagsFromString('--allow_natives_syntax');
21+
eval('%OptimizeFunctionOnNextCall(s.read)');
22+
s.push(b);
23+
while (s.read(128));
24+
25+
bench.start();
26+
for (var k = 0; k < n; ++k) {
27+
for (var i = 0; i < 1e4; ++i)
28+
s.push(b);
29+
while (s.read(128));
30+
}
31+
bench.end(n);
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const v8 = require('v8');
5+
const Readable = require('stream').Readable;
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [100e1]
9+
});
10+
11+
function main(conf) {
12+
const n = +conf.n;
13+
const b = new Buffer(32);
14+
const s = new Readable();
15+
function noop() {}
16+
s._read = noop;
17+
18+
// Force optimization before starting the benchmark
19+
s.push(b);
20+
v8.setFlagsFromString('--allow_natives_syntax');
21+
eval('%OptimizeFunctionOnNextCall(s.read)');
22+
s.push(b);
23+
while (s.read(106));
24+
25+
bench.start();
26+
for (var k = 0; k < n; ++k) {
27+
for (var i = 0; i < 1e4; ++i)
28+
s.push(b);
29+
while (s.read(106));
30+
}
31+
bench.end(n);
32+
}
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const v8 = require('v8');
5+
const Readable = require('stream').Readable;
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [200e1]
9+
});
10+
11+
function main(conf) {
12+
const n = +conf.n;
13+
const b = new Buffer(32);
14+
const s = new Readable();
15+
function noop() {}
16+
s._read = noop;
17+
18+
// Force optimization before starting the benchmark
19+
s.push(b);
20+
v8.setFlagsFromString('--allow_natives_syntax');
21+
eval('%OptimizeFunctionOnNextCall(s.push)');
22+
eval('%OptimizeFunctionOnNextCall(s.read)');
23+
s.push(b);
24+
while (s.read(32));
25+
26+
bench.start();
27+
for (var k = 0; k < n; ++k) {
28+
for (var i = 0; i < 1e4; ++i)
29+
s.push(b);
30+
while (s.read(32));
31+
}
32+
bench.end(n);
33+
}

‎benchmark/streams/readable-readall.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const v8 = require('v8');
5+
const Readable = require('stream').Readable;
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [50e2]
9+
});
10+
11+
function main(conf) {
12+
const n = +conf.n;
13+
const b = new Buffer(32);
14+
const s = new Readable();
15+
function noop() {}
16+
s._read = noop;
17+
18+
// Force optimization before starting the benchmark
19+
s.push(b);
20+
v8.setFlagsFromString('--allow_natives_syntax');
21+
eval('%OptimizeFunctionOnNextCall(s.read)');
22+
s.push(b);
23+
while (s.read());
24+
25+
bench.start();
26+
for (var k = 0; k < n; ++k) {
27+
for (var i = 0; i < 1e4; ++i)
28+
s.push(b);
29+
while (s.read());
30+
}
31+
bench.end(n);
32+
}
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const v8 = require('v8');
5+
const Readable = require('stream').Readable;
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [100e1]
9+
});
10+
11+
function main(conf) {
12+
const n = +conf.n;
13+
const b = new Buffer(32);
14+
const s = new Readable();
15+
function noop() {}
16+
s._read = noop;
17+
18+
// Force optimization before starting the benchmark
19+
s.push(b);
20+
v8.setFlagsFromString('--allow_natives_syntax');
21+
eval('%OptimizeFunctionOnNextCall(s.read)');
22+
s.push(b);
23+
while (s.read(12));
24+
25+
bench.start();
26+
for (var k = 0; k < n; ++k) {
27+
for (var i = 0; i < 1e4; ++i)
28+
s.push(b);
29+
while (s.read(12));
30+
}
31+
bench.end(n);
32+
}

‎lib/_stream_readable.js

+172-133
Large diffs are not rendered by default.

‎lib/internal/streams/BufferList.js

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
'use strict';
2+
3+
const Buffer = require('buffer').Buffer;
4+
5+
module.exports = BufferList;
6+
7+
function BufferList() {
8+
this.head = null;
9+
this.tail = null;
10+
this.length = 0;
11+
}
12+
13+
BufferList.prototype.push = function(v) {
14+
const entry = { data: v, next: null };
15+
if (this.length > 0)
16+
this.tail.next = entry;
17+
else
18+
this.head = entry;
19+
this.tail = entry;
20+
++this.length;
21+
};
22+
23+
BufferList.prototype.unshift = function(v) {
24+
const entry = { data: v, next: this.head };
25+
if (this.length === 0)
26+
this.tail = entry;
27+
this.head = entry;
28+
++this.length;
29+
};
30+
31+
BufferList.prototype.shift = function() {
32+
if (this.length === 0)
33+
return;
34+
const ret = this.head.data;
35+
if (this.length === 1)
36+
this.head = this.tail = null;
37+
else
38+
this.head = this.head.next;
39+
--this.length;
40+
return ret;
41+
};
42+
43+
BufferList.prototype.clear = function() {
44+
this.head = this.tail = null;
45+
this.length = 0;
46+
};
47+
48+
BufferList.prototype.join = function(s) {
49+
if (this.length === 0)
50+
return '';
51+
var p = this.head;
52+
var ret = '' + p.data;
53+
while (p = p.next)
54+
ret += s + p.data;
55+
return ret;
56+
};
57+
58+
BufferList.prototype.concat = function(n) {
59+
if (this.length === 0)
60+
return Buffer.alloc(0);
61+
if (this.length === 1)
62+
return this.head.data;
63+
const ret = Buffer.allocUnsafe(n >>> 0);
64+
var p = this.head;
65+
var i = 0;
66+
while (p) {
67+
p.data.copy(ret, i);
68+
i += p.data.length;
69+
p = p.next;
70+
}
71+
return ret;
72+
};

‎node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
'lib/internal/v8_prof_polyfill.js',
8989
'lib/internal/v8_prof_processor.js',
9090
'lib/internal/streams/lazy_transform.js',
91+
'lib/internal/streams/BufferList.js',
9192
'deps/v8/tools/splaytree.js',
9293
'deps/v8/tools/codemap.js',
9394
'deps/v8/tools/consarray.js',

‎test/parallel/test-stream-push-order.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ s.read(0);
2626
// ACTUALLY [1, 3, 5, 6, 4, 2]
2727

2828
process.on('exit', function() {
29-
assert.deepStrictEqual(s._readableState.buffer,
30-
['1', '2', '3', '4', '5', '6']);
29+
assert.deepStrictEqual(s._readableState.buffer.join(','), '1,2,3,4,5,6');
3130
console.log('ok');
3231
});

‎test/parallel/test-stream2-readable-from-list.js

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
// Flags: --expose_internals
12
'use strict';
23
require('../common');
34
var assert = require('assert');
45
var fromList = require('_stream_readable')._fromList;
6+
var BufferList = require('internal/streams/BufferList');
57

68
// tiny node-tap lookalike.
79
var tests = [];
@@ -30,6 +32,13 @@ function run() {
3032
});
3133
}
3234

35+
function bufferListFromArray(arr) {
36+
const bl = new BufferList();
37+
for (var i = 0; i < arr.length; ++i)
38+
bl.push(arr[i]);
39+
return bl;
40+
}
41+
3342
// ensure all tests have run
3443
process.on('exit', function() {
3544
assert.equal(count, 0);
@@ -43,6 +52,7 @@ test('buffers', function(t) {
4352
Buffer.from('bark'),
4453
Buffer.from('bazy'),
4554
Buffer.from('kuel') ];
55+
list = bufferListFromArray(list);
4656

4757
// read more than the first element.
4858
var ret = fromList(6, { buffer: list, length: 16 });
@@ -61,7 +71,7 @@ test('buffers', function(t) {
6171
t.equal(ret.toString(), 'zykuel');
6272

6373
// all consumed.
64-
t.same(list, []);
74+
t.same(list, new BufferList());
6575

6676
t.end();
6777
});
@@ -71,6 +81,7 @@ test('strings', function(t) {
7181
'bark',
7282
'bazy',
7383
'kuel' ];
84+
list = bufferListFromArray(list);
7485

7586
// read more than the first element.
7687
var ret = fromList(6, { buffer: list, length: 16, decoder: true });
@@ -89,7 +100,7 @@ test('strings', function(t) {
89100
t.equal(ret, 'zykuel');
90101

91102
// all consumed.
92-
t.same(list, []);
103+
t.same(list, new BufferList());
93104

94105
t.end();
95106
});

0 commit comments

Comments
 (0)
Please sign in to comment.