在了解V8/spidermonkey/chakra的内部工作知识的情况下,它是一种在JavaScript中显式初始化未定义对象成员的优化吗?

M-P*_*xel 6 javascript performance v8 spidermonkey chakra

在JavaScript中,一个通常被吹捧的良好性能原则是避免改变对象的形状.

这让我很奇怪,是这样的

class Foo {
  constructor() {
    this.bar = undefined;
  }

  baz(x) { this.bar = x; }
}
Run Code Online (Sandbox Code Playgroud)

一个有价值的最佳实践,将提供比这更好的性能

class Foo {
  constructor() {
  }

  baz(x) { this.bar = x; }
}
Run Code Online (Sandbox Code Playgroud)

这是真是假?为什么?在一个JS引擎中,它是否或多或少与其他引擎相比?

jmr*_*mrk 11

V8开发人员在这里.

是的,第一个版本通常是值得的最佳实践.

原因不是对象创建本身会更快.相反,很明显,没有做任何工作的构造函数至少会比做某些工作的构造函数快一点.

推荐第一个版本的原因是因为它确保Foo应用程序中的所有对象都具有相同的"形状",而对于第二个版本,可能会发生其中一些具有.bar属性而其他对象没有.有时存在的属性有时不会强制JavaScript引擎远离它可以使用的最快的状态/代码路径; 当有多个这样的财产时,效果会更大.

举个例子:

class Foo() {
  constructor() {}
  addBar(x) { this.bar = x; }
  addBaz(x) { this.baz = x; }
  addQux(x) { this.qux = x; }
}
var foo1 = new Foo(); foo1.addBar(1);
var foo2 = new Foo(); foo2.addBaz(10); foo2.addBar(2);
var foo3 = new Foo(); foo3.addQux(100); foo3.addBaz(20); foo3.addBar(3);

function hot_function(foo) {
  return foo.bar;  // [1]
}
hot_function(foo1);
hot_function(foo2);
hot_function(foo3);
Run Code Online (Sandbox Code Playgroud)

在标记的行上[1],使用此版本的构造函数,可以看到至少三种不同形状的对象.因此,JavaScript引擎将bar在对象中的至少三个不同位置找到属性.根据其内部实现细节,它可能每次都必须搜索所有对象的属性,或者它可以缓存之前看到的对象形状,但缓存几个比缓存一个更昂贵,并且缓存尝试将受到限制.但是,如果构造函数已将所有属性初始化为undefined,则foo此处的所有传入对象将具有相同的形状,并且该bar属性将始终是它们的第一个属性,并且引擎可以使用非常快速的代码来处理这种非常简单的情况.

这不仅仅是这样的负载:addBar()根据它是否可以简单地覆盖现有属性(非常快),还有什么不同,必须添加一个新属性(可能要慢得多,可能需要分配和复制对象) ,或必须在两种情况之间动态决定(当然最慢).

另一个影响是每个唯一的对象形状将需要一些内部元数据.因此,避免不必要的不​​同对象形状将节省一些内存.

当然,对于这样一个小例子,任何影响都会很小.但是,一旦你有一个拥有数千个对象的大型应用程序,每个对象都有几十个属性,它可以产生很大的不同.谨防误导性的微基准!

  • 避免因更改类型而产生的各种性能命中,这正是建议在构造函数中初始化字段的原因.是的,如果后来的类型是已知的(并且总是相同的),然后初始化为同一类型的值将是一个好一点(特别是当它是一个数字),但初始化为`undefined`是完全正常的,最让你的好处.正如我所说,当然构造函数为空时,单独初始化会更快; 但随后的程序执行会因初始化所有字段而受益,因此预先做好这一点是值得的. (3认同)