我有一个相当复杂的 Javascript 应用程序,它有一个每秒调用 60 次的主循环.似乎正在进行大量垃圾收集(基于 Chrome 开发工具中内存时间线的锯齿"输出)——这通常会影响应用程序的性能.
因此,我正在尝试研究减少垃圾收集器必须完成的工作量的最佳实践.(我在网上找到的大部分信息都是关于避免内存泄漏,这是一个稍微不同的问题——我的内存正在被释放,只是垃圾收集太多了.)我假设这主要归结为尽可能重用对象,但细节当然是魔鬼.
该应用按照 John Resig 的简单 JavaScript 继承 的类"构造.p>
我认为一个问题是某些函数每秒可以调用数千次(因为它们在主循环的每次迭代中使用了数百次),可能还有这些函数中的局部工作变量(字符串、数组、等)可能是问题.
我知道用于更大/更重对象的对象池(我们在一定程度上使用它),但我正在寻找可以全面应用的技术,尤其是与被多次调用的函数相关的技术在紧密的循环中.
我可以使用哪些技术来减少垃圾收集器必须完成的工作量?
而且,也许还有 - 可以使用哪些技术来识别哪些对象被垃圾回收最多?(这是一个非常大的代码库,所以比较堆的快照并不是很有成果)
你需要做的很多事情来最小化 GC churn 在大多数其他场景中被认为是惯用的 JS,所以请记住上下文判断我给出的建议.
现代口译员的分配发生在几个地方:
new
或通过文字语法 [...]
或 {}
创建对象时.(function (...) { ... })
.Object(myNumber)
或 Number.prototype.toString.call(42)
Array.prototype.slice
.arguments
来反映参数列表时.避免这样做,并尽可能集中和重用对象.
特别是寻找机会:
split
或正则表达式匹配重复解析,因为每个都需要多个对象分配.这经常发生在查找表和动态 DOM 节点 ID 的键上.例如,lookupTable['foo-' + x]
和 document.getElementById('foo-' + x)
都涉及分配,因为存在字符串连接.通常,您可以将键附加到长期存在的对象而不是重新连接.根据您需要支持的浏览器,您也许可以使用 Map
直接使用对象作为键.try { op(x) } catch (e) { ... }
,执行 if (!opCouldFailOn(x)) { op(x);} else { ... }
.JSON.stringify
这样的内置函数,它使用内部本机缓冲区来累积内容而不是分配多个对象.arguments
,因为使用它的函数在调用时必须创建一个类似数组的对象.我建议使用 JSON.stringify
来创建传出网络消息.使用 JSON.parse
解析输入消息显然涉及分配,其中很多用于大消息.如果您可以将传入消息表示为原语数组,那么您可以节省大量分配.String.prototype.charCodeAt
是您可以构建不分配解析器的唯一其他内置函数.一个复杂格式的解析器只使用它,但阅读起来会很糟糕.
I have a fairly complex Javascript app, which has a main loop that is called 60 times per second. There seems to be a lot of garbage collection going on (based on the 'sawtooth' output from the Memory timeline in the Chrome dev tools) - and this often impacts the performance of the application.
So, I'm trying to research best practices for reducing the amount of work that the garbage collector has to do. (Most of the information I've been able to find on the web regards avoiding memory leaks, which is a slightly different question - my memory is getting freed up, it's just that there's too much garbage collection going on.) I'm assuming that this mostly comes down to reusing objects as much as possible, but of course the devil is in the details.
The app is structured in 'classes' along the lines of John Resig's Simple JavaScript Inheritance.
I think one issue is that some functions can be called thousands of times per second (as they are used hundreds of times during each iteration of the main loop), and perhaps the local working variables in these functions (strings, arrays, etc.) might be the issue.
I'm aware of object pooling for larger/heavier objects (and we use this to a degree), but I'm looking for techniques that can be applied across the board, especially relating to functions that are called very many times in tight loops.
What techniques can I use to reduce the amount of work that the garbage collector must do?
And, perhaps also - what techniques can be employed to identify which objects are being garbage collected the most? (It's a farly large codebase, so comparing snapshots of the heap has not been very fruitful)
A lot of the things you need to do to minimize GC churn go against what is considered idiomatic JS in most other scenarios, so please keep in mind the context when judging the advice I give.
Allocation happens in modern interpreters in several places:
new
or via literal syntax [...]
, or {}
.(function (...) { ... })
.Object(myNumber)
or Number.prototype.toString.call(42)
Array.prototype.slice
.arguments
to reflect over the parameter list.Avoid doing those, and pool and reuse objects where possible.
Specifically, look out for opportunities to:
split
or regular expression matches since each requires multiple object allocations. This frequently happens with keys into lookup tables and dynamic DOM node IDs. For example, lookupTable['foo-' + x]
and document.getElementById('foo-' + x)
both involve an allocation since there is a string concatenation. Often you can attach keys to long-lived objects instead of re-concatenating. Depending on the browsers you need to support, you might be able to use Map
to use objects as keys directly. try { op(x) } catch (e) { ... }
, do if (!opCouldFailOn(x)) { op(x); } else { ... }
.JSON.stringify
which uses an internal native buffer to accumulate content instead of allocating multiple objects.arguments
since functions that use that have to create an array-like object when called.I suggested using JSON.stringify
to create outgoing network messages. Parsing input messages using JSON.parse
obviously involves allocation, and lots of it for large messages. If you can represent your incoming messages as arrays of primitives, then you can save a lot of allocations. The only other builtin around which you can build a parser that does not allocate is String.prototype.charCodeAt
. A parser for a complex format that only uses that is going to be hellish to read though.
这篇关于减少 Javascript 中垃圾收集器活动的最佳实践的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!