当前位置: 首页 > 知识库问答 >
问题:

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

李飞翼
2023-03-14

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

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

该应用程序按照John Resig的Simple JavaScript继承的思路在“类”中构建。

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

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

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

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

共有3个答案

冯曾笑
2023-03-14

一般来说,你应该尽可能多的缓存,尽可能少的创建和销毁你的循环。

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

var options = {var1: value1, var2: value2, ChangingVariable: value3};
function loopfunc()
{
    //do something
}

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

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

会比这运行得快得多:

while(true)
{
    $.each(listofthings, function(){
        //do something on the list
    });

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

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

抱歉,如果这与你已经尝试和想到的相比有点微不足道。

李兴庆
2023-03-14

Chrome开发工具有一个很好的跟踪内存分配的功能。它被称为记忆时间线。本文描述了一些细节。我想这就是你所说的“锯齿”?这是大多数GC'ed运行时的正常行为。分配继续进行,直到达到使用阈值触发收集。通常在不同阈值下有不同类型的集合。

垃圾收集包括在与跟踪相关的事件列表中以及它们的持续时间。在我相当旧的笔记本上,短暂的收集发生在大约4Mb,需要30ms。这是60Hz循环迭代中的2次。如果这是一个动画,30ms的收集可能会导致口吃。您应该从这里开始查看您的环境中发生了什么:收集阈值在哪里以及收集需要多长时间。这为您评估优化提供了一个参考点。但是您可能不会比通过减慢分配率、延长集合之间的间隔来降低口吃频率更好。

下一步是使用配置文件|记录堆分配功能,用于按记录类型生成分配目录。这将快速显示哪些对象类型在跟踪期间消耗的内存最多,这相当于分配速率。按速率降序关注这些内容。

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

我建议不要在整个应用程序中歪曲您的正常编码风格,以尝试产生更少的垃圾。出于同样的原因,您不应该过早地优化速度。你的大部分努力加上代码增加的复杂性和晦涩难懂将毫无意义。

危宜
2023-03-14

在大多数其他场景中,你需要做很多事情来最小化GC变动,这违背了习惯性的JS,所以在判断我给出的建议时,请记住上下文。

分配发生在现代口译员的几个地方:

  1. 当您通过new或通过文字语法[…]{}创建对象时。
  2. 连接字符串时。
  3. 当您输入包含函数声明的范围时。
  4. 当您执行触发异常的操作时。
  5. 当您计算函数表达式时:(function(…){…})
  6. 当您执行强制对象的操作时,如Object(myNumber)Number.prototype.toString.call(42)
  7. 当你在引擎盖下调用一个内置程序时,比如Array.prototype.slice
  8. 当您使用参数反映参数列表时。
  9. 当您拆分字符串或与正则表达式匹配时。

避免这样做,并尽可能汇集和重用对象。

具体来说,寻找机会:

  1. 将对关闭状态没有依赖关系或依赖性很少的内部函数拉出到更高、寿命更长的范围内。(某些代码小程序(如闭包编译器)可以内联内部函数,并可能提高 GC 性能。
  2. 避免使用字符串来表示结构化数据或用于动态寻址。特别是避免使用拆分或正则表达式匹配项重复解析,因为每个匹配项都需要多个对象分配。这经常发生在查找表和动态 DOM 节点 ID 中的键上。例如,查找表 ['foo-' x]文档.get 元素ById(“foo-'x)都涉及分配,因为存在字符串串联。通常,您可以将密钥附加到长期存在的对象,而不是重新串联。根据您需要支持的浏览器,您可能能够使用 Map 直接将对象用作键。
  3. 避免在正常代码路径上捕获异常。而不是尝试 { op(x) } catch (e) { ... }, do if (!opCouldFailOn(x)) { op(x); } else { ... }.
  4. 当您无法避免创建字符串时,例如将消息传递到服务器,请使用像JSON.stringify这样的内置内容,它使用内部本机缓冲区来累积内容,而不是分配多个对象。
  5. 避免对高频事件使用回调,并在可能的情况下,将一个长期存在的函数(请参阅 1)作为回调传递,该函数从消息内容重新创建状态。
  6. 避免使用参数,因为使用函数在调用时必须创建类似数组的对象。

我建议使用< code>JSON.stringify来创建传出的网络消息。使用< code>JSON.parse解析输入消息显然涉及到分配,而且对于大消息来说需要大量的分配。如果您可以将传入的消息表示为原语数组,那么您可以节省大量分配。唯一可以用来构建不分配的解析器的内置代码是< code > string . prototype . charcode at 。一个复杂格式的解析器,它只使用了,但是读起来很糟糕。

 类似资料:
  • 问题内容: 我正在使用util模块调试nodejs应用程序,而 heapUsed 值保持在30-100MB左右,而 heapTotal 值增长到1.4GB。 我已经读到这是v8垃圾收集器的行为方式,但是问题是,例如在512 MB设备上运行时,如何减少其分配的内存量(使其小于1.4GB) 问题答案: 您需要控制最大内存大小标志(所有大小以MB为单位)。 对于“低内存设备”,建议的数量为: 适用于32

  • 我正在做一个与JVM GC相关的项目,我计划用我的手动GC取代JVM自动GC。 我知道JAVA有一个自动垃圾收集器。如果我们集成一个新的手动垃圾收集器,其中开发人员需要显式地调用new并删除对象(如在C中)。 让我们假设程序员在没有内存泄漏的情况下写入空闲。 使用手动垃圾回收机制代替自动垃圾回收机制是否有效? 在工业中使用手动GC是否常见?还是程序员到处使用自动垃圾回收器?

  • Java 15 使 ZGC、Z 垃圾收集器成为标准功能。它是 Java 15 之前的一个实验性功能。它是低延迟、高度可扩展的垃圾收集器。 ZGC 是在 Java 11 中作为一项实验性功能引入的,因为开发人员社区认为它太大而无法提前发布。 即使在机器学习应用程序等海量数据应用程序的情况下,ZGC 也具有高性能和高效工作。它确保在处理数据时不会因垃圾收集而长时间停顿。它支持 Linux、Window

  • Java 15 使 ZGC、Z 垃圾收集器成为标准功能。它是 Java 15 之前的一个实验性功能。它是低延迟、高度可扩展的垃圾收集器。 ZGC 是在 Java 11 中作为一项实验性功能引入的,因为开发人员社区认为它太大而无法提前发布。从那时起,对这个垃圾收集做了很多改进,例如 - 并发类卸载 取消提交未使用的内存 支持班级数据共享 NUMA 多线程堆Pre-touch 最大堆大小限制从 4 T

  • 问题内容: 我从带有node.js的线程垃圾收集中学到了node.js使用世代GC。 我通常使用循环对象引用(最终我都会删除/确保超出范围),并想知道node.js是否能很好地处理它们。所以例如。如果使用参考完成。计数,会有一个问题,所以我想知道这个节点有多好。 一些使用场景: 对于每个http请求,我创建一个带有lambda的setTimeout,该lambda可能引用了范围对象。作用域对象还引

  • Kubernetes 垃圾收集器的角色是删除指定的对象,这些对象曾经有但以后不再拥有 Owner 了。 注意:垃圾收集是 beta 特性,在 Kubernetes 1.4 及以上版本默认启用。 Owner 和 Dependent 一些 Kubernetes 对象是其它一些的 Owner。例如,一个 ReplicaSet 是一组 Pod 的 Owner。具有 Owner 的对象被称为是 Owner