When comparing this benchmark with chrome 16 vs opera 11.6 we find that
- in chrome native bind is almost 5 times slower then an emulated version of bind
- in opera native bind is almost 4 times faster then an emulated version of bind
Where an emulated version of bind in this case is
var emulatebind = function (f, context) {
return function () {
f.apply(context, arguments);
};
};
Are there good reasons why there is such a difference or is this just a matter of v8 not optimizing enough?
Note: that emulatebind
only implements a subset but that isn't really relevant. If you have a fully featured and optimised emulated bind the performance difference in the benchmark still exists.
Based on http://jsperf.com/bind-vs-emulate/6, which adds the es5-shim version for comparison, it looks like the culprit is the extra branch and instanceof
that the bound version has to perform to test if it's being called as a constructor.
Each time the bound version is run, the code that gets executed is essentially:
if (this instanceof bound) {
// Never reached, but the `instanceof` check and branch presumably has a cost
} else {
return target.apply(
that,
args.concat(slice.call(arguments))
);
// args is [] in your case.
// So the cost is:
// * Converting (empty) Arguments object to (empty) array.
// * Concating two empty arrays.
}
In the V8 source code, this check appears (inside boundFunction
) as
if (%_IsConstructCall()) {
return %NewObjectFromBound(boundFunction);
}
(Plaintext link to v8natives.js for when Google Code Search dies.)
It is a bit puzzling that, for Chrome 16 at least, the es5-shim version is still faster than the native version. And that other browsers have rather varying results for es5-shim vs. native. Speculation: maybe %_IsConstructCall()
is even slower than this instanceof bound
, perhaps due to crossing native/JS code boundaries. And perhaps other browsers have a much faster way of checking for a [[Construct]]
call.
It's impossible to implement a fully-featured bind
in ES5 alone. In particular sections 15.3.4.5.1 through 15.3.4.5.3 of the spec cannot be emulated.
15.3.4.5.1, in particular, seems like a possible performance burden: in short bound functions have different [[Call]]
internal properties, so calling them is likely to take an unusual and possibly more complicated code path.
Various other specific un-emulatable features of a bound function (such as arguments
/caller
poisoning, and possibly the custom length
independent of original signature) could possibly add overhead to each call, although I admit it's a bit unlikely. Although it looks like V8 doesn't even implement the poisoning at the moment.
EDIT this answer is speculation, but my other answer has something more approaching evidence. I still think this is valid speculation, but it's a separate answer, so I'll leave it as such and just refer you to the other one.
The V8 source code for bind is implemented in JS.
The OP doesn't emulate bind
because it doesn't curry arguments the way bind
does. Here is a fully featured bind
:
var emulatebind = function (f, context) {
var curriedArgs = Array.prototype.slice.call(arguments, 2);
return function () {
var allArgs = curriedArgs.slice(0);
for (var i = 0, n = arguments.length; i < n; ++i) {
allArgs.push(arguments[i]);
}
return f.apply(context, allArgs);
};
};
Obviously, a quick optimization would be to do
return f.apply(context, arguments);
instead if curriedArgs.length == 0
, because otherwise you have two unnecessary array creations, and an unnecessary copy, but perhaps the native version is really implemented in JS and does not do that optimization.
Caveat: This fully featured bind
does not correctly handle some corner cases around this
argument coercion in strict mode. That might be another source of overhead.