为什么在V8上调用对象文字上的方法会变慢?

时间:2014-02-27 02:00:33

标签: javascript performance v8

我对这个simple jsperf test

的结果感到惊讶
Benchmark.prototype.setup = function() {
  var O = function() {
      this.f = function(){};
  }
  var o = new O();
  var o2 = {
      f : function(){}
  };
};

// Test case #1
o.f();  // ~721M ops/s

// Test case #2
o2.f(); // ~135M ops/s

我希望两者都执行相同的操作(实际上Firefox的性能类似)。 V8必须优化案例#1,但是什么?

2 个答案:

答案 0 :(得分:25)

关于V8和jsPerf的第一个基础知识:

  • V8使用一种名为隐藏类的技术。每个隐藏类描述某个对象形状,例如对象在偏移x 对象具有方法16 时具有属性f,并且这些隐藏类通过过渡连接在一起因为对象被突变形成过渡树(严格来说是dags)。并非所有隐藏类都驻留在同一个转换树中;相反,每个构造函数都会生成一个新的过渡树。看看these slides,了解隐藏课程背后的基本思想。

  • 当jsPerf执行以下操作以运行测试时:给定setupbody 多次会生成并运行一个类似于此的函数:

    function measure() {
      /* setup */
      var start = Date.now();
      for (var i = 0; i < N; i++) {
        /* body */
      }
      var end = Date.now();
    
      /* N / (start - end) determines ops / ms reported */
    }
    

    每次运行都称为样本

现在让我们看一下基准测试中的过渡树。

  1. o的隐藏类属于转换树,在构造函数O中具有根。请注意,每次构造函数都会被调用一次。这允许V8在内存中构建以下转换树:

    O{} -f-> O{ f: <closure> }
    

    隐藏的o类基本上告诉V8 o有一个名为f方法,由给定的闭包实现。这允许V8的优化编译器在您的基准测试中内联f,这实际上使得基准测试循环为空。

  2. 隐藏的o2类属于Object的过渡树。首先请注意setup被多次调用,因此如果V8尝试将相同的优化应用于促销f的方法,它将会到达不可能的转换树:

    Object{} -f-> Object{ f: <closure> }
             -f-> Object{ f: <other closure> }
    

    事实上,V8甚至没有尝试过。 V8实现者预见到了这种情况,V8只使f成为正常属性:

    Object{} -f-> Object{ f: <property at offset 8> }
    

    因此,要调用o2.f(),首先需要加载它,这也会损害内联。

  3. 在这里你应该意识到一件重要的事情:如果你两次调用O构造函数,那么V8将到达V8避免命中Object的同一个不可能的转换树:

        O{} -f-> O{ f: <closure> }
            -f-> O{ f: <other closure> }
    

    你不能拥有这样的结构。在这种情况下,V8即时将f转换为字段而不是使其成为方法并重写转换树:

        O{} -f-> O{ f: <property at offset 8> }
    

    http://jsperf.com/function-call-on-js-objects/3中看到此效果,我在创建new O()之前添加了一个o。您会注意到使用new构造的对象文字和对象的性能是相同的。

    此处的另一个细节是,如果在全局范围中声明了文字,V8也会尝试将f转换为文字的方法。针对V8,请参阅http://jsperf.com/function-call-on-js-objects/5Issue 2246。这里的推理很简单:全局范围内的文字只被评估一次,因此这种促销很可能会成功,并且如果多次评估文字,那么方法之间就不会发生冲突。

    您可以在my blog post中了解有关类似问题的更多信息。

答案 1 :(得分:2)

V8对已知原型进行了优化。换句话说,通过new对对象的使用和创建进行了优化。

您可以编写更多类似的测试,这将始终是结论。

在第二种情况下,你会使发动机失明。它不知道为什么,o2是否或何时会有属性。