【问题标题】:Working with arrays in V8 (performance issue)在 V8 中使用数组(性能问题)
【发布时间】:2013-06-02 10:43:02
【问题描述】:

我尝试了下一个代码(它在 Google Chrome 和 nodejs 中显示了类似的结果):

var t = new Array(200000); console.time('wtf'); for (var i = 0; i < 200000; ++i) {t.push(Math.random());} console.timeEnd('wtf');
wtf: 27839.499ms
undefined

我还运行了下一个测试:

var t = []; console.time('wtf'); for (var i = 0; i < 400000; ++i) {t.push(Math.random());} console.timeEnd('wtf');
wtf: 449.948ms
undefined
var t = []; console.time('wtf'); for (var i = 0; i < 400000; ++i) {t.push(undefined);} console.timeEnd('wtf');
wtf: 406.710ms
undefined

但在 Firefox 中,第一个变体看起来一切正常:

>>> var t = new Array(200000); console.time('wtf'); ...{t.push(Math.random());} console.timeEnd('wtf');
wtf: 602ms

V8 会发生什么?

UPD * 神奇地降低性能 *

var t = new Array(99999); console.time('wtf'); for (var i = 0; i < 200000; ++i) {t.push(Math.random());} console.timeEnd('wtf');
wtf: 220.936ms
undefined
var t = new Array(100000); t[99999] = 1; console.time('wtf'); for (var i = 0; i < 200000; ++i) {t.push(Math.random());} console.timeEnd('wtf');
wtf: 1731.641ms
undefined
var t = new Array(100001); console.time('wtf'); for (var i = 0; i < 200000; ++i) {t.push(Math.random());} console.timeEnd('wtf');
wtf: 1703.336ms
undefined
var t = new Array(180000); console.time('wtf'); for (var i = 0; i < 200000; ++i) {t.push(Math.random());} console.timeEnd('wtf');
wtf: 1725.107ms
undefined
var t = new Array(181000); console.time('wtf'); for (var i = 0; i < 200000; ++i) {t.push(Math.random());} console.timeEnd('wtf');
wtf: 27587.669ms
undefined

【问题讨论】:

  • 你为什么要new Array(200000)?除了设置length,它什么都不做。 (例如,它不会预先分配任何存储空间,因为数组并不是真正的数组。)
  • 我写这段代码只是为了测试。我想知道,为什么它表现得如此糟糕。
  • 我得走了,没时间回答,但答案很简单。 V8 在您的第一个示例中回退到稀疏数组,并在您的第二个示例中像顺序内存数组一样优化为 C。见code.google.com/p/v8/source/browse/trunk/src/array.js
  • 如果它除了设置长度什么都不做,为什么它会这样工作?
  • @yttrium 我不得不离开(现在是移动设备),但我已请 JS 室的朋友回答这个问题。不用担心你很快就会得到答案,但这是有道理的。

标签: javascript arrays google-chrome firefox v8


【解决方案1】:

稀疏数组来了! ? [2020]

在现代 JS 引擎中,完全支持稀疏数组。您可以以任何您喜欢的方式使用[]new Array(len),即使是随机访问。字典模式似乎已成为过去。

在当前的 Chrome 中(我猜是任何 V8 环境),数组的长度可以达到 2^32-1 并且分配是稀疏的(意味着空块不会占用任何内存):

但是,有一个问题

一方面,for 循环按预期工作,但是,Array 的内置高阶函数(例如 mapfilterfindsome 等)忽略未分配的元素。他们首先需要fill(或其他一些填充方法):

const a = new Array(10);
const b = new Array(10).fill(0);

a.forEach(x => console.log(x)); // does nothing
b.forEach(x => console.log(x)); // works as intended

the Array constructor codethe SetLengthWouldNormalize functionthe kMaxFastArrayLength constant 来看,它现在可以在使用字典模式之前支持几乎任意数量(目前上限为 3200 万)的元素。

但请注意,随着 V8 优化变得越来越复杂,现在还有更多考虑因素在起作用。 This official blog post from 2017 解释说数组可以区分 21 种不同类型的数组(或者更确切地说,数组元素类型),并且 - 引用:

“每个都有自己的一组可能的优化”

如果“稀疏数组有效,我们就这样吧!”对你来说还不够好,我会推荐以下:

原帖

如果您在 Chrome 或 Node(或更一般地,在 V8 中)中预先分配一个包含超过 10000 个元素的数组,它们会退回到字典模式,从而使事情变得非常慢。

感谢这个帖子中的一些 cmets,我能够追踪到 object.h's kInitialMaxFastElementArray

然后我将这些信息用于file an issue in the v8 repository,它现在开始获得一些关注,但仍需要一段时间。我引用:

我希望我们最终能够完成这项工作。但它仍然 可能还有很长的路要走。

【讨论】:

    【解决方案2】:

    如果您预先分配,请不要使用.push,因为您将创建一个由哈希表支持的稀疏数组。 You can preallocate sparse arrays up to 99999 elements 将由 C 数组支持,然后是哈希表。

    使用第二个数组,您以从 0 开始的连续方式添加元素,因此它将由真正的 C 数组支持,而不是哈希表。

    大致如下:

    如果你的数组索引从 0 到 Length-1 很好,没有空洞,那么它可以用一个快速的 C 数组来表示。如果你有 数组中的孔,那么它将由一个慢得多的哈希表表示。例外情况是,如果您预先分配一个数组 大小

    var a = new Array(N); 
    
    //If N < 100000, this will not make the array a hashtable:
    a[50000] = "sparse";
    
    var b = [] //Or new Array(N), with N >= 100000
    //B will be backed by hash table
    b[50000] = "Sparse";
    //b.push("Sparse"), roughly same as above if you used new Array with N > 0
    

    【讨论】:

    • +1 我必须为你投票,因为本杰明已经不在了(而且你也添加了更多内容)。
    • @dystroy 我在这里,通过手机投票。不错的答案! SpiderMonkey中对应的代码是怎样的?
    • @BenjaminGruenbaum 不能说,我从来没有看过。
    • @Esailija 实际上你写的并不完全正确。如果元素处于快速模式,则 ArrayPush 内置尝试将它们保持在快速模式。见code.google.com/p/v8/source/browse/trunk/src/builtins.cc#547中的逻辑
    • @yttrium 不同之处在于从字典模式返回快速模式的启发式方法如何工作。如果您的数组处于字典模式,那么每次需要增长时,V8 都会检查它是否足够密集,以及它是否可以通过使用连续(类 C)数组而不是字典来赢得空间。以 180000 为起点,启发式命中快速,而以 181000 启发式命中很晚。因此差异。启发式在这里:code.google.com/p/v8/source/browse/trunk/src/…
    猜你喜欢
    • 1970-01-01
    • 2016-12-28
    • 1970-01-01
    • 2017-08-02
    • 2020-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-16
    相关资源
    最近更新 更多