-
-
Save Salakar/6d7b84f7adf1f3bc62a754752a6e5d0e to your computer and use it in GitHub Desktop.
/** | |
Platform info: | |
Darwin 15.6.0 x64 | |
Node.JS 6.6.0 | |
V8 5.1.281.83 | |
Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz × 8 | |
var vs let | |
922,009,444 op/s » with var | |
19,823,034 op/s » with let | |
Suites: 1 | |
Benches: 2 | |
Elapsed: 5,900.32 ms | |
*/ | |
// benchmark below uses 'matcha' - feel free to adapt to which ever benchmark system you use. | |
console.log('\r\n'); | |
function withLet(str) { | |
let test = 'abcd' + 'efgh'; | |
test += str; | |
return test; | |
} | |
function withVar(str) { | |
var test = 'abcd' + 'efgh'; | |
test += str; | |
return test; | |
} | |
// testing | |
withVar('PING'); | |
withVar('PONG'); | |
withLet('PING'); | |
withLet('PONG'); | |
// suite | |
suite('var vs let', function () { | |
set('mintime', 2000); | |
set('concurrency', 500); | |
bench('with var', function () { | |
return withVar('PING'); | |
}); | |
bench('with let', function () { | |
return withLet('PING'); | |
}); | |
}); |
Try changing test += str
to test = test + str
, in my (slightly modified) testing changing just that brought them back to same level.
Edit: also using numbers instead of string made them perform same too.
I ran it against a "decent" benchmarking tool and the results were similar with var blowing the water out of let in the case where the +=
operator is used.
const Benchmark = require('benchmark');
const suite = new Benchmark.Suite
suite
.add('letperf1', () => {withLet('PING')})
.add('varperf1', () => {withVar('PONG')})
.on('start', function(event) {
console.log("Starting at " + (new Date));
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Complete at ' + (new Date));
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({
async: true
});
Starting at Tue Sep 27 2016 17:48:41 GMT-0700 (Pacific Daylight Time)
letperf1 x 15,704,048 ops/sec ±1.26% (88 runs sampled)
varperf1 x 82,499,281 ops/sec ±1.24% (88 runs sampled)
Complete at Tue Sep 27 2016 17:48:53 GMT-0700 (Pacific Daylight Time)
Fastest is varperf1
Switching it from test += str
to test = test + str
brings them both up to ~82M for me.
So @EvanCarroll I don't think this can be chalked up to just the benchmarking tool. There's no reason +=
in this case should be an order of magnitude slower, micro-optimization or not.
Throwing in Math.random() * Date.now()
to each function made the disparity less, but with that let is still twice as slow (switching test +=
to test = test +
made them both about the same speed.
4x vs 50x is a different result. 4x says to me that it is soundly in the realm of things that don't actually affect performance. Profile first. This should be viewed as a minor v8 bug report, not a scary warning to stop using let.
The += to test + is called a compound let assignment, that's a known issue. Again this is all microptimization. In the original example you're seeing a bad benchmark framework prioritizing the first statement executed. In the second example, you're seeing a known optimization in v8. You can read about others here,.
We're still microoptimizing.
@jakepusateri @EvanCarroll @tswaters I know a lot of you think that this kind of 'micro optimisation' is unnecessary and you're partly right, however; considering this de-opts the entire function that uses the 'let compound assignment' it therefore affects all the contained code within that function - which, in my opinion is where it no longer becomes 'micro'.
There is an issue with the matcha benchmarking lib, but apart from that the difference is still substantial. Consider the following real world example. (work in progress)
This is a redis parser I'm working on, namely the writable part of converting a cmd and it's args into the resp protocol.
'use strict';
const cmdCache = {};
const cmdCachePartial = {};
const newLine = '\r\n';
const zeroArg = `$1\r\n0\r\n`;
const oneArg = `$1\r\n1\r\n`;
const nullArg = `$4\r\nnull\r\n`;
const symbolArg = `$8\r\n[Symbol]\r\n`;
const undefArg = `$9\r\nundefined\r\n`;
const functionArg = `$10\r\n[Function]\r\n`;
const objectArg = `$15\r\n[object Object]\r\n`;
/**
* Faster for short strings less than 1kb to manually loop over
* Larger strings use Buffer.byteLength
* @param str
* @returns {*}
*/
function byteLength(str) {
var s = str.length;
if (s > 1023) return Buffer.byteLength(str, 'utf8');
var i = s - 1;
var code;
while (i--) {
code = str.charCodeAt(i);
if (code > 0x7f && code <= 0x7ff) s++;
else if (code > 0x7ff && code <= 0xffff) s += 2;
if (code >= 0xDC00 && code <= 0xDFFF) i--; // trail surrogate
}
return s;
}
/**
* Commands with no args - this increases ops/s by ~150k
* @param cmd
* @returns {*}
*/
function cmdWritable(cmd) {
return cmdCache[cmd] || (cmdCache[cmd] = `*1\r\n$${ cmd.length }\r\n${ cmd }\r\n`);
}
/**
* Caches a cmd partial (without the *argLength)
* @param cmd
* @returns {*}
*/
function cmdPartial(cmd) {
return cmdCachePartial[cmd] || (cmdCachePartial[cmd] = '\r\n$' + cmd.length + newLine + cmd + newLine);
}
/**
*
* @param arg
* @returns {string}
*/
function argWritable(arg) {
switch (typeof arg) {
case 'undefined':
return undefArg;
case 'object':
if (arg == null) return nullArg;
else return objectArg;
case 'function':
return functionArg;
case 'symbol':
return symbolArg;
case 'number':
if (arg == 0) return zeroArg;
else if (arg == 1) return oneArg;
return '$' + byteLength('' + arg) + newLine + arg + newLine;
case 'string':
case 'boolean':
default:
return '$' + byteLength('' + arg) + newLine + arg + newLine;
}
}
/**
* Convert a CMD and args to a redis writable
* @param cmd
* @param args
* @returns {string}
*/
function toWritable(cmd, args) {
if (!args || !args.length) return cmdWritable(cmd);
switch (args.length) {
case 1:
return '*2' + cmdPartial(cmd) + argWritable(args[0]);
case 2:
return '*3' + cmdPartial(cmd) + argWritable(args[0]) + argWritable(args[1]);
default:
const l = args.length;
var writable = `*${ l + 1 }${cmdPartial(cmd)}`;
for (var i = 0; i < l; i++) {
writable += argWritable(args[i]);
}
return writable;
}
}
// ------------------------------------------------------
// funcs that use let instead for benchmark example
// ------------------------------------------------------
function byteLengthLet(str) {
let s = str.length;
if (s > 1023) return Buffer.byteLength(str, 'utf8');
let i = s - 1;
let code;
while (i--) {
code = str.charCodeAt(i);
if (code > 0x7f && code <= 0x7ff) s++;
else if (code > 0x7ff && code <= 0xffff) s += 2;
if (code >= 0xDC00 && code <= 0xDFFF) i--; // trail surrogate
}
return s;
}
function argWritableLet(arg) {
switch (typeof arg) {
case 'undefined':
return undefArg;
case 'object':
if (arg == null) return nullArg;
else return objectArg;
case 'function':
return functionArg;
case 'symbol':
return symbolArg;
case 'number':
if (arg == 0) return zeroArg;
else if (arg == 1) return oneArg;
return '$' + byteLengthLet('' + arg) + newLine + arg + newLine;
case 'string':
case 'boolean':
default:
return '$' + byteLengthLet('' + arg) + newLine + arg + newLine;
}
}
function toWritableLet(cmd, args) {
if (!args || !args.length) return cmdWritable(cmd);
switch (args.length) {
case 1:
return '*2' + cmdPartial(cmd) + argWritableLet(args[0]);
case 2:
return '*3' + cmdPartial(cmd) + argWritableLet(args[0]) + argWritableLet(args[1]);
default:
const l = args.length;
let writable = `*${ l + 1 }${cmdPartial(cmd)}`;
for (let i = 0; i < l; i++) {
writable += argWritableLet(args[i]);
}
return writable;
}
}
// ------------------
// Benchmarks
// ------------------
const Benchmark = require('benchmark');
const suite = new Benchmark.Suite;
suite
.add('let', () => {toWritableLet('PING', ['foo'])})
.add('var', () => {toWritable('PONG', ['foo'])})
.on('start', function() {
console.log('\r\n');
console.log("Starting at " + (new Date));
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Complete at ' + (new Date));
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({
async: true
});
Results
Benchmarked using 'benchmark' rather than match.
Starting at Wed Sep 28 2016 12:40:54 GMT+0100 (BST)
let x 4,358,340 ops/sec ±0.64% (91 runs sampled)
var x 11,413,777 ops/sec ±0.77% (87 runs sampled)
Complete at Wed Sep 28 2016 12:41:06 GMT+0100 (BST)
Fastest is var
See my comment down below.