首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >在Javascript中减少垃圾收集器活动的最佳实践

在Javascript中减少垃圾收集器活动的最佳实践
EN

Stack Overflow用户
提问于 2013-08-22 01:40:47
回答 4查看 39K关注 0票数 102

我有一个相当复杂的Javascript应用程序,它有一个主循环,每秒调用60次。似乎有很多垃圾回收正在进行(基于Chrome开发工具中内存时间线的“锯齿”输出)-这通常会影响应用程序的性能。

因此,我正在尝试研究减少垃圾收集器必须执行的工作量的最佳实践。(我在网上能找到的大多数信息都是关于避免内存泄漏的,这是一个稍微不同的问题-我的内存正在被释放,只是有太多的垃圾回收正在进行。)我假设这主要归结为尽可能多地重用对象,但当然,问题在于细节。

这个应用程序是按照John Resig's Simple JavaScript Inheritance的思路以“类”的形式构建的。

我认为一个问题是有些函数每秒可以被调用数千次(因为它们在主循环的每次迭代中被使用了数百次),也许这些函数中的局部工作变量(字符串、数组等)。可能就是问题所在。

我知道对象池用于更大/更重的对象(我们在一定程度上使用了这一点),但我正在寻找可以全面应用的技术,特别是与在紧密循环中被多次调用的函数相关的技术。

我可以使用哪些技术来减少垃圾收集器必须执行的工作量?

而且,也许还有-可以采用什么技术来识别哪些对象被垃圾收集得最多?(这是一个非常大的代码库,所以比较堆的快照并不是很有效)

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2013-08-24 04:25:37

您需要做的许多事情是为了最大限度地减少GC流失,这与在大多数其他场景中被认为是惯用JS的做法背道而驰,因此在判断我给出的建议时,请记住上下文。

在现代解释器中,分配发生在以下几个地方:

  1. 当你通过new或文字语法[...]创建对象时,你会连接字符串。
  2. 当你进入一个包含函数declarations.
  3. When的作用域时,你会执行一个触发函数表达式的操作,你会对函数表达式求值:(function (...) { ... }).
  4. When你会执行一个强制对象的操作,比如Object(myNumber)Number.prototype.toString.call(42)
  5. When你会调用一个在幕后执行这些操作的内置函数,与Array.prototype.slice.
  6. When类似,您可以使用arguments来反映参数列表。拆分字符串或与正则表达式匹配时使用

避免这样做,并在可能的情况下池化和重用对象。

具体地说,要寻找机会:

  1. 将没有或很少依赖于关闭状态的内部函数拉出到更高、更持久的作用域中。(一些代码缩略器,如Closure compiler,可以内联内部函数,并可能使用字符串表示结构化数据或用于动态寻址来改进GC performance.)
  2. Avoid。尤其要避免使用split或正则表达式匹配的重复解析,因为每个匹配都需要多个对象分配。这经常发生在查找表中的键和动态DOM节点ID中。例如,lookupTable['foo-' + x]document.getElementById('foo-' + x)都涉及分配,因为存在字符串连接。通常,您可以将键附加到长期对象,而不是重新连接。根据您需要支持的浏览器,您可能能够使用Map直接将对象用作键。
  3. 避免捕获正常代码路径上的异常。do if (!opCouldFailOn(x)) { op(x); } else { ... }.
  4. When你不能避免创建字符串,例如传递消息到服务器,使用像JSON.stringify这样的内置函数,它使用内部本地缓冲区来累积内容,而不是使用回调为高频事件分配多个content.
  5. Avoid,并且在可以的情况下,将一个长期函数(参见1)作为回调传递,该函数使用arguments从消息try { op(x) } catch (e) { ... }重新创建状态,因为使用该函数的函数在调用时必须创建一个类似数组的对象。

我建议使用JSON.stringify创建传出的网络消息。使用JSON.parse解析输入消息显然涉及到分配,其中很多用于大型消息。如果您可以将传入的消息表示为基元数组,那么您可以节省大量的分配。您可以围绕其构建不分配的解析器的唯一其他内置方法是String.prototype.charCodeAt。一个用于复杂格式的解析器,它只使用读起来会很糟糕的格式。

票数 136
EN

Stack Overflow用户

发布于 2013-08-30 11:11:29

Chrome developer tools有一个非常好特性来跟踪内存分配。它被称为记忆时间线。This article描述了一些细节。我想这就是你所说的“锯齿”吧?这是大多数GC运行时的正常行为。分配会一直进行,直到达到使用阈值,从而触发收集。通常,在不同的阈值下有不同类型的集合。

垃圾收集包括在与跟踪关联的事件列表中及其持续时间。在我相当旧的笔记本上,短暂的收集大约在4Mb发生,需要30ms。这是你的两次60 is循环迭代。如果这是一个动画,30毫秒的集合可能会导致卡顿。您应该从这里开始了解您的环境中发生了什么:收集阈值在哪里,收集花费了多长时间。这为您提供了评估优化的参考点。但是,通过降低分配率、延长收集间隔来减少卡顿的频率可能是最好的做法。

下一步是使用Profiles | Record Heap Allocations特性来按记录类型生成分配目录。这将快速显示跟踪期间哪些对象类型消耗的内存最多,这相当于分配率。按照速率的降序关注这些内容。

这些技术不是火箭科学。当你可以使用未装箱的对象时,避免装箱的对象。使用全局变量来保存和重用单个装箱的对象,而不是在每次迭代中分配新的对象。在空闲列表中汇集公共对象类型,而不是放弃它们。缓存字符串连接结果,这些结果可能在将来的迭代中重复使用。通过在封闭作用域中设置变量,避免仅仅为了返回函数结果而进行分配。您必须在其各自的上下文中考虑每种对象类型,以找到最佳策略。如果你需要关于细节的帮助,发表一篇编辑,描述你正在查看的挑战的细节。

我建议不要在整个应用程序中为了产生更少的垃圾而破坏正常的编码风格。这是出于同样的原因,您不应该过早地优化速度。你的大部分努力加上代码的复杂性和模糊性将是毫无意义的。

票数 13
EN

Stack Overflow用户

发布于 2013-08-22 02:12:34

一般来说,你会希望尽可能多地缓存,并在每次循环运行时尽可能少地创建和销毁。

我想到的第一件事就是减少在主循环中使用匿名函数(如果你有的话)。此外,很容易陷入创建和销毁传递给其他函数的对象的陷阱。我绝对不是javascript专家,但我可以想象一下:

代码语言:javascript
复制
var options = {var1: value1, var2: value2, ChangingVariable: value3};
function loopfunc()
{
    //do something
}

while(true)
{
    $.each(listofthings, loopfunc);

    options.ChangingVariable = newvalue;
    someOtherFunction(options);
}

会运行得比这个快得多:

代码语言:javascript
复制
while(true)
{
    $.each(listofthings, function(){
        //do something on the list
    });

    someOtherFunction({
        var1: value1,
        var2: value2,
        ChangingVariable: newvalue
    });
}

你的程序有没有宕机?也许你需要它流畅地运行一两秒钟(例如,对于动画),然后它就有更多的时间来处理?如果是这种情况,我可以看到在整个动画中获取通常会被垃圾回收的对象,并在某个全局对象中保留对它们的引用。然后,当动画结束时,您可以清除所有引用,并让垃圾收集器来完成它的工作。

很抱歉,与您已经尝试和想到的内容相比,这一切都有些微不足道。

票数 9
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/18364175

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档