問題描述
我有一個相當復雜的 Javascript 應用程序,它有一個每秒調用 60 次的主循環.似乎正在進行大量垃圾收集(基于 Chrome 開發工具中內存時間線的鋸齒"輸出)——這通常會影響應用程序的性能.
因此,我正在嘗試研究減少垃圾收集器必須完成的工作量的最佳實踐.(我在網上找到的大部分信息都是關于避免內存泄漏,這是一個稍微不同的問題——我的內存正在被釋放,只是垃圾收集太多了.)我假設這主要歸結為盡可能重用對象,但細節當然是魔鬼.
該應用按照 John Resig 的簡單 JavaScript 繼承 的類"構造.p>
我認為一個問題是某些函數每秒可以調用數千次(因為它們在主循環的每次迭代中使用了數百次),可能還有這些函數中的局部工作變量(字符串、數組、等)可能是問題.
我知道用于更大/更重對象的對象池(我們在一定程度上使用它),但我正在尋找可以全面應用的技術,尤其是與被多次調用的函數相關的技術在緊密的循環中.
我可以使用哪些技術來減少垃圾收集器必須完成的工作量?
而且,也許還有 - 可以使用哪些技術來識別哪些對象被垃圾回收最多?(這是一個非常大的代碼庫,所以比較堆的快照并不是很有成果)
你需要做的很多事情來最小化 GC churn 在大多數其他場景中被認為是慣用的 JS,所以請記住上下文判斷我給出的建議.
現代口譯員的分配發生在幾個地方:
- 當您通過
new
或通過文字語法[...]
或{}
創建對象時. - 連接字符串時.
- 當您輸入包含函數聲明的范圍時.
- 當您執行觸發異常的操作時.
- 評估函數表達式時:
(function (...) { ... })
. - 當您執行像
Object(myNumber)
或Number.prototype.toString.call(42)
這樣的強制對象操作時 - 當您調用在后臺執行任何這些操作的內置函數時,例如
Array.prototype.slice
. - 當您使用
arguments
來反映參數列表時. - 當您拆分字符串或使用正則表達式匹配時.
避免這樣做,并盡可能集中和重用對象.
特別是尋找機會:
- 將不依賴或很少依賴封閉狀態的內部函數拉到更高、壽命更長的范圍內.(像 閉包編譯器 之類的一些代碼壓縮器可以內聯內部函數,并可能提高您的 GC 性能.)
- 避免使用字符串來表示結構化數據或動態尋址.尤其要避免使用
split
或正則表達式匹配重復解析,因為每個都需要多個對象分配.這經常發生在查找表和動態 DOM 節點 ID 的鍵上.例如,lookupTable['foo-' + x]
和document.getElementById('foo-' + x)
都涉及分配,因為存在字符串連接.通常,您可以將鍵附加到長期存在的對象而不是重新連接.根據您需要支持的瀏覽器,您也許可以使用Map
直接使用對象作為鍵. - 避免在正常代碼路徑上捕獲異常.代替
try { op(x) } catch (e) { ... }
,執行if (!opCouldFailOn(x)) { op(x);} else { ... }
. - 當您無法避免創建字符串時,例如要將消息傳遞給服務器,請使用像
JSON.stringify
這樣的內置函數,它使用內部本機緩沖區來累積內容而不是分配多個對象. - 避免對高頻事件使用回調,并且在可能的情況下,將一個長期存在的函數(參見 1)作為回調傳遞,該函數會根據消息內容重新創建狀態.
- 避免使用
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:
- When you create an object via
new
or via literal syntax[...]
, or{}
. - When you concatenate strings.
- When you enter a scope that contains function declarations.
- When you perform an action that triggers an exception.
- When you evaluate a function expression:
(function (...) { ... })
. - When you perform an operation that coerces to Object like
Object(myNumber)
orNumber.prototype.toString.call(42)
- When you call a builtin that does any of these under the hood, like
Array.prototype.slice
. - When you use
arguments
to reflect over the parameter list. - When you split a string or match with a regular expression.
Avoid doing those, and pool and reuse objects where possible.
Specifically, look out for opportunities to:
- Pull inner functions that have no or few dependencies on closed-over state out into a higher, longer-lived scope. (Some code minifiers like Closure compiler can inline inner functions and might improve your GC performance.)
- Avoid using strings to represent structured data or for dynamic addressing. Especially avoid repeatedly parsing using
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]
anddocument.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 useMap
to use objects as keys directly. - Avoid catching exceptions on normal code-paths. Instead of
try { op(x) } catch (e) { ... }
, doif (!opCouldFailOn(x)) { op(x); } else { ... }
. - When you can't avoid creating strings, e.g. to pass a message to a server, use a builtin like
JSON.stringify
which uses an internal native buffer to accumulate content instead of allocating multiple objects. - Avoid using callbacks for high-frequency events, and where you can, pass as a callback a long-lived function (see 1) that recreates state from the message content.
- Avoid using
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 中垃圾收集器活動的最佳實踐的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!