【发布时间】:2017-07-17 00:22:23
【问题描述】:
我正在对 Node.js(版本 v7.5.0,包含 15849x12771 个条目的矩阵)中的大量数据执行一些简单的数据验证。出于性能原因,现在整个数据集都在内存中。因此,将消耗的内存量减少到理论上的最小值(每个数字在 JS 中代表 8 个字节)对我来说至关重要。
请比较以下实现相同目标的方法。
forEach
regressData.forEach((yxa, yxaIndex) => {
yxa.forEach((yx, yxIndex) => {
if (!_.isFinite(yx)) {
throw new Error(`non-finite entry at [${yxaIndex}, ${yxIndex}]`);
}
});
});
这会消耗我所有节点进程的 4GB+ 内存,导致它永远不会(直到我的耐心耗尽)完成循环(我猜它会使用较慢的交换内存)。
然后是与典型for相同的版本:
for (var yxai = 0, yxal = regressData.length; yxai < yxal; yxai++) {
const yx = regressData[yxai];
for (var yxi = 0, yxl = yx.length; yxi < yxl; yxi++) {
if (!_.isFinite(yx[yxi])) {
throw new Error(`non-finite entry at [${yxai}, ${yxi}]`);
}
}
}
这几乎不会消耗额外的内存,从而在不到一秒的时间内完成验证。
这种行为是否符合预期?我曾预料到,由于 forEachs 具有封闭范围,因此与传统的 for 循环相比,不会出现额外内存使用的问题。
编辑:独立测试
节点 --expose-gc test_foreach.js
if (!gc) throw new Error('please run node like node --expose-gc test_foreach.js');
const _ = require('lodash');
// prepare data to work with
const x = 15849;
const y = 12771;
let regressData = new Array(x);
for (var i = 0; i < x; i++) {
regressData[i] = new Array(y);
for (var j = 0; j < y; j++) {
regressData[i][j] = _.random(true);
}
}
// for loop
gc();
const mb_pre_for = _.round(process.memoryUsage().heapUsed / 1024 / 1024, 2);
console.log(`memory consumption before for loop ${mb_pre_for} megabyte`);
validateFor(regressData);
gc();
const mb_post_for = _.round(process.memoryUsage().heapUsed / 1024 / 1024, 2);
const mb_for = _.round(mb_post_for - mb_pre_for, 2);
console.log(`memory consumption by for loop ${mb_for} megabyte`);
// for each loop
gc();
const mb_pre_foreach = _.round(process.memoryUsage().heapUsed / 1024 / 1024, 2);
console.log(`memory consumption before foreach loop ${mb_pre_foreach} megabyte`);
validateForEach(regressData);
gc();
const mb_post_foreach = _.round(process.memoryUsage().heapUsed / 1024 / 1024, 2);
const mb_foreach = _.round(mb_post_foreach - mb_pre_foreach, 2);
console.log(`memory consumption by foreach loop ${mb_foreach} megabyte`);
function validateFor(regressData) {
for (var yxai = 0, yxal = regressData.length; yxai < yxal; yxai++) {
const yx = regressData[yxai];
for (var yxi = 0, yxl = yx.length; yxi < yxl; yxi++) {
if (!_.isFinite(yx[yxi])) {
throw new Error(`non-finite entry at [${yxai}, ${yxi}]`);
}
}
}
};
function validateForEach(regressData) {
regressData.forEach((yxa, yxaIndex) => {
yxa.forEach((yx, yxIndex) => {
if (!_.isFinite(yx)) {
throw new Error(`non-finite entry at [${yxaIndex}, ${yxIndex}]`);
}
});
});
};
输出:
toms-mbp-2:mem_test tommedema$ node --expose-gc test_foreach.js
memory consumption before for loop 1549.31 megabyte
memory consumption by for loop 0.31 megabyte
memory consumption before foreach loop 1549.66 megabyte
memory consumption by foreach loop 3087.9 megabyte
【问题讨论】:
-
在您的第一个示例中,您在内部循环中引用
yxaIndex。如果您在new Error行中删除该引用,您的内存消耗情况如何。 -
这似乎很奇怪......你能用一个预定义的数据集和一个 isFinite(虚拟)函数让它可重现,这样就可以独立运行了吗?
-
“没有额外的内存”,你的硬数字是什么意思?
-
@CFrei 我已经用独立的可运行测试更新了这个问题。 @Bergi 看测试,
for循环使用了 0.3 兆字节,而forEach使用了几千兆字节。 -
this question 中的答案可能会对您有所帮助。似乎
for ... in和Object.keys.forEach是众所周知的记忆猪。我什至尝试使用节点 v7.6.0,但它仍然内存不足。我想 V8 将来可能会重写他们对这些函数的实现,以阻止他们将完整的数组加载到内存中,而不是仅仅用索引遍历它们。
标签: javascript arrays memory optimization v8