【问题标题】:Why is bind slower than a closure?为什么绑定比闭包慢?
【发布时间】:2013-07-12 09:02:43
【问题描述】:

之前的发帖人问Function.bind vs Closure in Javascript : how to choose?

并部分收到了这个答案,这似乎表明绑定应该比闭包更快:

范围遍历意味着,当你到达获取一个值时 (变量,对象)存在于不同的范围内,因此 增加了额外的开销(代码执行速度变慢)。

使用绑定,您正在调用具有现有范围的函数,因此 范围遍历不会发生。

两个 jsperfs 表明 bind 实际上比 closure 慢得多。

这是作为对上述内容的评论发布的

而且,我决定写my own jsperf

那么为什么绑定这么慢(70+% 在铬上)?

既然它不是更快,而且闭包可以达到同样的目的,应该避免绑定吗?

【问题讨论】:

  • “应该避免绑定” --- 除非你在一页上做数千次 - 你不应该关心它。
  • 从小块组装异步复杂任务可能需要在 nodejs 中看起来完全一样的东西,因为回调需要以某种方式对齐。
  • 我猜这是因为浏览器并没有在优化它上付出太多努力。请参阅 Mozilla 的代码 (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…) 以手动实现它。浏览器很有可能只是在内部这样做,这比快速关闭要多得多。
  • 间接函数调用 (apply/call/bind) 通常比直接调用慢得多。
  • @zerkms 谁能说一个人不这样做数千次?由于它提供的功能,我认为您可能会对这种情况的普遍性感到惊讶。

标签: javascript node.js performance v8


【解决方案1】:

我只是想在这里给出一点观点:

请注意,虽然 bind()ing 很慢,但 调用 函数一旦绑定就不会了!

我在 Linux 上的 Firefox 76.0 中的测试代码:

//Set it up.
q = function(r, s) {

};
r = {};
s = {};
a = [];
for (let n = 0; n < 1000000; ++n) {
  //Tried all 3 of these.
  //a.push(q);
  //a.push(q.bind(r));
  a.push(q.bind(r, s));
}

//Performance-testing.
s = performance.now();
for (let x of a) {
  x();
}
e = performance.now();
document.body.innerHTML = (e - s);

所以虽然.bind()ing 确实比不绑定慢大约 2 倍(我也测试过),但上面的代码对于所有 3 种情况(绑定 0、1 或 2变量)。


就我个人而言,我不在乎.bind()ing 在我当前的用例中是否很慢,我关心的是一旦这些变量已经绑定到函数后被调用的代码的性能。

【讨论】:

    【解决方案2】:

    Chrome 59 更新:正如我在下面的答案中预测的那样,使用新的优化编译器绑定不再慢。这是包含详细信息的代码:https://codereview.chromium.org/2916063002/

    大多数时候都没有关系。

    除非您正在创建一个应用程序,其中.bind 是我不会打扰的瓶颈。在大多数情况下,可读性比纯粹的性能更重要。我认为使用原生 .bind 通常会提供更易读和可维护的代码——这是一大优势。

    但是,是的,当它重要时 - .bind 更慢

    是的,.bind 比闭包要慢得多——至少在 Chrome 中,至少在当前在 v8 中实现的方式。由于性能问题,我个人不得不切换到 Node.JS(更一般地说,在性能密集型情况下,闭包有点慢)。

    为什么?因为.bind 算法比用另一个函数包装一个函数并使用.call.apply 复杂得多。 (有趣的是,它还返回一个 toString 设置为 [native function] 的函数)。

    从规范的角度和实现的角度来看,有两种方法。让我们观察两者。

    首先,让我们look at the bind algorithm defined in the specification

    1. 让 Target 成为 this 值。
    2. 如果 IsCallable(Target) 为 false,则抛出 TypeError 异常。
    3. 让 A 成为一个新的(可能为空)内部列表,其中包含在 thisArg(arg1、arg2 等)之后提供的所有参数值,按顺序排列。

    ...

    (21. 调用 F 的 [[DefineOwnProperty]] 内部方法,带参数 "arguments", PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [ [可配置]]: false}, 和 false。

    (22. 返回 F.

    看起来很复杂,不仅仅是一个包装。

    其次,我们来看看how it's implemented in Chrome

    让我们检查一下 v8(chrome JavaScript 引擎)源代码中的FunctionBind

    function FunctionBind(this_arg) { // Length is 1.
      if (!IS_SPEC_FUNCTION(this)) {
        throw new $TypeError('Bind must be called on a function');
      }
      var boundFunction = function () {
        // Poison .arguments and .caller, but is otherwise not detectable.
        "use strict";
        // This function must not use any object literals (Object, Array, RegExp),
        // since the literals-array is being used to store the bound data.
        if (%_IsConstructCall()) {
          return %NewObjectFromBound(boundFunction);
        }
        var bindings = %BoundFunctionGetBindings(boundFunction);
    
        var argc = %_ArgumentsLength();
        if (argc == 0) {
          return %Apply(bindings[0], bindings[1], bindings, 2, bindings.length - 2);
        }
        if (bindings.length === 2) {
          return %Apply(bindings[0], bindings[1], arguments, 0, argc);
        }
        var bound_argc = bindings.length - 2;
        var argv = new InternalArray(bound_argc + argc);
        for (var i = 0; i < bound_argc; i++) {
          argv[i] = bindings[i + 2];
        }
        for (var j = 0; j < argc; j++) {
          argv[i++] = %_Arguments(j);
        }
        return %Apply(bindings[0], bindings[1], argv, 0, bound_argc + argc);
      };
    
      %FunctionRemovePrototype(boundFunction);
      var new_length = 0;
      if (%_ClassOf(this) == "Function") {
        // Function or FunctionProxy.
        var old_length = this.length;
        // FunctionProxies might provide a non-UInt32 value. If so, ignore it.
        if ((typeof old_length === "number") &&
            ((old_length >>> 0) === old_length)) {
          var argc = %_ArgumentsLength();
          if (argc > 0) argc--;  // Don't count the thisArg as parameter.
          new_length = old_length - argc;
          if (new_length < 0) new_length = 0;
        }
      }
      // This runtime function finds any remaining arguments on the stack,
      // so we don't pass the arguments object.
      var result = %FunctionBindArguments(boundFunction, this,
                                          this_arg, new_length);
    
      // We already have caller and arguments properties on functions,
      // which are non-configurable. It therefore makes no sence to
      // try to redefine these as defined by the spec. The spec says
      // that bind should make these throw a TypeError if get or set
      // is called and make them non-enumerable and non-configurable.
      // To be consistent with our normal functions we leave this as it is.
      // TODO(lrn): Do set these to be thrower.
      return result;
    

    我们可以在实现中看到一堆昂贵的东西。即%_IsConstructCall()。这当然需要遵守规范 - 但在许多情况下它也比简单的包装慢。


    另一方面,调用.bind 也略有不同,规范说明“使用 Function.prototype.bind 创建的函数对象没有原型属性或 [[Code]]、[[FormalParameters]] 和[[Scope]] 内部属性"

    【讨论】:

    • 如果 f = g.bind(stuff); f() 应该比 g(stuff) 慢吗?我可以很快找到这一点,我只是好奇每次我们调用一个函数时是否会发生同样的事情,无论是什么实例化了该函数,或者它是否取决于该函数的来源。
    • @Paul 对我的回答持怀疑态度。所有这些都可能在未来版本的 Chrome (/V8) 中得到优化。我很少发现自己在浏览器中避免使用.bind,在大多数情况下,可读和可理解的代码更为重要。至于绑定函数的速度 - Yes, bound functions will stay slower at the moment ,尤其是在部分中未使用 this 值时。您可以从基准、规范和/或独立的实现中看到这一点(benchmark)
    • 我想知道是否:1) 自 2013 年以来发生了任何变化(现在已经两年了)2) 由于箭头函数具有 this 词法约束 - 箭头函数的设计速度较慢。
    • @KubaWyrostek 1) 不,2) 不,因为 bind 在设计上并不慢,它只是没有实现那么快。箭头函数还没有在 V8 中登陆(他们登陆然后被还原)我们会看到。
    • 未来对已经应用“绑定”的函数的调用会更慢吗? IE。 a: function(){}.bind(this)... 未来对 a() 的调用是否会比我一开始从未绑定时慢?
    猜你喜欢
    • 2011-03-23
    • 1970-01-01
    • 2020-03-18
    • 1970-01-01
    • 2016-02-14
    • 2019-12-18
    • 2015-10-04
    • 2012-09-16
    • 2016-10-06
    相关资源
    最近更新 更多