【问题标题】:How big are JavaScript function objects?JavaScript 函数对象有多大?
【发布时间】:2013-06-25 22:08:37
【问题描述】:

我只是想知道函数对象的开销如何。

在 OOP 设计模型中,您可以生成很多对象,每个对象都有自己的私有函数,但如果您有 10,000 多个,我认为这些私有函数对象会产生大量开销。

我想知道是否存在将这些函数移动到实用程序类或外部管理器以节省这些函数对象占用的内存的情况。

【问题讨论】:

  • js引擎可以回收相同的函数对象,所以用户态的10000个function vars实际上可以是1个函数和10000个指针...
  • 您打算使用实用程序类吗?它会占用更多内存……无论如何,这 a) 取决于引擎,b) 除非您看到它成为问题,否则可能不必担心。
  • 我的猜测是解释器对此进行了相当好的优化,但我确信它可能会写出糟糕的代码。有没有办法审计 JS 内存使用情况?
  • @landons: chrome 在开发工具中有一个任务管理器 + 配置文件 + 时间线来观察 JS ram 的使用情况。
  • @dandavis:您能否提供一个指向允许回收完整功能对象的规范的指针?这将使x = function(){...}; x.foo = []; 之类的东西成为禁忌,ECMA 文档对我来说就像加密文件一样可读。

标签: javascript performance


【解决方案1】:

这就是 Chrome 处理函数的方式,而其他引擎可能会做不同的事情。

让我们看看这段代码:

var funcs = [];
for (var i = 0; i < 1000; i++) {
    funcs.push(function f() {
        return 1;
    });
}
for (var i = 0; i < 1000; i++) {
    funcs[0]();
}

http://jsfiddle.net/7LS6B/4/

现在,引擎创建了 1000 个函数。

单个函数本身几乎不占用任何内存(在这种情况下为 36 个字节),因为它仅包含一个指向所谓的 SharedFunctionInfo 对象的指针,该对象基本上是对源代码中函数定义的引用* .这称为lazy parsing

只有当您频繁运行它时,JIT 才会启动,并创建函数的编译版本,这需要更多内存。所以,funcs[0] 最后占用了 256 个字节:

*) 这并不完全正确。它还包含作用域信息和函数名称and other metadata,这就是为什么在这种情况下它的大小为 592 字节的原因。

【讨论】:

  • JIT 编译的函数是否也共享(funcs[1] 运行相同的编译代码还是需要自己编译)?
  • @Bergi 好点。我不知道单次运行会发生什么。但是,如果您运行 funcs[1] a 1000 次(而 funcs[0] 的编译版本被缓存),则分析器显示 funcs[1] 正在添加对 funcs[0] 的引用(而不是直接指向编译版本)。不知道它是如何工作的,但看起来里面有一些魔法
  • 函数标识的 36 个字节(在 64 位节点下为 72 个)实际上很多,实际上是破坏交易的。它是普通对象开销的 3 倍(12 字节或 64 位上的 24 字节)。假设 node64,如果您创建 200 个对象,每个对象有 25 个方法,并且您每秒有 20 个请求,那么您每秒会创建约 7 兆字节的文字垃圾,并对 GC 施加巨大压力并浪费大量服务器潜力。当然,在客户端,如果你制作游戏或丰富的 GUI 应用程序,你会因为暂停而获得糟糕的用户体验。
【解决方案2】:

首先,将方法放在对象构造函数原型中是很常见的,因此它们将在给定对象的所有实例之间共享:

function MyObject() {
    ....
}

MyObject.prototype.do_this = function() {
   ...
}

MyObject.prototype.do_that = function() {
   ...
}

还要注意“函数对象”是一个常量代码块或闭包;在这两种情况下,大小都与代码无关:

x = [];
for (var i=0; i<1000; i++) {
    x.push(function(){ ... });
}

数组中每个元素的大小不取决于代码大小,因为代码本身将在所有函数对象实例之间共享。 1000 个实例中的每一个都需要一些内存,但它与字符串或数组等其他对象所需的内存量大致相同,并且与函数中存在多少代码无关。 p>

如果您使用 JavaScript 的 eval 创建函数,情况会有所不同:在这种情况下,我希望每个函数都占用相当多的时间并且与代码大小成正比,除非在这个级别也完成了一些超级智能缓存和共享.

【讨论】:

  • 您应该提到每个函数都需要保存对作用域变量的引用,因此根据这一点存在一些(微小)差异。
  • @Pumbaa80 - 不,函数对象不保存该信息,无论如何都可以根据需要延迟收集,因为关闭会阻止垃圾收集。
  • @Pumbaa80:在示例中,所有函数都将共享相同的封闭变量,因为 JavaScript 中的作用域是整个函数。例如for(var i=0; i&lt;1000; i++){ x.push((function(i){return function(){alert(i)}}))(i)); } 情况会有所不同,其中每个闭包都会引用不同的i 变量。
  • 更准确地说,我说的是Environment Record,它必须与函数一起存储。现在需要多少内存?其实,我不知道任何引擎的实现细节,但仔细想想,我猜可能只是指向某个Lexical Environment对象的指针(在这种情况下,函数之间没有区别)。跨度>
  • 我想一种简单的说法是:您的 JS 环境将有许多函数对象,这些函数对象的数量等于在您的代码中写入关键字 'function' 的次数(或者对于某些智能编译器,少于那个)
【解决方案3】:

函数对象实际上确实占用了大量空间。如下所示,对象本身可能不会占用太多空间,但 Function 对象似乎占用更多空间。为了测试这一点,我使用了Function("return 2;") 来创建一个匿名函数数组。

结果正如 OP 所暗示的那样。这些实际上确实占用了空间。

创建

创建的这些Function() 中的 100,000 个导致使用了 75.4 MB,从 0 开始。我在更受控的环境中运行了这个测试。这种转换更明显一点,它表明每个函数对象将消耗 754 个字节。而这些都是空的。较大的函数对象可能会超过 1kb,这将很快变得重要。在客户端上启动 75MB 并非易事,并导致 UI 锁定近 4 秒。

这是我用来创建函数对象的脚本:

fs = [];
for(var i = 0; i < 100000; i++ ){
 fs.push(Function("return 2;"));
}

调用这些函数也会影响内存级别。调用这些函数会额外增加 34MB 的内存使用量。

调用

这是我以前对他们的称呼:

for( var i = 0; i < fs.length; i++ ){
 for( var a = 0; a < 1000; a++ ){
     fs[i]();
 }
}

在编辑模式下使用 jsfiddle 很难得到准确的结果,我建议嵌入它。

Embedded jsFiddle Demo


这些陈述是不正确的,我留下它们是为了让 cmets 保留上下文。

函数对象根本不占用太多空间。可用的操作系统和内存将最终决定如何管理此内存。这不会真正影响您应该担心的规模。

在我的电脑上加载时,一个相对空白的 jsfiddle 消耗了 5.4MB 的内存。创建 100,000 个函数对象后,它跃升至 7.5MB。这似乎是每个函数对象的微不足道的内存量(意味着每个函数对象 21 个字节:7.5M-5.4M / 100k)。

jsFiddle Demo

【讨论】:

  • 并不是那么简单:函数对象使用的内存量会随着它们在任何 JITing VM 中编译而增加,可能分多个步骤(取决于是否同时保留多个编译副本)。
  • @gsnedders - “函数对象使用的内存量将在编译时增加”。你认为编译需要多长时间?为了简洁起见,我截取了上面的屏幕截图并对其进行了裁剪。它在相当长的一段时间内保持在 7.5MB 的相同水平,直到几分钟后它实际下降。它从未增加。您的评论有什么依据或证据?
  • 开始打电话给他们。在那个 Fiddle 中,你只有一个函数对象 (f)——你只需在它上面调用 [[Construct]] 100k 次,这将创建(非函数)对象。
  • @gsnedders - 感谢这些 cmets。尽管调用它们不会影响内存级别,但您对实例化函数(普通对象)和函数对象的区别肯定是正确的。请看我上面的编辑。
  • 你的回答只对lazy parsing的JS引擎有效
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-14
  • 2012-10-04
  • 2011-08-22
  • 2018-06-10
  • 1970-01-01
相关资源
最近更新 更多