我试图了解v8优化器是如何工作的,以便使我的javascript代码获得最大的性能,这使得一些相当密集的计算。
为此,我使用3个不同的选项来保存6个矩阵值,称为Xx
,Xy
,Yx
,Yy
,Tx
,Ty
:
1 -使用普通对象:
XfObj.new = (Xx, Xy, Yx, Yy, Tx, Ty) => {
return { Xx, Xy, Yx, Yy, Tx, Ty };
}
2 -使用类
class XfCls_ {
Xx;
Xy;
Yx;
Yy;
Tx;
Ty;
constructor(Xx, Xy, Yx, Yy, Tx, Ty) {
this.Xx = Xx;
this.Xy = Xy;
this.Yx = Yx;
this.Yy = Yy;
this.Tx = Tx;
this.Ty = Ty;
}
}
XfCls.new = (Xx, Xy, Yx, Yy, Tx, Ty) => {
return new XfCls_(Xx, Xy, Yx, Yy, Tx, Ty);
}
3 -使用旧的学校班级建设
const XfCls2_ = function XfCls2_(Xx, Xy, Yx, Yy, Tx, Ty) {
this.Xx = Xx;
this.Xy = Xy;
this.Yx = Yx;
this.Yy = Yy;
this.Tx = Tx;
this.Ty = Ty;
return this;
};
XfCls2.new = (Xx, Xy, Yx, Yy, Tx, Ty) => {
return new XfCls2_(Xx, Xy, Yx, Yy, Tx, Ty);
}
而且由于某些原因,表演真的不太一样
1 -普通对象:3569毫秒
2 - es6类:13577毫秒
3、老学校:2519女士
请注意,对于案例2,如果我删除了类的字段声明,所以只有构造函数保留在类体中,我会获得与旧学校类相同的性能,所以差异可能来自于此,但是为什么有字段声明会使类示例变慢呢?
计算的细节在这里并不重要。我有3个引用矩阵的数组,然后我迭代地浏览以执行矩阵组合。你要知道,初始化代码和预热是不测量的,每次运行彼此独立,所以一切都保持单态(我用v8-deopt-viewer检查了一下)。
另一件令人惊讶的事情是,如果我检查v8-natives,这3个都没有相同的Map,而他们的debugPrint非常相似。
你知道这是怎么回事吗?
我已经通过V8文档,博客文章和一些视频,但没有找到任何相关的。也v8-natives并没有真正帮助,因为唯一有用的函数是debugPrint
和haveSameMap
。
1条答案
按热度按时间tp5buhyn1#
这确实是一个令人惊讶的差异,我不知道是什么原因导致的。我必须调查一个可以观察到它的案例,但是玩弄你提供的片段,我无法重现任何显著的性能差异。
所以,如果你想要更多的帮助,请发布一个完整的可复制的例子。(更新:见下文增编)
或者你可以继续前进:在构造函数之外“声明”属性没有任何好处(引号是因为“声明属性”不是JavaScript中存在的概念),但显然在您的情况下,它有一个明显的缺点,所以只需保存额外的输入工作,不要“声明”属性。简单地写:
今天就到此为止。
(By顺便说一句,老式构造函数末尾的
return this
也没有任何用处,可以省略。)如果我检查v8-natives,这3个都没有相同的Map
不同的构造函数总是产生具有不同Map的对象,即使对象的布局完全相同。
提供复制案例后的附录:
谢谢你的重现,这让我们很清楚地看到了发生了什么。相反,它们被有效地大致处理为:
(这是因为,正如我今天所了解的,它们实际上可以在某些有点奇特的情况下具有语义意义:它们导致超类上相同属性的任何setter被子类上的普通属性覆盖。)
这反过来又击败了V8的“字段类型跟踪”机制:在没有声明的情况下,V8正确地观察到该字段到目前为止一直保持一个数字,并基于这样的假设进行优化,即通过为属性的值使用可变/可重用的数字框,这使得在其中存储新值变得便宜。(和隐式
undefined
-初始化),V8认为(* 有些 * 正确...)该字段过去同时包含undefined
和数字,因此它不会应用相同的优化,因此,对该属性赋值会导致每次分配新的(不可变的)HeapNumbers,这对性能有一些直接和间接的负面影响。我们将看看我们是否可以在V8中改进这一点。
在此期间,您可以在您的终端上执行两个解决方案:(1)正如我之前建议的,不要声明class字段。在TypeScript中,有
useDefineForClassFields
设置,您可以关闭它以不获取它们。(2)将属性初始化为数字,例如:这将确保
Xx
始终保持数值的不变量。(额外的赋值将被优化掉。)