在异步函数中迭代Promises时,内存使用量显着增加

时间:2019-03-19 13:11:35

标签: node.js memory promise

在以下代码中,我试图找出导致内存使用量显着增加的原因。

async function a () {
    for (let i = 0; i < 10000000000; i++) {
        await new Promise(resolve => {
            if (i%100000 === 0) {
                console.log(i)
                console.log(process.memoryUsage())
            }
            resolve(i)
        })
    }
}
a()

在代码运行时,这种内存使用量跳跃会发生多次,并且总是在i达到一定数字时发生。

在7.9.0中,总是从2000000-> 2100000出现

1700000
{ rss: 20135936, heapTotal: 9355264, heapUsed: 6003256, external: 8772 }
1800000
{ rss: 19836928, heapTotal: 9355264, heapUsed: 4490432, external: 8772 }
1900000
{ rss: 19316736, heapTotal: 9355264, heapUsed: 5039992, external: 8772 }
<-- Jump happens between here -->
2000000
{ rss: 19357696, heapTotal: 9355264, heapUsed: 5587808, external: 8772 }
2100000
{ rss: 23605248, heapTotal: 13549568, heapUsed: 6088208, external: 8772 }
<-- and here -->
2200000
{ rss: 23601152, heapTotal: 13549568, heapUsed: 6586000, external: 8772 }
2300000
{ rss: 23568384, heapTotal: 13549568, heapUsed: 7083112, external: 8772 }

在8.3.0中,总是从2600000-> 2700000出现

2400000
{ rss: 30507008, heapTotal: 9437184, heapUsed: 4785896, external: 8252 }
2500000
{ rss: 30523392, heapTotal: 9437184, heapUsed: 4710912, external: 8252 }
<-- Jump happens between here -->
2600000
{ rss: 30539776, heapTotal: 9437184, heapUsed: 4636176, external: 8252 }
2700000
{ rss: 34742272, heapTotal: 13631488, heapUsed: 6606512, external: 8252 }
<-- and here -->
2800000
{ rss: 34750464, heapTotal: 13631488, heapUsed: 8571208, external: 8252 }
2900000
{ rss: 34758656, heapTotal: 13631488, heapUsed: 6412304, external: 8252 }

1 个答案:

答案 0 :(得分:2)

在V8(Node.js使用的JS运行时)中,为堆预分配了一定数量的大小。这就是您看到的heapTotal。当V8怀疑您将需要更多空间时,它将增加堆的总大小。

在您的示例代码中,发生的事情是在堆上分配了许多小对象。这反映在heapUsed中,它是代码正在使用的实际内存量。当堆填满时,将执行一轮垃圾回收(GC),以释放空间。因此,如果您在增加heapUsed的同时绘制i,那么您会看到它不断上升,直到GC启动并且它又下降了。

事实上,这正是我非常长期以来所做的!

Heap usage for 10 billion iterations

您可以清楚地看到,在GC启动之前,绝不允许堆变大。

要进一步验证这一点,如果我们使用node --expose_gc

运行以下命令,则可以手动在代码中触发GC
async function run() {
    for (let i = 0; i < 10000000000; i++) {
        await new Promise(async resolve => {
            if (i % 10000000 === 0) {
                global.gc();
                console.log(`${i}, ${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)}mb`);
            }
            resolve();
        });
    }
}
run();

您将在v7.9.0上获得以下输出

0, 3.12mb
10000000, 2.77mb
20000000, 2.78mb
30000000, 2.78mb
40000000, 2.78mb
50000000, 2.78mb
60000000, 2.78mb

不同的节点版本

非常有趣的是,如果我们在不同版本的节点上运行测试!

heap size plotted against number of iterations for different node versions

如您所见,v8.2.0之前的版本和后续版本的node.js的内存配置文件存在巨大差异。如果我们去看看v8.3.0的change log,我们会明白为什么!

  

V8引擎已升级到版本6.0,该版本的性能概况已大大改变

这是V8的版本,其中包括Turbofan,该版本实现了节点之多,并且为GC提供了许多性能增强。

在Thorsten Lorenz的v8-perf回购中可以更深入地了解V8 GC的工作原理。