我正在对一个大数据集(在 Node.js 中)执行一些简单的数据验证(版本为 v7.5.0,矩阵大小为 15849x12771)。为了提高性能,整个数据集现在都存储在内存中。因此,我需要将所占用的内存量降到理论最低(JS 中每个数字代表 8 字节)。
请比较以下实现相同目标的不同方法。
使用
这会消耗我节点进程的所有内存,达到4GB+,导致它永远无法完成循环(直到我的耐心耗尽为止),我猜它将使用更慢的交换内存。以下是使用典型for循环的相同版本:
请比较以下实现相同目标的不同方法。
使用
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}]`);
}
}
}
这几乎不占用额外的内存,使验证在不到一秒钟的时间内完成。
这种行为是否符合预期?我原以为由于 forEach
具有闭合作用域,与传统的 for
循环相比,不会出现额外的内存使用问题。
编辑:独立测试
node --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
行中删除该引用,您的内存消耗会如何? - David Thomasfor
循环使用了额外的 0.3 兆字节,而forEach
使用了数千兆字节。 - Tom和
Object.keys.forEach`被认为是内存占用量大的函数。我甚至在node v7.6.0上尝试,但仍然没有足够的内存。我想V8未来可能会重写它们的实现方式,以防止将整个数组加载到内存中,而只是通过索引进行迭代。 - David Thomas