处理大型数组时,Web worker缺少内存

时间:2016-03-23 10:23:25

标签: javascript web-worker

我正在构建一个应用程序,除此之外还可以将文件上传到现有API。此API在JSON对象中获取文件元数据和内容,因此我需要将文件的二进制内容转换为base64编码的字符串。

由于这是一个潜在的繁重操作,我将功能转移到了Web工作者。 worker使用二进制文件内容(从ArrayBuffer返回)接收FileReader.readAsArrayBuffer()对象,并返回base64编码的字符串。

这适用于较小的文件,但对于我需要支持的最大文件(~40 MB),这会导致我的工作程序的内存不足(Internet Explorer中的8007000E)。在极少数情况下它会通过,但大部分时间工人都会死。在将它移动到工作者之前发生了同样的事情,除了整个浏览器页面崩溃(在IE和Chrome中)。与IE相比,Chrome似乎对工作者的内存压力更具弹性,但我仍然需要在IE(10 +)中使其正常工作。

我的工人:

onmessage = e => {
  const bytes = new Uint8Array(e.data);
  const l = bytes.length;
  const chars = new Array(l);
  for (let i = 0, j = l - 1; i <= j; ++i, --j) {
    chars[i] = String.fromCharCode(bytes[i]);
    chars[j] = String.fromCharCode(bytes[j]);
  }
  const byteString = chars.join('');
  const base64bytes = btoa(byteString);

  try {
    postMessage(base64bytes, [base64bytes]);
  } catch (e) {
    postMessage(base64bytes);
  }
};

我在这里做了一些大的no-nos吗?有没有办法减少内存消耗?我想到的一个解决方案是以块为单位处理内容而不是整个文件,然后连接结果字符串并在外部对其进行编码。这会是可行的,还是会导致问题呢?还有其他我不知道的神奇功能吗?我对FileReader.readAsBinaryString()有一线希望,但它现在已从标准中移除(无论如何都不支持IE10),所以我无法使用它。

(我意识到这个问题也可能与Code Review有关,但由于我的代码实际上崩溃了,我认为这是正确的地方)

1 个答案:

答案 0 :(得分:0)

  

我想到的一个解决方案是处理块中的内容而不是整个文件,然后连接生成的字符串并在外部对其进行编码。这会是可行的,还是会导致问题呢?

这就是https://github.com/beatgammit/base64-js似乎做的事情,一次做〜16k。使用这个,不使用transferables(因为IE 10不支持它们)在我的计算机上,Chrome设法编码190mb ArrayBuffer(大于此抱怨无效的字符串长度),IE 11 40mb(大于此我得到一个内存不足的例外)。

您可以在https://plnkr.co/edit/SShi1PE4DuMATcyqTRPx?p=preview处看到此信息,其中工作人员拥有代码

var worker = new Worker('worker.js');
var length = 1024 * 1024 * 40;
worker.postMessage(new ArrayBuffer(length));

worker.onmessage = function(e) {
  console.log('Received Base64 in UI thread', e.data.length, 'bytes');
}

和主线程

var worker = new Worker('worker.js');
var length = 1024 * 1024 * 60;
var buffer = new ArrayBuffer(length);

var maxMessageLength = 1024 * 1024;
var i = 0;
function next() {
  var end = Math.min(i + maxMessageLength, length);
  var copy = buffer.slice(i, end);
  worker.postMessage(copy);
  i = end;
}

var results = [];
worker.onmessage = function(e) {
  results.push(e.data);
  if (i < length) {
    next();
  } else {
    results = results.join('');
    alert('done ' + results.length);
  }
};

next();

要超出40mb的限制,一种看似有希望的方法是,一次只将一个较小的切片传递给工人(比如1mb),对其进行编码,返回结果,然后才将下一个切片传递给工人,最后将所有结果连接起来。我设法使用它来编码更大的缓冲区(在IE 11中高达250mb)。我怀疑异步性允许垃圾收集器在调用之间运行。

例如at https://plnkr.co/edit/un7TXeHwYu8eBltfYAII?p=preview,在上面的worker中使用相同的代码,但是在UI线程中:

<i>
for(i=0;i<20000;i++)
            {
                //System.out.println(volts[i]);

                if(i!=0)
                    g2d.draw(new Line2D.Double(time-(timeScale/y),-(((voltScale/x)*50*volts[i-1])-300),time,-(((voltScale/x)*50*volts[i])-300)));
                time+=(timeScale/y);
            }
</i>