如何在 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。

重现:

  1. 克隆此存储库:git@github.com:canonic-epicure/monopoly.git
  2. 在 repo 文件夹内,运行 在控制台中:
> npm i
> npx tsc
  1. 运行 在控制台中:
> 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.

这是不正确的,但也与手头的案例无关。