如何在 v8 中有效地实例化 JavaScript 数组文字?
How to instantiate JavaScript array literal in v8 efficiently?
v8 专家提问。
我注意到,与 class 实例化相比,数组文字实例化似乎没有经过优化。这很奇怪,因为构造 class 实例涉及执行构造函数代码 - 而数组文字不需要执行任何代码并且可以非常有效地分配。我想知道我是否遗漏了一些明显的东西。
我在节点 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:
的实例化
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,
'0',
false,
true,
object,
0,
'true',
false,
0,
0,
0,
0,
0,
0,
0,
]
分配函数如下所示:
//------------------------------------------------------------------------------
const size = 30000;
const generateObject = () => {
const instances = [];
do {
instances.push(__ALLOCATION_CALL_HERE__);
} while (instances.length < size);
return instances;
};
我希望数组文字实例化的性能更好,因为它不需要执行任何代码并且可以通过内存复制 + 一些 GC 跟踪进行分配。至少,我希望数组文字的性能与 class 实例化相当。
我实例化 16 个属性的原因是,我在某处读到,在 v8 中,每个新分配的数组接收 16 个元素的 space。
重现:
- 克隆此存储库:
git@github.com: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
class 将所有属性存储在对象中本身。)
由于 size
影响它们,我们可以推断差异是由于 instances.push(...)
调用,而不是 object/array 分配本身;但实例列表在两种情况下都是相同的。我可能会猜测(或花一整天的时间进行调查)到底是什么导致了这些差异(快速猜测:不同的分配大小会影响 new-space GC 周期的时间,这反过来会导致后续写入屏障成本的差异?),但话又说回来,这并不重要——这只是一个误导性的微基准测试,这里没有任何可操作的东西可以学习。
constructing class instance involves executing constructor code - and array literals do not need to execute any code
不,从引擎的角度来看,这是不成立的。 V8 将简单的 class 构造函数优化为与文字基本相同,因此它们都不会“执行任何代码”。然后再创建任何对象实例,无论优化策略如何,当然涉及到 CPU 执行指令,所以对于编译器而言,它们都执行代码......无论如何,它基本上是一样的机制,因此具有相同的性能。
in v8 every newly allocated array receives space for 16 elements.
这是不正确的,但也与手头的案例无关。
v8 专家提问。
我注意到,与 class 实例化相比,数组文字实例化似乎没有经过优化。这很奇怪,因为构造 class 实例涉及执行构造函数代码 - 而数组文字不需要执行任何代码并且可以非常有效地分配。我想知道我是否遗漏了一些明显的东西。
我在节点 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:
的实例化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,
'0',
false,
true,
object,
0,
'true',
false,
0,
0,
0,
0,
0,
0,
0,
]
分配函数如下所示:
//------------------------------------------------------------------------------
const size = 30000;
const generateObject = () => {
const instances = [];
do {
instances.push(__ALLOCATION_CALL_HERE__);
} while (instances.length < size);
return instances;
};
我希望数组文字实例化的性能更好,因为它不需要执行任何代码并且可以通过内存复制 + 一些 GC 跟踪进行分配。至少,我希望数组文字的性能与 class 实例化相当。
我实例化 16 个属性的原因是,我在某处读到,在 v8 中,每个新分配的数组接收 16 个元素的 space。
重现:
- 克隆此存储库:
git@github.com: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
class 将所有属性存储在对象中本身。)
由于 size
影响它们,我们可以推断差异是由于 instances.push(...)
调用,而不是 object/array 分配本身;但实例列表在两种情况下都是相同的。我可能会猜测(或花一整天的时间进行调查)到底是什么导致了这些差异(快速猜测:不同的分配大小会影响 new-space GC 周期的时间,这反过来会导致后续写入屏障成本的差异?),但话又说回来,这并不重要——这只是一个误导性的微基准测试,这里没有任何可操作的东西可以学习。
constructing class instance involves executing constructor code - and array literals do not need to execute any code
不,从引擎的角度来看,这是不成立的。 V8 将简单的 class 构造函数优化为与文字基本相同,因此它们都不会“执行任何代码”。然后再创建任何对象实例,无论优化策略如何,当然涉及到 CPU 执行指令,所以对于编译器而言,它们都执行代码......无论如何,它基本上是一样的机制,因此具有相同的性能。
in v8 every newly allocated array receives space for 16 elements.
这是不正确的,但也与手头的案例无关。