Web worker 在处理大型数组时内存不足
Web worker out of memory when processing large array
我正在构建一个应用程序,它能够将文件上传到现有 API。此 API 在 JSON 对象中同时获取文件元数据和内容,因此我需要将文件的二进制内容转换为 base64 编码字符串。
由于这是一项可能很繁重的操作,我将此功能移到了 Web Worker 中。工作人员接收一个 ArrayBuffer
对象,其中包含二进制文件内容(从 FileReader.readAsArrayBuffer()
返回),以及 returns 一个 base64 编码的字符串。
这适用于较小的文件,但对于我需要支持的最大文件(~40 MB),这会导致我的工作人员出现内存不足异常(Internet Explorer 中为 8007000E)。在极少数情况下它会通过,但大多数时候工人只是死了。在将其移至 worker 之前也发生了同样的情况,只是整个浏览器页面崩溃了(在 IE 和 Chrome 中)。 Chrome 似乎比 IE 对工作人员的记忆压力更有弹性,但我仍然必须使其在 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);
}
};
我在这里做一些大禁忌吗?有什么方法可以减少内存消耗吗?我考虑过的一种解决方案是以块而不是整个文件的形式处理内容,然后连接生成的字符串并在外部对其进行编码。这是否可行,或者这会导致其自身出现问题吗?还有什么我不知道的神奇功能吗?我曾对 FileReader.readAsBinaryString()
抱有一线希望,但它现在已从标准中删除(并且在 IE10 中不受支持)所以我无法使用它。
(我意识到这个问题也可能与代码审查有关,但由于我的代码实际上崩溃了,我认为 SO 是正确的地方)
One solution I've thought about would be to process the contents in chunks rather than the whole file, then concatenate the resulting strings and encode it on the outside. Would that be viable, or will that cause problems of its own?
这就是 https://github.com/beatgammit/base64-js 似乎在做的事情,一次做 ~16k。使用这个,在我的电脑上不使用 transferables(因为 IE 10 不支持它们),Chrome 设法编码一个 190mb 的 ArrayBuffer(大于这个它抱怨无效的字符串长度)和 IE 11 40mb(大于这是我遇到内存不足的异常)。
您可以在 https://plnkr.co/edit/SShi1PE4DuMATcyqTRPx?p=preview 看到这个,那里的工作人员有代码
var exports = {};
importScripts('b64.js')
onmessage = function(e) {
var base64Bytes = fromByteArray(new Uint8Array(e.data));
postMessage(base64Bytes);
};
和主线程
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');
}
要超越 40mb 的限制,一种看起来很有希望的方法是一次只将较小的切片传递给工作人员(比如 1mb),对其进行编码,return 结果,然后才将下一个切片传递给工作人员,最后连接所有结果。我已经设法使用它来编码更大的缓冲区(在 IE 11 中高达 250mb)。我怀疑异步性允许垃圾收集器在调用之间 运行。
例如在 https://plnkr.co/edit/un7TXeHwYu8eBltfYAII?p=preview,在 worker 中使用与上面相同的代码,但在 UI 线程中:
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();
我正在构建一个应用程序,它能够将文件上传到现有 API。此 API 在 JSON 对象中同时获取文件元数据和内容,因此我需要将文件的二进制内容转换为 base64 编码字符串。
由于这是一项可能很繁重的操作,我将此功能移到了 Web Worker 中。工作人员接收一个 ArrayBuffer
对象,其中包含二进制文件内容(从 FileReader.readAsArrayBuffer()
返回),以及 returns 一个 base64 编码的字符串。
这适用于较小的文件,但对于我需要支持的最大文件(~40 MB),这会导致我的工作人员出现内存不足异常(Internet Explorer 中为 8007000E)。在极少数情况下它会通过,但大多数时候工人只是死了。在将其移至 worker 之前也发生了同样的情况,只是整个浏览器页面崩溃了(在 IE 和 Chrome 中)。 Chrome 似乎比 IE 对工作人员的记忆压力更有弹性,但我仍然必须使其在 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);
}
};
我在这里做一些大禁忌吗?有什么方法可以减少内存消耗吗?我考虑过的一种解决方案是以块而不是整个文件的形式处理内容,然后连接生成的字符串并在外部对其进行编码。这是否可行,或者这会导致其自身出现问题吗?还有什么我不知道的神奇功能吗?我曾对 FileReader.readAsBinaryString()
抱有一线希望,但它现在已从标准中删除(并且在 IE10 中不受支持)所以我无法使用它。
(我意识到这个问题也可能与代码审查有关,但由于我的代码实际上崩溃了,我认为 SO 是正确的地方)
One solution I've thought about would be to process the contents in chunks rather than the whole file, then concatenate the resulting strings and encode it on the outside. Would that be viable, or will that cause problems of its own?
这就是 https://github.com/beatgammit/base64-js 似乎在做的事情,一次做 ~16k。使用这个,在我的电脑上不使用 transferables(因为 IE 10 不支持它们),Chrome 设法编码一个 190mb 的 ArrayBuffer(大于这个它抱怨无效的字符串长度)和 IE 11 40mb(大于这是我遇到内存不足的异常)。
您可以在 https://plnkr.co/edit/SShi1PE4DuMATcyqTRPx?p=preview 看到这个,那里的工作人员有代码
var exports = {};
importScripts('b64.js')
onmessage = function(e) {
var base64Bytes = fromByteArray(new Uint8Array(e.data));
postMessage(base64Bytes);
};
和主线程
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');
}
要超越 40mb 的限制,一种看起来很有希望的方法是一次只将较小的切片传递给工作人员(比如 1mb),对其进行编码,return 结果,然后才将下一个切片传递给工作人员,最后连接所有结果。我已经设法使用它来编码更大的缓冲区(在 IE 11 中高达 250mb)。我怀疑异步性允许垃圾收集器在调用之间 运行。
例如在 https://plnkr.co/edit/un7TXeHwYu8eBltfYAII?p=preview,在 worker 中使用与上面相同的代码,但在 UI 线程中:
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();