提问人:ThaiSon Nguyen 提问时间:6/10/2022 最后编辑:ThaiSon Nguyen 更新时间:6/25/2022 访问量:1043
NodeJS Promise 消耗太多内存?
NodeJS Promises consume too much memory?
问:
我正在尝试分析 NodeJS 在处理异步函数方面的有效性。
我有下面的 NodeJS 脚本来启动 10 数百万个 Promise,这些 Promise 将休眠 2 秒以模拟密集的后端 API 调用。脚本运行了一段时间(~30 秒),消耗了多达 4096 MB 的内存并引发了错误。JavaScript heap out of memory
- 承诺真的会消耗那么多内存吗?
- 为什么 NodeJS 在使用太多内存时应该适合 I/O 密集型操作?
- Golang 只用 10MB 的内存来处理 100 数百万个 Go Routine,Golang 在处理 I/O 密集型操作方面是否比 NodeJS 更好?
const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const fakeAPICall = async (i) => {
await sleep(2000);
return i;
};
const NUM_OF_EXECUTIONS = 1e7;
console.time(`${NUM_OF_EXECUTIONS} executions:`);
[...Array(NUM_OF_EXECUTIONS).keys()].forEach((i) => {
fakeAPICall(i).then((r) => {
if (r === NUM_OF_EXECUTIONS - 1) {
console.timeEnd(`${NUM_OF_EXECUTIONS} executions:`);
}
});
});
错误
<--- Last few GCs --->
[41215:0x10281b000] 36071 ms: Mark-sweep (reduce) 4095.5 (4100.9) -> 4095.3 (4105.7) MB, 5864.0 / 0.0 ms (+ 1.3 ms in 2767 steps since start of marking, biggest step 0.0 ms, walltime since start of marking 7190 ms) (average mu = 0.296, current mu = 0.[41215:0x10281b000] 44534 ms: Mark-sweep (reduce) 4096.3 (4104.7) -> 4096.3 (4105.7) MB, 8461.4 / 0.0 ms (average mu = 0.140, current mu = 0.000) allocation failure scavenge might not succeed
<--- JS stacktrace --->
FATAL ERROR: MarkCompactCollector: young object promotion failed Allocation failed - JavaScript heap out of memory
1: 0x100098870 node::Abort() [/usr/local/opt/node@14/bin/node]
2: 0x1000989eb node::OnFatalError(char const*, char const*) [/usr/local/opt/node@14/bin/node]
3: 0x1001a6d55 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/usr/local/opt/node@14/bin/node]
4: 0x1001a6cff v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/usr/local/opt/node@14/bin/node]
5: 0x1002dea5b v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/usr/local/opt/node@14/bin/node]
6: 0x100316819 v8::internal::EvacuateNewSpaceVisitor::Visit(v8::internal::HeapObject, int) [/usr/local/opt/node@14/bin/node]
答:
1赞
Dmitriy Mozgovoy
6/25/2022
#1
Nodejs 有一个默认的内存限制,可以使用 NODE 选项进行更改;--max_old_space_size=<memory in MB>
我有下面的 NodeJS 脚本来启动 10 数百万个 Promise
甚至没有接近。大约有5000万。
const sleep = async (ms) => { // redundant async - Promise#1
return new Promise((resolve) => setTimeout(resolve, ms)); // Promise#2
}
const fakeAPICall = async (i) => { // async - Promise#3
await sleep(2000); // await - Promise#4
return i;
};
const NUM_OF_EXECUTIONS = 1e7;
console.time(`${NUM_OF_EXECUTIONS} executions:`);
for (let i = 0; i < NUM_OF_EXECUTIONS; i++) {
fakeAPICall(i).then((r) => { // then - Promise#5
if (r === NUM_OF_EXECUTIONS - 1) {
console.timeEnd(`${NUM_OF_EXECUTIONS} executions:`);
}
});
}
在每次迭代中,您实际上至少创建了 5 个 promise 和一个生成器,因此您在内存中有 5000 万个 promise 和大量其他对象。这是很多的,因为它们是用JS编写的纯JS对象,当然,它们比低级预编译语言消耗更多的内存。节点与低内存消耗无关,但内存成为您案例中的瓶颈。 承诺易于使用,如果您需要内存优化 - 纯回调可以更便宜。
在这里,我们创建了 10M 的承诺:
const NUM_OF_EXECUTIONS = 5_000_000;
console.log(`Start `, NUM_OF_EXECUTIONS);
const sleep = (ms, i) => new Promise((resolve) => setTimeout(resolve, ms, I)); // Promise#1
console.time(`${NUM_OF_EXECUTIONS} executions`);
for (let i = 0; i < NUM_OF_EXECUTIONS; i++) {
sleep(2000, i).then((r) => { // then - Promise#2
if (r === NUM_OF_EXECUTIONS - 1) {
console.timeEnd(`${NUM_OF_EXECUTIONS} executions`);
}
});
}
内存 (2.6 GB):
Start 5000000
{
rss: '2.72 GB',
heapTotal: '2.68 GB',
heapUsed: '2.6 GB',
external: '308 kB',
arrayBuffers: '10.4 kB'
}
5000000 executions: 24.776s
Process finished with exit code 0
评论
0赞
Brian Takita
4/25/2023
我使用 nodejs v20 运行了这个并得到了一个 OOM 错误...... # # MemoryChunk 分配中的致命 javascript OOM 在反序列化期间失败。#
0赞
Brian Takita
4/25/2023
v19 有一个致命错误:达到堆限制分配失败 - JavaScript 堆内存不足
0赞
Dmitriy Mozgovoy
4/25/2023
您尚未更改默认内存限制。第一个脚本最多需要 10GB 的 RAM 限制。通过添加 --max_old_space_size=10000 启动参数来增加内存限制,或者通过设置 NUM_OF_EXECUTIONS = 1_000_000 来减少迭代次数
评论