为什么Function.prototype.bind变慢?

Ray*_*nos 32 javascript google-chrome v8

将此基准与chrome 16与opera 11.6 进行比较时,我们发现了这一点

  • 在chrome native中绑定几乎比模拟版本的bind慢5倍
  • 在opera native中绑定几乎是模拟绑定版本的4倍

在这种情况下,绑定的模拟版本是

var emulatebind = function (f, context) {
    return function () {
        f.apply(context, arguments);
    };
};
Run Code Online (Sandbox Code Playgroud)

有没有充分的理由说明为什么存在这样的差异,或者仅仅是v8没有足够优化的问题?

注意:emulatebind这只实现了一个子集,但实际上并不相关.如果您拥有功能齐全且经过优化的模拟绑定,则基准测试中性能差异仍然存在.

Dom*_*nic 27

基于http://jsperf.com/bind-vs-emulate/6,它添加了es5-shim版本进行比较,看起来罪魁祸首是额外的分支instanceof,并且绑定版本必须执行以测试它是否正在称为构造函数.

每次运行绑定版本时,执行的代码基本上是:

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.
}
Run Code Online (Sandbox Code Playgroud)

在V8源代码中,此检查显示(内部boundFunction)为

if (%_IsConstructCall()) {
    return %NewObjectFromBound(boundFunction);
}
Run Code Online (Sandbox Code Playgroud)

(当Google Code Search死亡时,明文链接到v8natives.js.)

令人费解的是,至少对于Chrome 16来说,es5-shim版本仍然比原生版本更快.并且其他浏览器对于es5-shim与native相比具有相当不同的结果.推测:可能%_IsConstructCall()比甚至更慢this instanceof bound,可能是由于跨越本机/ JS代码边界.也许其他浏览器可以更快地检查[[Construct]]呼叫.

  • 我个人认为这里的任何优化都是微优化,我会等到我的基准测试显示绑定函数超出网络延迟时导致我的应用程序在考虑任何类型之前变慢. (3认同)

Dom*_*nic 7

bind仅在ES5中实现全功能是不可能的.特别是规范15.3.4.5.1至15.3.4.5.3的部分无法模拟.

特别是15.3.4.5.1似乎是一种可能的性能负担:简而言之,绑定函数具有不同的 [[Call]]内部属性,因此调用它们可能会占用一个不寻常的,可能更复杂的代码路径.

绑定函数的各种其他特定的不可模拟特性(例如arguments/ caller中毒,可能是length独立于原始签名的自定义)可能会增加每次调用的开销,尽管我承认它有点不太可能.虽然看起来V8目前还没有实施中毒.

编辑这个答案是猜测,但我的另一个答案有更接近的证据.我仍然认为这是一个有效的推测,但它是一个单独的答案,所以我会保留它,只是引用你到另一个.