node.js - 在异步函数中迭代 Promise 时内存使用量显着增加

标签 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 }

最佳答案

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

在您的示例代码中,会发生很多小对象在堆上分配的情况。这反射(reflect)在 heapUsed 中,并且是代码正在使用的实际内存量。当堆填满时,将执行一轮垃圾收集 (GC),以释放空间。因此,如果您在增加 i 时绘制 heapUsed,那么您会看到它不断上升,直到 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

不同的 Node 版本

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

heap size plotted against number of iterations for different node versions

正如您所看到的,v8.2.0 之前的版本和后续版本的 Node.js 的内存配置文件存在巨大差异。如果我们去看看change log对于 v8.3.0,我们明白原因了!

The V8 engine has been upgraded to version 6.0, which has a significantly changed performance profile

这是包含 Turbofan 的 V8 版本它实现了 Node 海,并包括许多 GC 性能增强。

Thorsten Lorenz 的 v8-perf 中提供了有关 V8 GC 如何工作的更深入的 View 。 repo 协议(protocol)。

关于node.js - 在异步函数中迭代 Promise 时内存使用量显着增加,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55241904/

相关文章:

javascript - Node.js 集群示例

c - 修复内存泄漏

javascript - AngularJS $q promise 不会按预期工作

javascript - 使用 Promise.all() 实现 promise 时执行操作

node.js - 由于 "AuthorizationError",未捕获来自 Passport-facebook 的服务器错误

sockets - 如何仅使用 Net 模块(而非 socket.io)向 node.js 中的单个客户端发送消息

c++ - Windows中是否有任何内存虚拟文件API?

javascript - 可能未处理的 promise 拒绝(id :0) Warning

mysql - 如何使我的 Node.js MySQL 连接成为 promise 工作?

performance - 为什么嵌套的 MaybeT 会导致指数分配