JS数组内部如何调整大小?

mobileDev07

因此,我一直在尝试在JS中实现具有某些自定义功能的类的集合类型(类似于C#中的List)。我还希望对其进行某种程度的优化(我已经阅读了一些有关如何正确使用JS数组的文章)。因此,我对自己想:“如果我们不为数组定义初始大小,而是继续向其添加对象,则内部必须为每次插入分配新的大小,这必须很慢。我可以通过分配来避免这种情况我自己更改了一个新大小(改变了数组的长度),有点类似于在CSharp中完成的操作,每当达到最大容量时,大小都会加倍(我知道这不是小事,但这只是一个开始)”

我试图实现这个想法,发现它慢得多(慢了10倍):

//This simplified approach of my implementation is faster...
var array = [];
var counter = 0;
function addItem(newItem) {
    array[++counter] = newItem;
}

//..than this version that resizes the array when a limit is reached
var array = [];
array.length = INITIAL_SIZE;
/*
 Alternatively
 var array = new Array(INITIAL_SIZE);
*/
var counter = 0;
function addItem(newItem) {
    if( CheckCapacity(counter + 1) ) { //Function that checks if the maximum size is reached and if it is, change the array.length to the new size
        array[++counter] = newItem;
    }
}

在测试之前,我心想:“由于在调用CheckCapacity(counter + 1)时具有新的数组大小,因此在内部(JS Array)与第一个函数相比,无需进行过多的操作因为我确保有足够的可用空间”,也就是说,第二个函数上的array [++ counter] = newItem行应比第一个函数中的相同更快。我什至使用了不同的数组,其中包含预先计算的大小,用于存放项目。它仍然较慢。

回到我的问题,JS Array的实现如何分配必要的大小?我是否可以正确地假设不能做太多事情来加快此过程?对我而言,每次添加新项时动态分配更多内存的对象(JS数组)的弊端就是速度的损失(除非它实现了很好的算法,但我不知道)不知道,因此是我的问题)。

詹姆斯·劳森

在Javascript中,数组是一种抽象。它的实现方式(以及执行分配和调整大小的时间)由Javascript引擎决定-ECMAScript规范并不指示如何实现。因此,基本上没有确切的方法知道

实际上,Javascript引擎非常聪明地了解如何分配内存,并确保不要分配太多。在我看来,它们比C#复杂得多List-因为Javascript引擎可以根据情况动态更改基础数据结构。算法各不相同,但是大多数算法会考虑数组中是否有“空洞”:

var array = [];
array[0] = "foo"          // is a resizable array
array[1] = "bar"          // is a resizable array
array[2] = "baz"          // is a resizable array
array[1000000] = "hello"; // is now a hash table
console.log(array[1000000]) // "hello"

如果您正常使用数组并使用从零开始的连续键,则没有“空洞”,并且大多数Javascript引擎将通过使用可调整大小的数组数据结构来表示Javascript数组。现在考虑第四项任务,我创建了一个所谓的“孔”,其大小大约为一百万(该孔跨越插槽3-999999)。事实证明,JavaScript引擎足够聪明,不会为这个巨大的漏洞分配约100万个内存插槽。它检测到我们现在有一个空洞,它将使用字典/哈希表(类似于数据结构)表示Javascript数组(它使用对密钥进行哈希处理的二进制搜索树)来节省空间。它不会存储空间的孔,只有四个映射:(0, "foo")(1, "bar")(2, "baz")(1000000, "hello")

不幸的是,对于引擎而言,访问数组现在变得更慢,因为它现在必须计算哈希值并遍历树。如果没有孔,则使用可调整大小的数组,访问时间更快,但是,如果有孔,则阵列的性能会降低。通用术语是说Array是密集数组,当它没有任何孔时(使用可调整大小的数组=更好的性能),而Array是稀疏数组,当它具有一个或多个孔时(使用哈希)表=性能降低)。通常,为了获得最佳性能,请尝试使用密集阵列。

现在结束,让我告诉您以下是一个坏主意:

var array = new Array(1000000);
array[0] = "foo";               // is a hash table

上面的数组有一个约100万个大小的孔(就像["foo", undefined, undefined, ... undefined]这样:),因此,它使用哈希表作为基础数据结构。因此,自己实施调整大小是一个坏主意-这会造成漏洞,并导致性能不佳。您只是在混淆Javascript引擎。这就是您的代码正在做的事情,您的数组中始终有一个洞,因此使用哈希表作为基础数据结构;与没有任何孔的阵列(又称代码的第一个版本)相比,性能会降低。

我是否可以正确地假设不能做太多事情来加快此过程?

是的,关于空间的预分配,在用户方面几乎没有什么可做的。通常,要加快Javascript数组的速度,您要避免创建稀疏数组(避免创建空洞):

  1. 不要使用预先分配new Array(size)而是“随您成长”。引擎将计算出可调整大小的基础数组本身的大小
  2. 使用从0开始的连续整数键。不要从大整数开始。不要添加非整数的键(例如,不要使用字符串作为键)。
  3. 尽量不要删除数组中间的键(不要从填充了索引0-9的数组中删除索引5的元素)。
  4. 不要在密集和稀疏的数组之间来回转换(即不要重复添加和删除孔)。引擎在与可调整大小的数组和哈希表表示形式之间来回转换会产生开销。

[基于C#列表的JS数组的缺点是,每次添加新项时它们都会动态分配更多的内存]

不,不一定当Javascript数组没有空洞时,C#列表和Javascipt数组基本相同。两者都是可调整大小的数组。区别在于:

  1. C#列表使用户可以更好地控制可调整大小数组的行为。在Javascript中,您无法控制它-它在引擎内部。
  2. C#列表允许用户预分配内存以获得更好的性能,而在Javascript中,您应该让引擎自动计算出如何在底层可调整大小的数组中预分配内存以获得更好的性能。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章