30

基准:

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() { })

问题:

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

更新:

是什么特别导致newfg内联时变慢。

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

如果您链接任何JS引擎源,请说明。

4

5 回答 5

16

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

  1. 它总是在 V8 运行时(而不是生成的代码)中处理,并且编译的 JS 代码和 C++ 运行时之间的转换非常昂贵。随后的分配通常在生成的代码中处理。你可以看看Generate_JSConstructStubHelperinbuiltins-ia32.cc并注意到,Runtime_NewObject当闭包没有初始映射时,它会落入。(见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。这里重要的是地图被分配在单独的内存空间中。这个空间不能被快速的部分清除收集器清理。当 map 空间溢出时,V8 必须执行相对昂贵的 full mark-sweep GC。

当您第一次使用闭包作为构造函数时,还会发生其他一些事情,但是 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 为加速后续构造函数调用所做的所有工作。

于 2011-06-25T14:47:24.627 回答
5

问题是您可以检查各种引擎的当前源代码,但这对您没有多大帮助。不要试图智取编译器。无论如何,他们都会尝试针对最常见的用法进行优化。我认为(function() { return this; }).call(Object.create(Object.prototype))调用 1000 次根本没有真正的用例。

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

Abelson & Sussman,SICP,第一版前言

于 2011-06-24T21:02:19.650 回答
3

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

  1. t(exp1) : t(对象创建)
  2. t(exp2) : t(由 Object.create() 创建对象)
  3. t(exp3) : t(通过 Object.create() 创建对象) + t(创建函数对象)
  4. t(exp4) : t(Object Creation) + t(Function Object Creation) + t(Class Object Creation)[在 Chrome 中]

    • 对于 Chrome 中的隐藏类,请查看:http ://code.google.com/apis/v8/design.html 。
    • 当 Object.create 创建一个新对象时,不必创建新的 Class 对象。已经有一个用于对象字面量,不需要新的类。
于 2011-06-23T18:17:19.333 回答
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 倍。

于 2011-06-23T15:41:21.213 回答
0
new f;
  1. 使用本地函数'f'(通过本地框架中的索引访问) - 便宜。
  2. 执行字节码 BC_NEW_OBJECT (或类似的东西) - 便宜。
  3. 执行功能 - 这里便宜。

现在这个:

g.call(Object.create(Object.prototype));
  1. 查找全局变量Object- 便宜?
  2. 在 Object 中查找属性prototype- 一般
  3. 在 Object 中查找属性create- 一般
  4. 查找本地变量 g;- 便宜的
  5. 寻找房产call- 一般
  6. 调用create函数 - 一般
  7. 调用call函数 - 一般

还有这个:

new (function() { })
  1. 创建新的函数对象(即匿名函数) - 相对昂贵。
  2. 执行字节码 BC_NEW_OBJECT - 便宜
  3. 执行功能 - 这里便宜。

如您所见,案例#1 消耗最少。

于 2011-06-24T21:26:34.597 回答