【问题标题】:A significant increased in memory usage when iterating Promises in an async function在异步函数中迭代 Promise 时内存使用量显着增加
【发布时间】:2019-03-19 13:11:34
【问题描述】:

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

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 }

【问题讨论】:

  • 两个屏幕截图是一样的吗?
  • 我提供了 2 个新的屏幕截图,更清楚地显示了显着增加。这两个屏幕截图显示了在 nodejs7.9 和 nodejs8.3 中运行的结果。无论我尝试多少次,RSS 跳转都会发生在同一个版本中的相同计数处。
  • 这是你的程序的范围吗?或者在这个循环的同时还有其他东西在运行吗?如果还有更多,你能把它贴出来(或者如果它太大的话,请描述它)?
  • 只需复制粘贴代码并在节点中运行,然后观察 RSS。

标签: node.js memory promise


【解决方案1】:

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

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

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

您可以清楚地看到,在 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

不同的节点版本

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

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

V8 引擎已升级到 6.0 版,性能配置文件发生了显着变化

这是包含 Turbofan 的 V8 版本,它实现了一个节点海,并为 GC 提供了许多性能增强。

Thorsten Lorenz 的 v8-perf 存储库中提供了有关 V8 GC 工作原理的更深入的视图。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-13
    • 2018-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-28
    • 2019-10-22
    相关资源
    最近更新 更多