JavaScript类型的内存分配

7

我正在尝试优化一个移动应用程序,并且想知道哪些内容占用了最小的内存空间(我意识到这可能因浏览器而异):

  • 对象指针
  • 布尔文本
  • 数字文本
  • 字符串文本

理论上哪个应该占用最少的内存空间?


1
我的猜测是一个空字符串字面量。 - Niet the Dark Absol
4
@DesertIvy 这仅用于带宽优化。 - Daniel A. White
3
"Optimize" 是一个相对的术语。在字面量上节省几个字节不会节省太多内存(除非你有很多这样的字面量),但如果解释器必须将值转换为其他形式,则会增加执行时间。 - user1864610
1
被踩了3次赞?看来普通的JavaScript开发者对浏览器内部工作原理没有多少好奇心,哇。 - user2800679
2
我认为投反对票的人应该添加评论,以帮助提问者改进这个问题。在我看来,这听起来很有前途,但可能需要更多具体细节。例如,哪些部分需要优化?提问者是否已经对应用程序进行了分析,确定了内存占用确实是问题所在?@user2800679,您如何知道需要缩小对象以改善性能,而不是采取其他方法?您正在尝试构建什么样的应用程序? - Jay
显示剩余6条评论
2个回答

19

在 V8 引擎中:

布尔值、数字、字符串、null 和 void 0 字面量占用常量 4/8 字节的内存,用于指针或嵌入在指针中的立即整数值。但是,对于这些字面量根本没有堆分配,因为字符串字面量只会被内部化。异常情况包括大整数或双精度浮点数,它们将被装箱处理,并使用 4/8 字节作为盒子指针和 12-16 字节作为盒子的大小。在优化代码中,局部双精度浮点数可以保持未装箱状态存储在寄存器或堆栈中,或者一个仅包含双精度浮点数的数组将以未装箱状态存储它们。

考虑生成代码的核心部分

function weird(d) {
    var a = "foo";
    var b = "bar";
    var c = "quz";

    if( d ) {
        sideEffects(a, b, c);
    }
}

正如您所看到的,字符串的指针是硬编码的,没有进行任何分配。

普通对象的最小标识需要12/24字节,数组需要16/32字节,函数需要32/72字节(如果需要分配上下文对象,则需要额外约30/60字节)。只有在运行最新版本的v8并且标识不会逃逸到无法内联的函数中,才能在此处避免堆分配。

例如:

function arr() {
    return [1,2,3]
}

函数返回的所有数组将共享值为1、2、3的支持数组作为写时复制数组,但每个数组仍需要分配唯一标识对象。查看生成的代码可以看到这是多么复杂的。因此,即使使用了此优化,如果您不需要数组的唯一标识,只需从上层作用域返回一个数组,就可以避免在每次调用函数时为标识分配内存:

var a = [1,2,3];
function arr() {
    return a;
}

简单得多。

如果您在不做任何看起来疯狂的事情时遇到JavaScript内存问题,那么您肯定正在动态创建函数。将所有函数提升到一个不需要重新创建它们的级别。正如您从上面可以看到的那样,仅函数的标识本身就非常庞大,考虑到大多数代码可以通过利用 this 来使用静态函数。

因此,如果您想从中获益,请避免使用非IIFE闭包,如果您的目标是性能。任何显示它们不是问题的基准测试都是错误的基准测试。

您可能会有直觉,即当您拥有8GB时,额外的内存使用情况并不重要。嗯,在C语言中这并不重要。但是在JavaScript中,内存不仅仅停留在那里,而是被垃圾回收器跟踪。越多的内存和对象停留在那里,性能就越差。

只需考虑运行类似以下内容的东西:

var l = 1024 * 1024 * 2
var a = new Array(l);

for( var i = 0, len = a.length; i < len; ++i ) {
    a[i] = function(){};
}

使用--trace_gc --trace_gc_verbose --print_cumulative_gc_stat参数。 看看为了什么而做了这么多工作。

与静态函数进行比较:

var l = 1024 * 1024 * 2
var a = new Array(l);
var fn = function(){};

for( var i = 0, len = a.length; i < len; ++i ) {
    a[i] = fn;
}

太棒了。这正是我所希望得到的信息;不过,我有一个关于你最后一段代码的问题。如果我调用bind: a[i] = fn.bind(ref); (我知道bind很慢) 与以下代码相比,这会对垃圾回收有所帮助吗? a[i] = function() {}; 我猜想不会,但如果你有什么想法,请告诉我。无论如何,非常感谢你详细的答复。 - user2800679
@user2800679 不,.bind 每次都会返回一个新的函数,除非现在你还创建了一个上下文对象、一个 FixedArray 对象和大量其他垃圾(请参见 https://github.com/v8/v8/blob/ea835a25d9e53c7f1104f019b18fd0bfc69f1d3b/src/v8natives.js#L1727 和 https://github.com/v8/v8/blob/4d940b47fd46b1b4b1fcd50a2d34c74e1b320af4/src/runtime.cc#L8058)。如果您需要 bind 的功能,请使用自定义函数而不是内置函数:https://dev59.com/0WMk5IYBdhLWcg3w3xnq#18909668。 - Esailija

0
“Literal” 意味着代码(即使不在字符串序列化中),这是一种更复杂的类型,因此需要比 更多的空间。
理论上,布尔值可能占用最少的空间,因为它们适合单个位。但是,任何引擎都不太可能优化这一点。如果您想强制执行此操作,可以手动执行并使用类型化数组进行操作。
然而,性能是一个实际的问题,您只能测试、测试和测试。正如您已经知道的那样,没有明确的跨浏览器跨版本答案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接