Tom*_*Tom 8 javascript memory arrays optimization v8
我正在对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}]`);
}
});
});
Run Code Online (Sandbox Code Playgroud)
这消耗了我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}]`);
}
}
}
Run Code Online (Sandbox Code Playgroud)
这几乎不消耗额外的内存,导致验证在不到一秒的时间内完成.
这种行为是否符合预期?我曾预料到,由于forEachs已关闭范围,因此与传统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}]`);
}
});
});
};
Run Code Online (Sandbox Code Playgroud)
输出:
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
Run Code Online (Sandbox Code Playgroud)
(V8开发人员在这里.)这是如何Array.forEach在V8的旧执行管道(完全代码+ Crankshaft)中实现的不幸结果.简而言之,在某些情况下,使用forEach数组会将该数组的内部表示更改为内存效率更低的格式.(具体来说:如果数组之前只包含双值,并且forEach已经在具有其他类型元素但没有太多不同类型对象的数组上使用,并且代码运行得足够热以进行优化.它相当复杂;-))
使用新的执行管道(当前在--future标志后面,默认情况下将很快打开),我不再看到额外的内存消耗.
(也就是说,经典for循环确实往往具有较小的性能优势forEach,仅仅因为在引擎盖下(根据ES规范)更少.在许多实际工作负载中,差异太小而无关紧要,但在微基准测试中,它通常是可见的我们未来可能能够优化掉更多forEach的开销,但是如果你知道每个CPU周期都很重要,我建议使用普通的旧for (var i = 0; i < array.length; i++)循环.)
| 归档时间: |
|
| 查看次数: |
1442 次 |
| 最近记录: |