问题描述
针对 v8 专家的问题。
我注意到,与类实例化相比,数组文字实例化似乎非常未优化。这很奇怪,因为构造类实例涉及执行构造函数代码 - 并且数组文字不需要执行任何代码并且可以非常有效地分配。我想知道我是否遗漏了一些明显的东西。
我在 Node 16 中收到的结果:
nickolay@frontier:~/workspace/typescript/monopoly$ nvm exec 16 node -r esm --expose-gc src_js/instantiation.js
Running node v16.4.1 (npm v7.18.1)
Garbage collection available.
Instantiate object: 1.325ms ±0.017 i:6 c:328
Instantiate array: 3.303ms ±0.077 i:6 c:138
数组字面量的性能要差 2.5 倍!!
第一个基准测试这个简单类的实例化:
class DataType0 {
constructor () {
this.prop00 = object
this.prop01 = 0
this.prop02 = '0'
this.prop03 = false
this.prop04 = true
this.prop05 = object
this.prop06 = 0
this.prop07 = 'true'
this.prop08 = false
this.prop09 = 0
this.prop10 = 0
this.prop11 = 0
this.prop12 = 0
this.prop13 = 0
this.prop14 = 0
this.prop15 = 0
}
}
第二个基准测试使用相同元素对数组字面量进行实例化:
[
object,'0',false,true,object,'true',]
分配函数如下所示:
//------------------------------------------------------------------------------
const size = 30000;
const generateObject = () => {
const instances = [];
do {
instances.push(__ALLOCATION_CALL_HERE__);
} while (instances.length < size);
return instances;
};
我希望数组文字实例化的性能要好得多,因为它不需要执行任何代码并且可以通过内存复制 + 一些 GC 跟踪来分配。至少,我希望数组文字的性能与类实例化相当。
我实例化 16 个属性的原因是,我在 v8 中的某个地方读到,每个新分配的数组都会收到 16 个元素的空间。
复制:
- 克隆此存储库:
[email protected]:canonic-epicure/monopoly.git
- 在 repo 文件夹内,在控制台中运行:
> npm i
> npx tsc
- 在控制台中运行:
> node -r esm --expose-gc src_js/instantiation.js
基准文件为:src_js/instantiation.js
问题:我是否遗漏了一些明显的东西(并且有一种方法可以更有效地实例化数组),或者这确实是 v8 中的“慢路径”?
解决方法
(此处为 V8 开发人员。)
TL;DR:这是一个微基准测试工件,不能反映真实世界的性能。稍加调整,相同的微基准测试会产生相反的结果。
长版:乍一看,这确实是一个奇怪的案例;我还希望对象和数组分配具有相同的性能。再说一次,它是一个微基准测试(尽管它们是人为的),而且微基准测试通常具有误导性,因此观察到的差异很可能是这个特定基准测试的编写方式造成的,因此与较少人为的情况无关。>
具体来说,快速分析运行表明该基准不主要测量分配——相反,它主要测量垃圾收集,因为它对两种创建垃圾的高效方法进行压力测试,这自然会产生大量垃圾。
稍微处理一下常量,结果证明当你设置 size = 20000
时,数组看起来是对象的两倍,而如果你将它保持在 size = 30000
,那么对象就会出现比数组快两倍;使用 size = 33000
,数组再次出现更快;使用 size = 1000
,它们的速度大致相同。 (数组稍微贵一点是有道理的,因为它们必须在后台分配两个堆对象,而在这种特殊情况下,DataType0
类可以将所有属性存储在对象本身中。)
由于 size
影响它们,我们可以推断出差异是由于 instances.push(...)
调用,而不是对象/数组分配本身;但实例列表在两种情况下都是相同的。我可以让你猜测(或花一整天的时间调查)究竟是什么导致了这些差异(快速猜测:不同的分配大小会影响新空间 GC 周期的时间,这反过来又会导致后续写入屏障成本的差异?),但是再说一遍,这并不重要——这只是一个误导性的微基准测试,这里没有什么可操作的。
构造类实例涉及执行构造函数代码——而数组字面量不需要执行任何代码
不,从引擎的角度来看,这不成立。 V8 将简单的类构造函数优化为与文字基本相同,因此它们都不会“执行任何代码”。然后再创建任何对象实例,不管优化策略如何,当然涉及到让CPU执行指令,所以就编译器而言,它们都执行代码......无论如何,它基本上是相同的机制,因此~相同的性能。
在 v8 中,每个新分配的数组都会收到 16 个元素的空间。
这是不正确的,但也与手头的案例无关。