JS String 连接爆炸内存消耗
JS String concatenation explodes memory consumption
我广泛分析了一段代码,直到我发现当 "array" 的大小约为 33MB 时,以下代码在最新的 Chrome 版本上以私有模式分配了超过 1GB 的 RAM,大小并不重要,它只是一个具有此大小的文件,我 运行 使用它进行测试。
我不知道如何在代码中生成如此大的 Uint8Array 供您测试,因此下面的代码不能 运行 原样,但也许您无论如何都能理解并帮助我。
const bytesToString = function (array) {
let uint8Array = new Uint8Array(array);
let length = uint8Array.byteLength;
let stringToEncode = "";
for (let i = 0; i < length; i++) {
stringToEncode += String.fromCharCode(uint8Array[i]);
}
return stringToEncode;
}
取消注释 "for loop" 时,RAM 消耗保持在同一水平,而 运行 宁我的代码,一旦 "for loop" 处于活动状态,消耗就会激增到超过 1GB。这当然会在某个时候出现 GC,但是我有一个普遍的内存问题,浏览器最终会因为内存消耗过多而崩溃,我想弄清楚这个函数是否是问题所在。
我可以通过 Chrome 的性能分析器看到 GC 被调用了很多次,我不知道 Chrome 的 GC 是如何工作的,因为你可以阅读很多 "Minor GC" 并且在一些指向末尾 "Major GC" 我想知道 "Minor GC" 是否真的意味着 RAM 正在被释放,而是 "collected" 并且只是在稍后一点 "Major GC" 真的释放内存。如果是这种情况,我想在调用此函数和 "Major GC" 我的代码之间 运行s 也需要比平时更多的 RAM,然后浏览器崩溃。如果是这种情况,问题是我的功能是否有更好的实现,或者我可以操纵 GC 吗?据我所知,我不能。
JS中的字符串是不可变的,所以每添加一个字符,都会创建一个比前一个长1个字符的新字符串。 GC 不会 运行 直到一切都完成,所以你会被大量不同长度的字符串困住。
您需要其他组合字符串的方法。在这种情况下,您的整个函数可以写成 String.fromCharCode(...array)
(尽管如果您真的想从二进制数据创建一个字符串,您应该考虑使用 TextDecoder
代替,它支持各种编码,但需要注意的是在 Node.js).
等环境中不可用
更新: String.fromCharCode
似乎不适用于非常大的数组(任何函数的参数数量都有限制),因此您可以尝试将数组映射为 1 个字符的字符串,然后将它们连接在一起:
Array.prototype.map.call(uint8Array, c => String.fromCharCode(c)).join("")
(注意使用 Array.prototype.map
而不是 uint8Array.map
,因为后者会 运行 将您的结果转换为 Uint8)
我认为 TextDecoder
可能是正确的解决方案。但如果您坚持,您也可以尝试创建一个 blob,然后从中读取。
let blob = new Blob([arrayBuffer], {type: 'application/octet-stream'});
let reader = new FileReader();
reader.onload = function (event) {
console.log(event.target.result);
};
// Use if you want the UTF-8 encoded version
reader.readAsText(blob);
// Use if you for example need to use the result with "window.btoa" as it was in my case.
reader.readAsBinaryString(blob);
我广泛分析了一段代码,直到我发现当 "array" 的大小约为 33MB 时,以下代码在最新的 Chrome 版本上以私有模式分配了超过 1GB 的 RAM,大小并不重要,它只是一个具有此大小的文件,我 运行 使用它进行测试。 我不知道如何在代码中生成如此大的 Uint8Array 供您测试,因此下面的代码不能 运行 原样,但也许您无论如何都能理解并帮助我。
const bytesToString = function (array) {
let uint8Array = new Uint8Array(array);
let length = uint8Array.byteLength;
let stringToEncode = "";
for (let i = 0; i < length; i++) {
stringToEncode += String.fromCharCode(uint8Array[i]);
}
return stringToEncode;
}
取消注释 "for loop" 时,RAM 消耗保持在同一水平,而 运行 宁我的代码,一旦 "for loop" 处于活动状态,消耗就会激增到超过 1GB。这当然会在某个时候出现 GC,但是我有一个普遍的内存问题,浏览器最终会因为内存消耗过多而崩溃,我想弄清楚这个函数是否是问题所在。 我可以通过 Chrome 的性能分析器看到 GC 被调用了很多次,我不知道 Chrome 的 GC 是如何工作的,因为你可以阅读很多 "Minor GC" 并且在一些指向末尾 "Major GC" 我想知道 "Minor GC" 是否真的意味着 RAM 正在被释放,而是 "collected" 并且只是在稍后一点 "Major GC" 真的释放内存。如果是这种情况,我想在调用此函数和 "Major GC" 我的代码之间 运行s 也需要比平时更多的 RAM,然后浏览器崩溃。如果是这种情况,问题是我的功能是否有更好的实现,或者我可以操纵 GC 吗?据我所知,我不能。
JS中的字符串是不可变的,所以每添加一个字符,都会创建一个比前一个长1个字符的新字符串。 GC 不会 运行 直到一切都完成,所以你会被大量不同长度的字符串困住。
您需要其他组合字符串的方法。在这种情况下,您的整个函数可以写成 String.fromCharCode(...array)
(尽管如果您真的想从二进制数据创建一个字符串,您应该考虑使用 TextDecoder
代替,它支持各种编码,但需要注意的是在 Node.js).
更新: String.fromCharCode
似乎不适用于非常大的数组(任何函数的参数数量都有限制),因此您可以尝试将数组映射为 1 个字符的字符串,然后将它们连接在一起:
Array.prototype.map.call(uint8Array, c => String.fromCharCode(c)).join("")
(注意使用 Array.prototype.map
而不是 uint8Array.map
,因为后者会 运行 将您的结果转换为 Uint8)
我认为 TextDecoder
可能是正确的解决方案。但如果您坚持,您也可以尝试创建一个 blob,然后从中读取。
let blob = new Blob([arrayBuffer], {type: 'application/octet-stream'});
let reader = new FileReader();
reader.onload = function (event) {
console.log(event.target.result);
};
// Use if you want the UTF-8 encoded version
reader.readAsText(blob);
// Use if you for example need to use the result with "window.btoa" as it was in my case.
reader.readAsBinaryString(blob);