将项目推入数组的最佳性能方式是什么?

8
在我的网站上,我有许多包含数据的数组。例如:顶点数组、颜色数组、大小数组...
我正在处理大量的项目,高达数千万。
在将数据添加到数组之前,我需要对其进行处理。迄今为止,我是在主线程中完成的,这使得我的网站冻结了X秒。它因处理和将处理后的数据添加到数组而冻结。
今天,我将处理工作移到了Web Workers中,但处理后的数据仍然是在主线程中添加的。我设法节省了处理时间,但没有减少添加时间。
添加操作只需使用array.push()array.splice()即可完成。
我阅读了一些关于数组工作原理的文章,并发现当我们向数组添加项时,会将数组完全复制到内存中的新位置,并增加array.length + 1的大小,然后再添加值。这使得我的数据推送变慢。
我还了解到,类型化数组要快得多。但是,为此我需要知道数组的大小,而我不知道,而且为了创建一个带有额外计数器的大型类型化数组并管理在数组中间(而不是在数组末尾)添加项,需要改变很多代码,这些我现在不想做。
所以,对于我的问题,我有从Web Workers返回的TypedArray,我需要将其放入常规数组中。最佳的性能方式是什么?(今天我正在循环运行并一个一个地推送)
编辑
网站工作示例: 客户端添加项目计数,假设是100000个。 收集项目原始数据并发送到worker。 worker处理所有信息并将处理后的数据作为类型化数组发送回来(用作可传输对象)。在主线程中,我们将处理后的数据添加到数组中-放置在末尾或某个特定索引中。 第二轮。客户端再次添加100000个项目。发送到worker并将结果添加到主线程数组中。 第3轮可能是10个项目,第4轮是10000个项目,第5轮可以删除索引10-2000,...

1
听起来你应该使用缓存。另外,你真的需要在客户端同时拥有所有的数据吗? - tyteen4a03
你可能想复制原始数组,然后在 worker 中改变新的数组。如果原始数组不包含对象,则可以使用 Array#slice 复制它。 - Pavlo
它是可转移对象,不需要从 Web Worker 中取回时间。对于 tyteen4a03,不幸的是,客户正在上传大量数据,他不希望主线程冻结。 - Raziza O
1
为了明确一点,我的主线程包含真实的数据数组。工作线程正在处理部分数据,完成后将其添加到主线程数组中。我无法使用@Pavlo的解决方案,因为我不能复制我的主线程数组并将其发送到工作线程,因为它可能会被另一个工作线程更改。 - Raziza O
1
你可以将你的解决方案作为一个被接受的答案添加进来 :) 顺便说一下,干得好。 - BeyelerStudios
显示剩余7条评论
1个回答

6

通过评论做了更多的研究,思考了另一种方向。

我尝试使用 typedArray.set 方法,并发现它非常快速。

使用 set 方法处理 1000 万个项目只需 0.004 秒,与使用 array.push 相比需要 0.866 秒。我将这 1000 万个项目分成了 10 个数组,以确保 set 方法从索引 0 开始时并不比其他情况运行得更快。

这样做,我认为我甚至可以使用 TypedArray 实现自己的 insertAtIndex,即将所有项向前推送并将新的项设置在正确的索引处。

此外,我可以使用 TypedArray.subArray 根据数组中实际数据的数量来获取子数据(无需复制数据),这对于上传数据到缓冲区(WebGL)非常有用。

我曾说过我想使用普通数组,但我觉得否则我不会获得这种性能提升。而且这并不是那么费力的事情,当我把 MyNewTypedArray 封装成带有所有 pushsplice 和自己实现的 TypedArray 时。

希望这些信息对任何人都有所帮助。

var maxCount = 10000000;
var a = new Float32Array(maxCount);
var aSimple = [];

var arrays = [];
var div = 10;
var arrayLen = maxCount / div;
for (var arraysIdx = 0; arraysIdx < div; arraysIdx++) {
  var b = new Float32Array(arrayLen);
  for (var i = 0; i < b.length; i++) {
    b[i] = i * (arraysIdx + 1);
  }
  arrays.push(b);
}

var timeBefore = new Date().getTime();

for (var currArrayIdx = 0; currArrayIdx < arrays.length; currArrayIdx++) {
  a.set(arrays[currArrayIdx], currArrayIdx * arrayLen);
}
var timeAfter = new Date().getTime();
good.innerHTML = (timeAfter - timeBefore) / 1000 + " sec.\n";
timeBefore = new Date().getTime();
for (var currArrayIdx = 0; currArrayIdx < arrays.length; currArrayIdx++) {
  for (var i = 0; i < arrayLen; i++) {
    aSimple.push(arrays[currArrayIdx][i]);
  }
}

timeAfter = new Date().getTime();
bad.innerHTML = (timeAfter - timeBefore) / 1000 + " sec.\n";
Using set of TypedArray:
<div id='good' style='background-color:lightGreen'>working...</div>
Using push of Array:
<div id='bad' style='background-color:red'>working...</div>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接