Skip to content

Commit

Permalink
console: refactor to use rest params and template strings
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
jasnell committed Apr 26, 2016
1 parent c0abecd commit 77e2efe
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 16 deletions.
133 changes: 133 additions & 0 deletions benchmark/misc/console.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
'use strict';

const common = require('../common.js');
const assert = require('assert');
const Writable = require('stream').Writable;
const util = require('util');
const v8 = require('v8');

v8.setFlagsFromString('--allow_natives_syntax');

var bench = common.createBenchmark(main, {
method: ['restAndSpread',
'argumentsAndApply',
'restAndApply',
'restAndConcat'],
concat: [1, 0],
n: [1000000]
});

const nullStream = createNullStream();

function usingRestAndConcat(...args) {
nullStream.write('this is ' + args[0] + ' of ' + args[1] + '\n');
}

function usingRestAndSpreadTS(...args) {
nullStream.write(`${util.format(...args)}\n`);
}

function usingRestAndApplyTS(...args) {
nullStream.write(`${util.format.apply(null, args)}\n`);
}

function usingArgumentsAndApplyTS() {
nullStream.write(`${util.format.apply(null, arguments)}\n`);
}

function usingRestAndSpreadC(...args) {
nullStream.write(util.format(...args) + '\n');
}

function usingRestAndApplyC(...args) {
nullStream.write(util.format.apply(null, args) + '\n');
}

function usingArgumentsAndApplyC() {
nullStream.write(util.format.apply(null, arguments) + '\n');
}

function optimize(method, ...args) {
method(...args);
eval(`%OptimizeFunctionOnNextCall(${method.name})`);
method(...args);
}

function runUsingRestAndConcat(n) {
optimize(usingRestAndConcat, 'a', 1);

var i = 0;
bench.start();
for (; i < n; i++)
usingRestAndConcat('a', 1);
bench.end(n);
}

function runUsingRestAndSpread(n, concat) {

const method = concat ? usingRestAndSpreadC : usingRestAndSpreadTS;
optimize(method, 'this is %s of %d', 'a', 1);

var i = 0;
bench.start();
for (; i < n; i++)
method('this is %s of %d', 'a', 1);
bench.end(n);
}

function runUsingRestAndApply(n, concat) {

const method = concat ? usingRestAndApplyC : usingRestAndApplyTS;
optimize(method, 'this is %s of %d', 'a', 1);

var i = 0;
bench.start();
for (; i < n; i++)
method('this is %s of %d', 'a', 1);
bench.end(n);
}

function runUsingArgumentsAndApply(n, concat) {

const method = concat ? usingArgumentsAndApplyC : usingArgumentsAndApplyTS;
optimize(method, 'this is %s of %d', 'a', 1);

var i = 0;
bench.start();
for (; i < n; i++)
method('this is %s of %d', 'a', 1);
bench.end(n);
}

function main(conf) {
const n = +conf.n;
switch (conf.method) {
case 'restAndSpread':
runUsingRestAndSpread(n, conf.concat);
break;
case 'restAndApply':
runUsingRestAndApply(n, conf.concat);
break;
case 'argumentsAndApply':
runUsingArgumentsAndApply(n, conf.concat);
break;
case 'restAndConcat':
if (conf.concat)
runUsingRestAndConcat(n);
break;
default:
throw new Error('Unexpected method');
}
}

function createNullStream() {
// Used to approximate /dev/null
function NullStream() {
Writable.call(this, {});
}
util.inherits(NullStream, Writable);
NullStream.prototype._write = function(cb) {
assert.strictEqual(cb.toString(), 'this is a of 1\n');
};
return new NullStream();
}
31 changes: 15 additions & 16 deletions lib/console.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,29 @@ function Console(stdout, stderr) {
}
}

Console.prototype.log = function() {
this._stdout.write(util.format.apply(null, arguments) + '\n');

// As of v8 5.0.71.32, the combination of rest param, template string
// and .apply(null, args) benchmarks consistently faster than using
// the spread operator when calling util.format.
Console.prototype.log = function(...args) {
this._stdout.write(`${util.format.apply(null, args)}\n`);
};


Console.prototype.info = Console.prototype.log;


Console.prototype.warn = function() {
this._stderr.write(util.format.apply(null, arguments) + '\n');
Console.prototype.warn = function(...args) {
this._stderr.write(`${util.format.apply(null, args)}\n`);
};


Console.prototype.error = Console.prototype.warn;


Console.prototype.dir = function(object, options) {
this._stdout.write(util.inspect(object, util._extend({
customInspect: false
}, options)) + '\n');
options = Object.assign({customInspect: false}, options);
this._stdout.write(`${util.inspect(object, options)}\n`);
};


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


Console.prototype.trace = function trace() {
Console.prototype.trace = function trace(...args) {
// TODO probably can to do this better with V8's debug object once that is
// exposed.
var err = new Error();
err.name = 'Trace';
err.message = util.format.apply(null, arguments);
err.message = util.format.apply(null, args);
Error.captureStackTrace(err, trace);
this.error(err.stack);
};


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

Expand Down

0 comments on commit 77e2efe

Please sign in to comment.