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

bfa*_*tto 10 javascript performance v8

我对这个简单的jsperf测试的结果感到惊讶:

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

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

Vya*_*rov 25

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

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

  • 当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 */
    }
    
    Run Code Online (Sandbox Code Playgroud)

    每次运行称为样本.

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

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

    O{} -f-> O{ f: <closure> }
    
    Run Code Online (Sandbox Code Playgroud)

    隐藏的类o本质上告诉V8 o有一个被给定闭包实现方法f.这允许V8的优化编译器f在上面的基准测试中内联,这实质上使基准测试循环变空.

  2. 隐藏类o2属于过渡树Object.首次通知setup被多次调用,因此如果V8尝试将相同的优化应用于f方法,它将到达一个不可能的转换树:

    Object{} -f-> Object{ f: <closure> }
             -f-> Object{ f: <other closure> }
    
    Run Code Online (Sandbox Code Playgroud)

    事实上,V8甚至没有尝试过.V8实现者预见到了这种情况,V8只是做f了一个正常的属性:

    Object{} -f-> Object{ f: <property at offset 8> }
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    O{} -f-> O{ f: <closure> }
        -f-> O{ f: <other closure> }
Run Code Online (Sandbox Code Playgroud)

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

    O{} -f-> O{ f: <property at offset 8> }
Run Code Online (Sandbox Code Playgroud)

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

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

您可以在我的博文中阅读有关类似问题的更多信息.