Why is .bind so slow?
you might consider ticketing performance bugs against the various implementations.
If you can manage it, most effective would be to get .bind (or any other operation you want to be faster) into some widely quoted benchmark suite.
I think we all know that's extremely slow and since ever. I always wondered the reason too ... in jsperf there are tons of tests about this, here yet another one just quickly created to compare the gap: jsperf.com/bind-is-slow
in Chrome, bind(context) without even arguments is 87% slower than a closure with call
On Jul 12, 2013, at 10:09 AM, "Mark S. Miller" <erights at google.com> wrote:
If you can manage it, most effective would be to get .bind (or any other operation you want to be faster) into some widely quoted benchmark suite.
In WebKit at least, we have a thing called JSRegress which is meant to be a dumping ground for benchmarks for interesting corners of the language. Microbenchmarks are welcome; the goal is to just expand coverage of things that we measure timings of. It's not widely quoted, and it doesn't get the same priority as "macrobenchmark" suites, but we do care about it and we do optimize for it. For example, all of our optimization a for typeof, switch, and 'in' arose from JSRegress tests, since none of those things were hot enough in any of the "major" benchmarks.
We don't have coverage for bind, and it would be great to fix that.
If you have a standalone program that uses bind, you can either post a patch on bugs.webkit.org that adds it to JSRegress (I.e. LayoutTests/fast/js/regress/script-tests), or if you're lazy, you can just email the program to me and I can do the footwork. The less micro-benchmarky the better, but we'll take what we can get. If you have multiple such benchmarks for bind then that's even better still.
TL;DR getting a benchmark to have visibility among implementors is as easy as telling them about it.
Allen Wirfs-Brock wrote:
you might consider ticketing performance bugs against the various implementations.
Right, and at most summarize with links to those issues for es-discuss. This is not a language issue, rather a quality of implementation one.
Just to clarify, JSRegress is not a "benchmark" in the sunspider/kraken/etc sense, as the tests tend far more towards microbenchmarks than full "real" programme tests. As the name suggests its main purpose is to help us make sure we're not regressing core language primitives.
I've had some back and forth with v8 devs about this since it affects my compiler. I believe they already have open issues about it but I don't know the bug #s.
In general, the problem seems to be that Function.bind creates functions that have different type information from normal functions you wrote in pure JS; they're 'special' native functions in the same fashion as say, a DOM API:
> document.createElement
function createElement() { [native code] }
> function f () {}
> f
function f() {}
> f.bind(null)
function () { [native code] }
This is important because v8 tries to gather information about callees at various call sites. Having a mix of these special and non-special functions means that the JIT is not able to make safe optimizations based on all the callers being the same type.
IIRC there are also some other problems specific to v8, like it only being able to optimize Function.apply and Function.call if the .apply/.call methods are the implementations used for pure-JS functions (so bind breaks those too).
I can't comment on why it's slow in SpiderMonkey (I've never asked... I should file a bug) but it is indeed the case that f.bind(null) produces a 'native code' function in SpiderMonkey, so I expect some of the same optimization consequences apply.
I also expect that it is much harder for v8 and spidermonkey to inline a function that contains native code, if not entirely impossible.
All of these problems, as I understand them, are completely fixable. It might be as simple as making bind return a pure-JS function instead of a native function. This is supported by the fact that a pure-JS polyfill for .bind is usually faster in my tests. In general VM authors are much more helpful when shown real world applications affected by these issues, based on my experience. They tend to ignore jsperf microbenchmarks, etc.
I don't know that this could be addressed at all from a specification perspective. The only thing I can think of would be specifying that the result of Function.bind should somehow be indistinguishable from a hand-written JS function (no 'native code' in tostring, etc) but I don't think that sort of spec requirement would actually prevent any of these performance traps.
Hope this helps,
In the future wouldn't using a Function Proxy be potentially much faster?
Thanks, kg! Your message represents the kind of discussion/information I was hoping for. If your hunch as to the reason is correct, it would seem an easy target for optimization. Partially and efficiently emulating arrow functions in ES6 transpilers should be a strong argument in favor, though not the only one (eg bind keeps coming up as a recommendation when using class methods as callback parameters, etc.).
For those interested, I've put my (micro) bench in a gist:
https://gist.github.com/clausreinke/5987876
(note in particular the performance difference between
.bind and an .apply-based polyfill; other engines do worse)
I used es-discuss for this thread because:
-
all engines are slow on .bind, so it is likely a general issue
-
all engines are slow on .bind, so recommending .bind as freely as I (and several people on this list) used to do does not seem realistic; that puts a serious dent in the usability of this part of the spec
-
even if that issue may turn out not to be spec-related, this is the only list I know of where I can reach all engine developers and es language gurus at once.
If this kind of es implementation/performance discussion is not welcome here, a dedicated cross-engine list for such topics would be nice. Would only work if all engines had developers listening in.
As long as there isn't enough traffic to warrant a dedicated list, I (as one of the list owners there) welcome such threads on js-tools
groups.google.com/group/js-tools/about
(on the basis that engines are our most fundamental js tools;-)
Please let me know where to raise such cross-engine threads in future. Claus
On Jul 12, 2013, at 1:55 PM, Matthew Robb wrote:
In the future wouldn't using a Function Proxy be potentially much faster?
It seems highly unlikely that any use of Proxy will be faster than a rough equivalent using an ordinary object.
I expect proxies to be much harder for implementations to optimized than ordinary objects.
Looking at it another way, if implementation haven't found it straightforward to optimize ES5 bound functions why would you expect that would have an easier time with Proxys?
If a function proxy is just forwarding an operation through an intermediary (the proxy and it's call trap) to another function it sounds very similar to a regularly wrapped/bound js function. So what I am saying is if browsers implemented bind using a proxy instead of the special native functions it would be theoretically faster.
just to add some extra info to this discussion, lo-dash does some crazy thing to optimize at its best bound functions.
/\bthis\b/.test(Function.prototype.toString.call(callback))
or something
similar to check if the function needs to use call/apply at all, together
with the number of arguments, and some other analysis to optimize all
further calls to the bound function.
While I believe that's a bit extreme, I have to admit performance gain is huge so maybe some trick used in lo-dash could be used internally too ? Like different specialized bound version? Most common use case is still a method bound to the object itself in order to don't loose the context on some event call.
my 2 cents
one more thing ... I believe this will impact arrow function too since is basically bound callbacks all over the place (or at least this is how I believe it will be transpiled)
On 07/12/2013 04:59 PM, Andrea Giammarchi wrote:
one more thing ... I believe this will impact arrow function too since is basically bound callbacks all over the place (or at least this is how I believe it will be transpiled)
Sadly, based on the arrow-function patches I've reviewed in SpiderMonkey, I don't believe this will be necessarily true. Arrow functions and bind-bound functions are two rather different beasts. Bind-bound functions are potentially constructible; arrow functions never are. |arguments.callee| inside a function that's been bound doesn't refer to the bound function -- it refers to the lexical entity. (That is, |function f() { return arguments.callee; } f.bind(null)()| is |f|, not |b|.) |arguments.callee| inside an arrow function -- at least, so long as arrow functions aren't automatically strict, which decision I would revisit -- refers to the arrow function. I expect there are other differences I'm not yet aware of, that would affect having a common implementation of the two concepts.
It seems like a pretty bad idea to me for arrow functions to not be substantially semantically similar to bind-bound functions, but they are as it stands now. I wish I had the time to sit down and think through a solid unification of the two concepts.
On 07/12/2013 02:54 PM, Allen Wirfs-Brock wrote:
Looking at it another way, if implementation haven't found it straightforward to optimize ES5 bound functions why would you expect that would have an easier time with Proxys?
I'm pretty sure no implementation has seriously tried to optimize bound functions, and that that's the major reason for any slowness. I don't think there's anything fundamentally difficult about optimizing bound functions. It's just never been a priority for engines, because it doesn't show up in benchmarks and because the perf bugs for it are less pressing than other perf work is, as bound functions remain underused on the web. Chicken and egg? Sure.
In the meantime, while I wouldn't currently question (as a purely pragmatic choice) avoiding bind in highly intensive code where every bit of perf matters, I do think bound functions are fast enough for the vast majority of cases. The overhead of calling a bound function will rarely be the difference between adequate and inadequate performance.
Le 13/07/2013 20:55, Jeff Walden a écrit :
On 07/12/2013 04:59 PM, Andrea Giammarchi wrote:
one more thing ... I believe this will impact arrow function too since is basically bound callbacks all over the place (or at least this is how I believe it will be transpiled) Sadly, based on the arrow-function patches I've reviewed in SpiderMonkey, I don't believe this will be necessarily true. Arrow functions and bind-bound functions are two rather different beasts. Bind-bound functions are potentially constructible; arrow functions never are. |arguments.callee| inside a function that's been bound doesn't refer to the bound function -- it refers to the lexical entity.
One of these things I didn't know (and didn't want to know) about the language...
(That is, |function f() { return arguments.callee; } f.bind(null)()| is |f|, not |b|.) |arguments.callee| inside an arrow function -- at least, so long as arrow functions aren't automatically strict, which decision I would revisit -- refers to the arrow function.
arguments.callee is poisoned is strict mode, so the difference you're referring only affects non-strict. Should we care? I know I don't. In general, it should be expected from developers to be in strict mode. They also shouldn't be using the "arguments" object...
"which decision I would revisit" => I disagree. Implicit strictness at the function level would surprise
people a lot. Maybe break their code when turning a function expression to an arrow function. Implicit strictness at the module or class level will already surprise people a lot.
I expect there are other differences I'm not yet aware of, that would affect having a common implementation of the two concepts.
Even in strict mode? If there are no differences in strict mode, I don't think we should care.
It seems like a pretty bad idea to me for arrow functions to not be substantially semantically similar to bind-bound functions, but they are as it stands now. I wish I had the time to sit down and think through a solid unification of the two concepts.
Hopefully by simplifying how bound functions work?
Arrow functions, whether strict or non-strict, are not supposed to have their own |arguments|
On Jul 13, 2013, at 12:39 PM, Mark S. Miller wrote:
Arrow functions, whether strict or non-strict, are not supposed to have their own |arguments|
Correct. Implementors should be aware that in the current ES6 draft this is stated in a margin note but not in the actual algorithm for function declaration instantiations (10.5.3) step 11.
On 07/13/2013 12:56 PM, Allen Wirfs-Brock wrote:
On Jul 13, 2013, at 12:39 PM, Mark S. Miller wrote:
Arrow functions, whether strict or non-strict, are not supposed to have their own |arguments|
Correct. Implementors should be aware that in the current ES6 draft this is stated in a margin note but not in the actual algorithm for function declaration instantiations (10.5.3) step 11.
Good to know, thanks. Lack of arguments is sane; last I'd seen in bug commentary was that this wasn't the case.
Sorry Jeff, but this is actually not the case. The performance consequences from using .bind() can be quite severe because it pollutes type information and disables fast paths. Not 'your entire application runs in the interpreter' slow, but surprisingly slow. It can even slow down code paths where .bind()/bound functions are not being used because of how inline caches and such tend to work in modern engines.
The problem here isn't that 'calling a bound function is slow'; it's a more general problem that the introduction of bound functions into an existing application can make the application get slower in places where the bound functions aren't being used.
I do agree with the general principle that it is not likely to be the biggest performance problem, but it's surprisingly harmful. On a related note, it would certainly be unwelcome if arrow functions had the same downsides.
this makes more sense than callee
since .bind()
functions are slow even
in "use strict"
scenarios.
However, I was not aware about the fact arrow functions have no arguments access at all ... between "cool" and "limiting" if you ask me but if performances are better then it's OK.
Also OK would be to have a way to flag a function or a bound one "static"
so that no arguments
or caller
will ever be available 'cause if this is
the performance problem developers would love to have a better way to
improve there.
In the DOM world, as example, all bound functions accepts one single argument, the event object, while in node.js world basically all emitted functions accept 2 arguments and never/rarely more than two.
As somebody said already in another thread, me, as developer, would love to help the JS engine to go faster where needed!
not a part, I am imaging already a scenario where everyone will start
avoiding .bind()
in order to use arrow functions because these are faster
... something like:
obj.method = function () {
return (e) => {
// rest here
};
};
node.addEventListener('evt', obj.method());
... I don't really like the approach ... can anyone think about a way to have "arrowified" functions at definition time? Will classes allow arrow functions magic inline ? nothing to bind anymore in this case ... different from the JS I know but that might work pretty well for performance ... although the prototype would be even more messed up ...
function()->{},
Identical in every way to fat-arrow function except not lexically bound, has access to arguments but not callee/caller, and can be call/applied. No construct, supports no curlys, implicit return :D
In the future wouldn't using a Function Proxy be potentially much faster?
That will most certainly be the slowest option of all, at least in the foreseeable future, and not totally unlikely, forever.
The TypeScript project tries to emulate arrow functions through the "_this = this" pattern and keeps running into corner cases where a semi-naïve renaming is not sufficient.
I have been trying to suggest using .bind to emulate arrow functions instead, but the counter-arguments are (a) .bind might not be available (supporting pre-ES5 targets) and (b) .bind is slow.
The polyfill isn't the problem, but I'm a bit shocked every time someone reminds me of the performance hit for using .bind. Given that a bound function has strictly more info than an unbound one, I wouldn't expect that (I expected a bound function to be roughly the same as an unbound function that does not use "this"). Unless there is no special casing for the just-add-this case, and .bind is always treated as a non-standard (meta-level) call.
While playing with test-code, I also found that v8 does a lot better than other engines when using an .apply-based .bind emulation.
Can anyone explain what is going on with .bind, .apply and the performance hits?
The TypeScript issue is typescript.codeplex.com/workitem/1322 . My test code (*) is attached there as "bind-for-arrows.html".
(*) I also tried to make a jsperf test case, but the way jsperf runs the loop seems to prevent the optimization that makes v8 look good for the .apply-based bind.