为什么新的慢?

时间:2011-06-22 13:28:36

标签: javascript v8 ecmascript-5 javascript-engine

基准:

JsPerf

不变量:

var f = function() { };

var g = function() { return this; }

测试:

以下按预期速度的顺序

  • new f;
  • g.call(Object.create(Object.prototype));
  • new (function() { })
  • (function() { return this; }).call(Object.create(Object.prototype));

实际速度:

  1. new f;
  2. g.call(Object.create(Object.prototype));
  3. (function() { return this; }).call(Object.create(Object.prototype));
  4. new (function() { })
  5. 问题:

    1. 当您将fg交换为内联匿名函数时。为什么new(测试4.)测试速度较慢?
    2. 更新

      newf内联时,g会导致{{1}}更慢的原因。

      我对ES5规范的引用或对JagerMonkey或V8源代码的引用感兴趣。 (请随意链接JSC和Carakan源代码。哦,如果他们愿意,IE团队可以泄漏Chakra源。)

      如果你链接任何JS引擎源,请解释它。

5 个答案:

答案 0 :(得分:15)

#4和所有其他情况之间的主要区别在于,第一次使用闭包作为构造函数总是非常昂贵。

  1. 它始终在V8运行时(不在生成的代码中)处理,并且编译的JS代码和C ++运行时之间的转换非常昂贵。后续分配通常在生成的代码中处理。您可以查看Generate_JSConstructStubHelper中的builtins-ia32.cc,并注意当关闭没有初始地图时,Runtime_NewObject会落到{{1}}。 (见http://code.google.com/p/v8/source/browse/trunk/src/ia32/builtins-ia32.cc#138

  2. 当闭包第一次用作构造函数时,V8必须创建一个新的地图(也称为隐藏类)并将其指定为该闭包的初始地图。见http://code.google.com/p/v8/source/browse/trunk/src/heap.cc#3266。这里重要的是地图分配在一个单独的内存空间中。快速部分清除收集器无法清除此空间。当地图空间溢出时,V8必须执行相对昂贵的完整标记扫描 GC。

  3. 第一次使用闭包作为构造函数时,还有其他一些事情发生,但是1和2是测试用例#4缓慢的主要原因。

    如果我们比较表达式#1和#4,那么差异是:

    • #1每次都不会分配新的闭包;
    • #1每次都不进入运行时:关闭后,初始映射构造在生成代码的快速路径中处理。在生成的代码中处理整个构造比在运行时和生成的代码之间来回快得多;
    • #1不会每次为每个新闭包分配新的初始映射;
    • #1不会因溢出地图空间而造成标记扫描(只有廉价的清除)。

    如果我们比较#3和#4,那么差异是:

    • #3不会每次为每个新的闭包分配新的初始地图;
    • #3不会因溢出地图空间而造成标记扫描(只有便宜的清除);
    • #4在JS方面(没有Function.prototype.call,没有Object.create,没有Object.prototype查找等)在C ++方面做得更少(每次你做Object.create时#3也进入运行时但是非常那里很少。)

    这里的底线是,与相同闭包的后续构造调用相比,第一次使用闭包作为构造函数是昂贵的,因为V8必须设置一些管道。如果我们立即丢弃闭包,我们基本上抛弃了V8为加速后续构造函数调用所做的所有工作。

答案 1 :(得分:5)

问题是您可以检查各种引擎的当前源代码,但它对您没有多大帮助。不要试图超越编译器。无论如何,他们会尝试优化最常见的用法。我认为(function() { return this; }).call(Object.create(Object.prototype))调用1,000次并不具备真正的用例。

  

“应编写程序供人们阅读,并且只是偶然让机器执行。”

     

Abelson& Sussman,SICP,第一版序言

答案 2 :(得分:3)

我想以下扩展解释了V8中发生了什么:

  1. t(exp1):t(对象创建)
  2. t(exp2):t(Object.create()创建对象)
  3. t(exp3):t(Object.create()创建对象)+ t(功能对象创建)
  4. t(exp4):t(对象创建)+ t(功能对象创建)+ t(类对象创建)[在Chrome中]

    • 对于Chrome中的隐藏课程,请查看:http://code.google.com/apis/v8/design.html
    • 当Object.create创建新Object时,不必创建新的Class对象。已经有一个用于对象文字而不需要新的类。

答案 3 :(得分:0)

嗯,这两个电话并没有做同样的事情。考虑这种情况:

var Thing = function () {
  this.hasMass = true;
};
Thing.prototype = { holy: 'object', batman: '!' };
Thing.prototype.constructor = Thing;

var Rock = function () {
  this.hard = 'very';
};
Rock.prototype = new Thing();
Rock.constructor = Rock;

var newRock = new Rock();
var otherRock = Object.create(Object.prototype);
Rock.call(otherRock);

newRock.hard // => 'very'
otherRock.hard // => 'very'
newRock.hasMass // => true
otherRock.hasMass // => undefined
newRock.holy // => 'object'
otherRock.holy // => undefined
newRock instanceof Thing // => true
otherRock instanceof Thing // => false

因此,我们可以看到调用Rock.call(otherRock)不会导致otherRock继承原型。这必须考虑至少一些增加的缓慢。虽然在我的测试中,即使在这个简单的例子中,new结构也慢了近30倍。

答案 4 :(得分:0)

new f;
  1. 采取本地功能'f'(访问 本地框架中的索引) - 便宜。
  2. 执行字节码BC_NEW_OBJECT(或 类似的东西) - 便宜。
  3. 执行此功能 - 这里便宜。
  4. 现在这个:

    g.call(Object.create(Object.prototype));
    
    1. 查找全局var Object - 便宜吗?
    2. 在对象中找到属性prototype - 通用
    3. 在对象中找到属性create - 通用
    4. 查找本地变量; - 便宜的
    5. 查找属性call - 一般情况
    6. 调用create函数 - 通用
    7. 调用call函数 - 通用
    8. 而且:

      new (function() { })
      
      1. 创建新的函数对象(即匿名函数) - 相对昂贵。
      2. 执行字节码BC_NEW_OBJECT - 廉价
      3. 执行此功能 - 这里便宜。
      4. 如你所见,案例#1耗费最少。