考虑到 V8/spidermonkey/chakra 的内部工作原理,显式初始化 JavaScript 中未定义的对象成员是否是一种优化?
Is it an optimization to explicitly initialize undefined object members in JavaScript, given knowledge of the innerworkings of V8/spidermonkey/chakra?
在 JavaScript 中,一个通常被吹捧的良好性能原则是避免改变对象的形状。
这让我想知道,这是
class Foo {
constructor() {
this.bar = undefined;
}
baz(x) { this.bar = x; }
}
一个有价值的最佳实践,将提供比这更好的性能
class Foo {
constructor() {
}
baz(x) { this.bar = x; }
}
这是真的还是假的?为什么?在一个 JS 引擎中是否比其他引擎更真实或更不真实?
这里是 V8 开发人员。
是的,总的来说,第一个版本是一个有价值的最佳实践。
原因是不是对象创建本身会更快。相反,很明显,不做任何工作的构造函数至少比做一些工作的构造函数快一点。
推荐第一个版本的原因是因为它确保应用程序中的所有 Foo
个对象将具有相同的 "shape",而对于第二个版本,其中一些对象可能会发生有 .bar
属性 而其他人没有。有时存在有时不存在的属性往往会迫使 JavaScript 引擎远离它可以使用的最快可能 states/code 路径;当有多个这样的 属性.
时,效果会大得多
举个例子:
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);
在标记为 [1]
的行中,使用此版本的构造函数,可以看到至少三种不同形状的对象。因此 JavaScript 引擎将在对象内的至少三个不同位置找到 属性 bar
。根据其内部实现细节,它可能每次都必须搜索所有对象的属性,或者它可以缓存它以前见过的对象形状,但缓存多个比缓存一个更昂贵,并且缓存尝试会有限制.
但是,如果构造函数将所有属性初始化为 undefined
,那么这里所有传入的 foo
对象将具有相同的形状,并且 bar
属性 将始终是它们的第一个 属性,引擎可以使用非常快速的代码来处理这种非常简单的情况。
不仅仅是这样的负载:addBar()
的幕后工作也会有所不同,具体取决于它是否可以简单地覆盖现有的 属性(非常快),必须添加一个新的 属性(可能慢很多,可能需要分配和复制对象),或者必须在两种情况之间动态决定(当然是最慢的)。
另一个影响是每个独特的对象形状都需要一定数量的内部元数据。因此,避免不必要的不同对象形状将节省一些内存。
当然对于这么小的例子,任何影响都是很小的。但是一旦你拥有一个包含数千个对象的大型应用程序,每个对象都有几十个属性,它就会产生很大的不同。谨防误导性微基准测试!
在 JavaScript 中,一个通常被吹捧的良好性能原则是避免改变对象的形状。
这让我想知道,这是
class Foo {
constructor() {
this.bar = undefined;
}
baz(x) { this.bar = x; }
}
一个有价值的最佳实践,将提供比这更好的性能
class Foo {
constructor() {
}
baz(x) { this.bar = x; }
}
这是真的还是假的?为什么?在一个 JS 引擎中是否比其他引擎更真实或更不真实?
这里是 V8 开发人员。
是的,总的来说,第一个版本是一个有价值的最佳实践。
原因是不是对象创建本身会更快。相反,很明显,不做任何工作的构造函数至少比做一些工作的构造函数快一点。
推荐第一个版本的原因是因为它确保应用程序中的所有 Foo
个对象将具有相同的 "shape",而对于第二个版本,其中一些对象可能会发生有 .bar
属性 而其他人没有。有时存在有时不存在的属性往往会迫使 JavaScript 引擎远离它可以使用的最快可能 states/code 路径;当有多个这样的 属性.
举个例子:
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);
在标记为 [1]
的行中,使用此版本的构造函数,可以看到至少三种不同形状的对象。因此 JavaScript 引擎将在对象内的至少三个不同位置找到 属性 bar
。根据其内部实现细节,它可能每次都必须搜索所有对象的属性,或者它可以缓存它以前见过的对象形状,但缓存多个比缓存一个更昂贵,并且缓存尝试会有限制.
但是,如果构造函数将所有属性初始化为 undefined
,那么这里所有传入的 foo
对象将具有相同的形状,并且 bar
属性 将始终是它们的第一个 属性,引擎可以使用非常快速的代码来处理这种非常简单的情况。
不仅仅是这样的负载:addBar()
的幕后工作也会有所不同,具体取决于它是否可以简单地覆盖现有的 属性(非常快),必须添加一个新的 属性(可能慢很多,可能需要分配和复制对象),或者必须在两种情况之间动态决定(当然是最慢的)。
另一个影响是每个独特的对象形状都需要一定数量的内部元数据。因此,避免不必要的不同对象形状将节省一些内存。
当然对于这么小的例子,任何影响都是很小的。但是一旦你拥有一个包含数千个对象的大型应用程序,每个对象都有几十个属性,它就会产生很大的不同。谨防误导性微基准测试!