在像 V8 这样的 JavaScript 引擎中,对象是否比返回它的函数消耗更多的内存?

In a JavaScript engine like V8, does an object consume more memory than a function that returns it?

提问人:Steve Clay 提问时间:5/4/2023 最后编辑:Steve Clay 更新时间:5/9/2023 访问量:87

问:

假设我有一个不平凡的静态数据结构:

[
  { id: 1, title: 'Long title 1...' },
  { id: 2, title: 'Long title 2...' },
  /* 998 more... */
]

考虑到我的应用程序很少需要映射/过滤它(我假设将模块保留在内存中),它是否会消耗更少的内存来 (A) 将其导出为 const:

export const rows = [ ... ];

或者 (B),导出一个返回文本的函数:

export function getRows() {
  return [ ... ];
}

或者 (C),导出一个 JSON.parse 字符串文字的函数?

export function getRows() {
  return JSON.parse("[ ... ]");
}

需要明确的是,在页面生命周期的长度内,它对浏览器选项卡的 RAM 消耗的贡献。在不频繁操作期间,RAM 使用率的瞬时峰值不太令人担忧。

我知道在(B)和(C)中,每次调用都可能会产生一些对象构造时间,但这并不像占用RAM那样令人担忧。我知道 (C) 延迟了文字的解析时间,但目前尚不清楚解析后的表示是否比其 JSON 字符串文字消耗更多的 RAM。

JavaScript 内存 v8 字符串 对象文字

评论

2赞 Pointy 5/4/2023
使用(第三个选项)真的很傻。在前两者之间,基本上没有区别,尽管为什么你不想要第一个的简单性是一个谜。JSON.parse()
2赞 Barmar 5/4/2023
该函数包含它将返回的对象。因此,它必须使用比对象字面量本身更多的内存。
2赞 Phil 5/4/2023
您的性能分析调试告诉您什么?
1赞 Barmar 5/4/2023
如果它不包含它,它怎么能归还它?它可能不包含对象本身,但它包含构造对象的代码,该对象必须包含至少与对象一样多的内存。
1赞 Barmar 5/4/2023
@SteveClay我不是在谈论源代码,而是在谈论编译源代码的结果。

答:

1赞 Steve Clay 5/5/2023 #1

简短的回答可能是“是”。从我在此存储库中执行的测试来看,“获胜者”是 (C) 长期内存消耗最少,(B) 消耗两倍内存,(A) 内存消耗 4 倍

当然,这将取决于数据的大小和形状。在这种情况下:

模块导出 磁盘上的大小 (b) 30 秒后添加到“heapUsed”的字节数
C 使用 JSON.parse() 的函数 1,400,667 1,934,336
B 返回文本的函数 1,379,845 4,422,656
一个 常量 1,379,828 9,018,368

每个模块都单独测试,首先捕获 的输出,然后动态加载数据模块,访问数据,然后等待 10 秒和 30 秒加载后重新测量内存使用情况(不允许导入的资源超出范围)。在此电子表格中可以看到 3 次运行的结果(这些值是从基线调用到 的增量,以 kB 为单位)。memoryUsage()memoryUsage()

我怀疑正在发生的事情是,源代码中的单个大型 JSON 字符串可以存储在连续的 RAM 中,而其他两种表示形式需要更多的单个对象和昂贵的“边缘”,因为缺乏更好的术语。令人惊讶的是,构造的变量占用的内存是函数的编译表示的两倍,该函数将对象作为文字返回!

但也许这并不奇怪:我假设文字的 AST 不能被 GC 编辑,无论它是常量还是函数,所以当模块在范围内时,你总是在任何可变存储之上支付内存成本。在所有条件相同的情况下,尽量在堆上存储尽可能少的存储时间。

评论

0赞 Steve Clay 5/5/2023
现在有一个 repo,我应该尝试一个更小的对象以及一个与问题中类似的对象数组。
1赞 jmrk 5/10/2023
对于某些对象图(特别是许多相互指向的小对象),JSON 表示形式小于对象本身的内存大小,这是有道理的。不过,并非所有对象图都是如此,这取决于您的特定数据。在您的结果中,我发现令人惊讶的是 B 比 C 占用的内存更少,这可能是一个测量伪影。您可以尝试显式触发 GC 循环(使用 和 ),而不是等待 20 秒。node --expose-gcgc();