Javascript 比较内存中的对象大小
Javascript compare object size in memory
我在 javascript 中有大约一百万行,我需要为每一行存储一个元数据对象。给定以下两种不同的对象类型:
{0: {'e', 0, 'v': 'This is a value'}
并且:
{0: '0This is a value'}
一百万个第一种类型的对象和一百万个第二种类型的对象在内存上有什么区别?即:
[obj1, obj1, obj1, ...] // array of 1M
[obj2, obj2, obj2, ...] // array of 1M
这里是 V8 开发人员。答案仍然是 "it depends",因为动态语言的引擎往往会适应你正在做的事情,所以一个小的测试用例很可能不能代表真实应用程序的行为。一个永远适用的 high-level 经验法则:单个字符串比 object 包装该字符串占用的内存更少。少多少?视情况而定。
也就是说,我可以针对您的具体示例给出具体的答案。对于以下代码:
const kCount = 1000000;
let a = new Array(kCount);
for (let i = 0; i < kCount; i++) {
// Version 1 (comment out the one or the other):
a[i] = {0: {'e': 0, 'v': 'This is a value'}};
// Version 2:
a[i] = {0: '0This is a value'};
}
gc();
运行 --expose-gc --trace-gc
,我看到了:
版本 1:244.5 MB
版本 2:206.4 MB
(接近当前的 V8,x64,d8
shell。@paulsm4 建议您可以自己在 DevTools 中执行此操作。)
明细如下:
- 数组本身每个条目需要 8 个字节
- 从 object 文字创建的 object 有一个 header 的 3 个指针,并为 4 个命名属性(此处未使用)预分配 space,总共 7 * 8 = 56 字节
- 其索引属性的后备存储为 17 个条目分配 space,即使只使用一个条目,加上 header 19 个指针 = 152 个字节
- 在版本 1 中,我们有一个内部 object 检测到需要两个(并且只有两个)命名属性,因此它被修剪为 5 的大小(3 header,2 "e" 和 "v") 指针 = 40 字节
- 在版本 2 中没有内部 object,只有一个指向字符串的指针
- 字符串字面量被去重,0 直接作为 "Smi" 存储在指针中,所以这些都不需要额外的 space.
总结:
版本 1:8+56+152+40 = 256 字节/object
版本 2:8+56+152 = 216 字节/object
但是,如果不是所有字符串都相同,如果 object 具有更多或更少的命名或索引属性,如果它们来自构造函数而不是文字,如果它们在其生命周期中增长或缩小,以及一系列其他因素。坦率地说,我不认为可以从这些数字中收集到任何特别有用的见解(具体来说,虽然它们看起来效率很低,但它们不太可能以这种方式在实践中发生——我敢打赌你实际上并没有存储那么多零,并将实际数据包装成 single-property {0: ...}
object 看起来也不现实)。
让我们看看!如果我从小测试中删除所有 obviously-redundant 信息,同时强制为每个条目创建一个新的、唯一的字符串,我将留下这个循环来填充数组:
for (let i = 0; i < kCount; i++) {
a[i] = i.toString();
}
总共只消耗 ~31 MB。更喜欢实际的 object 作为元数据?
function Metadata(e, v) {
this.e = e;
this.v = v;
}
for (let i = 0; i < kCount; i++) {
a[i] = new Metadata(i, i.toString());
}
现在我们大约有 69 MB。如您所见:巨大的变化 ;-)
因此,要确定您实际的完整应用程序的内存要求,以及它的任何实施备选方案,您必须自己进行测量。
我在 javascript 中有大约一百万行,我需要为每一行存储一个元数据对象。给定以下两种不同的对象类型:
{0: {'e', 0, 'v': 'This is a value'}
并且:
{0: '0This is a value'}
一百万个第一种类型的对象和一百万个第二种类型的对象在内存上有什么区别?即:
[obj1, obj1, obj1, ...] // array of 1M
[obj2, obj2, obj2, ...] // array of 1M
这里是 V8 开发人员。答案仍然是 "it depends",因为动态语言的引擎往往会适应你正在做的事情,所以一个小的测试用例很可能不能代表真实应用程序的行为。一个永远适用的 high-level 经验法则:单个字符串比 object 包装该字符串占用的内存更少。少多少?视情况而定。
也就是说,我可以针对您的具体示例给出具体的答案。对于以下代码:
const kCount = 1000000;
let a = new Array(kCount);
for (let i = 0; i < kCount; i++) {
// Version 1 (comment out the one or the other):
a[i] = {0: {'e': 0, 'v': 'This is a value'}};
// Version 2:
a[i] = {0: '0This is a value'};
}
gc();
运行 --expose-gc --trace-gc
,我看到了:
版本 1:244.5 MB
版本 2:206.4 MB
(接近当前的 V8,x64,d8
shell。@paulsm4 建议您可以自己在 DevTools 中执行此操作。)
明细如下:
- 数组本身每个条目需要 8 个字节
- 从 object 文字创建的 object 有一个 header 的 3 个指针,并为 4 个命名属性(此处未使用)预分配 space,总共 7 * 8 = 56 字节
- 其索引属性的后备存储为 17 个条目分配 space,即使只使用一个条目,加上 header 19 个指针 = 152 个字节
- 在版本 1 中,我们有一个内部 object 检测到需要两个(并且只有两个)命名属性,因此它被修剪为 5 的大小(3 header,2 "e" 和 "v") 指针 = 40 字节
- 在版本 2 中没有内部 object,只有一个指向字符串的指针
- 字符串字面量被去重,0 直接作为 "Smi" 存储在指针中,所以这些都不需要额外的 space.
总结:
版本 1:8+56+152+40 = 256 字节/object
版本 2:8+56+152 = 216 字节/object
但是,如果不是所有字符串都相同,如果 object 具有更多或更少的命名或索引属性,如果它们来自构造函数而不是文字,如果它们在其生命周期中增长或缩小,以及一系列其他因素。坦率地说,我不认为可以从这些数字中收集到任何特别有用的见解(具体来说,虽然它们看起来效率很低,但它们不太可能以这种方式在实践中发生——我敢打赌你实际上并没有存储那么多零,并将实际数据包装成 single-property {0: ...}
object 看起来也不现实)。
让我们看看!如果我从小测试中删除所有 obviously-redundant 信息,同时强制为每个条目创建一个新的、唯一的字符串,我将留下这个循环来填充数组:
for (let i = 0; i < kCount; i++) {
a[i] = i.toString();
}
总共只消耗 ~31 MB。更喜欢实际的 object 作为元数据?
function Metadata(e, v) {
this.e = e;
this.v = v;
}
for (let i = 0; i < kCount; i++) {
a[i] = new Metadata(i, i.toString());
}
现在我们大约有 69 MB。如您所见:巨大的变化 ;-)
因此,要确定您实际的完整应用程序的内存要求,以及它的任何实施备选方案,您必须自己进行测量。