Skip to content

Commit 77e2efe

Browse files
committed
console: refactor to use rest params and template strings
Overall cleanup in code, eliminate reliance on `arguments`. Benchmarks show that as of v8 5.0.71.32, using rest params + apply has good performance. The spread operator is not yet well optimized in v8 ``` misc/console.js method=restAndSpread concat=1 n=1000000: 374779.38359 misc/console.js method=restAndSpread concat=0 n=1000000: 375988.30434 misc/console.js method=argumentsAndApply concat=1 n=1000000: 682618.61125 misc/console.js method=argumentsAndApply concat=0 n=1000000: 645093.74443 misc/console.js method=restAndApply concat=1 n=1000000: 682931.41217 misc/console.js method=restAndApply concat=0 n=1000000: 664473.09700 ``` PR-URL: #6233 Reviewed-By: Brian White <mscdex@mscdex.net>
1 parent c0abecd commit 77e2efe

File tree

2 files changed

+148
-16
lines changed

2 files changed

+148
-16
lines changed

benchmark/misc/console.js

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const assert = require('assert');
5+
const Writable = require('stream').Writable;
6+
const util = require('util');
7+
const v8 = require('v8');
8+
9+
v8.setFlagsFromString('--allow_natives_syntax');
10+
11+
var bench = common.createBenchmark(main, {
12+
method: ['restAndSpread',
13+
'argumentsAndApply',
14+
'restAndApply',
15+
'restAndConcat'],
16+
concat: [1, 0],
17+
n: [1000000]
18+
});
19+
20+
const nullStream = createNullStream();
21+
22+
function usingRestAndConcat(...args) {
23+
nullStream.write('this is ' + args[0] + ' of ' + args[1] + '\n');
24+
}
25+
26+
function usingRestAndSpreadTS(...args) {
27+
nullStream.write(`${util.format(...args)}\n`);
28+
}
29+
30+
function usingRestAndApplyTS(...args) {
31+
nullStream.write(`${util.format.apply(null, args)}\n`);
32+
}
33+
34+
function usingArgumentsAndApplyTS() {
35+
nullStream.write(`${util.format.apply(null, arguments)}\n`);
36+
}
37+
38+
function usingRestAndSpreadC(...args) {
39+
nullStream.write(util.format(...args) + '\n');
40+
}
41+
42+
function usingRestAndApplyC(...args) {
43+
nullStream.write(util.format.apply(null, args) + '\n');
44+
}
45+
46+
function usingArgumentsAndApplyC() {
47+
nullStream.write(util.format.apply(null, arguments) + '\n');
48+
}
49+
50+
function optimize(method, ...args) {
51+
method(...args);
52+
eval(`%OptimizeFunctionOnNextCall(${method.name})`);
53+
method(...args);
54+
}
55+
56+
function runUsingRestAndConcat(n) {
57+
optimize(usingRestAndConcat, 'a', 1);
58+
59+
var i = 0;
60+
bench.start();
61+
for (; i < n; i++)
62+
usingRestAndConcat('a', 1);
63+
bench.end(n);
64+
}
65+
66+
function runUsingRestAndSpread(n, concat) {
67+
68+
const method = concat ? usingRestAndSpreadC : usingRestAndSpreadTS;
69+
optimize(method, 'this is %s of %d', 'a', 1);
70+
71+
var i = 0;
72+
bench.start();
73+
for (; i < n; i++)
74+
method('this is %s of %d', 'a', 1);
75+
bench.end(n);
76+
}
77+
78+
function runUsingRestAndApply(n, concat) {
79+
80+
const method = concat ? usingRestAndApplyC : usingRestAndApplyTS;
81+
optimize(method, 'this is %s of %d', 'a', 1);
82+
83+
var i = 0;
84+
bench.start();
85+
for (; i < n; i++)
86+
method('this is %s of %d', 'a', 1);
87+
bench.end(n);
88+
}
89+
90+
function runUsingArgumentsAndApply(n, concat) {
91+
92+
const method = concat ? usingArgumentsAndApplyC : usingArgumentsAndApplyTS;
93+
optimize(method, 'this is %s of %d', 'a', 1);
94+
95+
var i = 0;
96+
bench.start();
97+
for (; i < n; i++)
98+
method('this is %s of %d', 'a', 1);
99+
bench.end(n);
100+
}
101+
102+
function main(conf) {
103+
const n = +conf.n;
104+
switch (conf.method) {
105+
case 'restAndSpread':
106+
runUsingRestAndSpread(n, conf.concat);
107+
break;
108+
case 'restAndApply':
109+
runUsingRestAndApply(n, conf.concat);
110+
break;
111+
case 'argumentsAndApply':
112+
runUsingArgumentsAndApply(n, conf.concat);
113+
break;
114+
case 'restAndConcat':
115+
if (conf.concat)
116+
runUsingRestAndConcat(n);
117+
break;
118+
default:
119+
throw new Error('Unexpected method');
120+
}
121+
}
122+
123+
function createNullStream() {
124+
// Used to approximate /dev/null
125+
function NullStream() {
126+
Writable.call(this, {});
127+
}
128+
util.inherits(NullStream, Writable);
129+
NullStream.prototype._write = function(cb) {
130+
assert.strictEqual(cb.toString(), 'this is a of 1\n');
131+
};
132+
return new NullStream();
133+
}

lib/console.js

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,26 +35,29 @@ function Console(stdout, stderr) {
3535
}
3636
}
3737

38-
Console.prototype.log = function() {
39-
this._stdout.write(util.format.apply(null, arguments) + '\n');
38+
39+
// As of v8 5.0.71.32, the combination of rest param, template string
40+
// and .apply(null, args) benchmarks consistently faster than using
41+
// the spread operator when calling util.format.
42+
Console.prototype.log = function(...args) {
43+
this._stdout.write(`${util.format.apply(null, args)}\n`);
4044
};
4145

4246

4347
Console.prototype.info = Console.prototype.log;
4448

4549

46-
Console.prototype.warn = function() {
47-
this._stderr.write(util.format.apply(null, arguments) + '\n');
50+
Console.prototype.warn = function(...args) {
51+
this._stderr.write(`${util.format.apply(null, args)}\n`);
4852
};
4953

5054

5155
Console.prototype.error = Console.prototype.warn;
5256

5357

5458
Console.prototype.dir = function(object, options) {
55-
this._stdout.write(util.inspect(object, util._extend({
56-
customInspect: false
57-
}, options)) + '\n');
59+
options = Object.assign({customInspect: false}, options);
60+
this._stdout.write(`${util.inspect(object, options)}\n`);
5861
};
5962

6063

@@ -66,7 +69,7 @@ Console.prototype.time = function(label) {
6669
Console.prototype.timeEnd = function(label) {
6770
var time = this._times.get(label);
6871
if (!time) {
69-
throw new Error('No such label: ' + label);
72+
throw new Error(`No such label: ${label}`);
7073
}
7174
const duration = process.hrtime(time);
7275
const ms = duration[0] * 1000 + duration[1] / 1e6;
@@ -75,24 +78,20 @@ Console.prototype.timeEnd = function(label) {
7578
};
7679

7780

78-
Console.prototype.trace = function trace() {
81+
Console.prototype.trace = function trace(...args) {
7982
// TODO probably can to do this better with V8's debug object once that is
8083
// exposed.
8184
var err = new Error();
8285
err.name = 'Trace';
83-
err.message = util.format.apply(null, arguments);
86+
err.message = util.format.apply(null, args);
8487
Error.captureStackTrace(err, trace);
8588
this.error(err.stack);
8689
};
8790

8891

89-
Console.prototype.assert = function(expression) {
92+
Console.prototype.assert = function(expression, ...args) {
9093
if (!expression) {
91-
const argsLen = arguments.length || 1;
92-
const arr = new Array(argsLen - 1);
93-
for (var i = 1; i < argsLen; i++)
94-
arr[i - 1] = arguments[i];
95-
require('assert').ok(false, util.format.apply(null, arr));
94+
require('assert').ok(false, util.format.apply(null, args));
9695
}
9796
};
9897

0 commit comments

Comments
 (0)