成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

如何編寫避免垃圾開銷的實時Javascript代碼

Shisui / 2693人閱讀

摘要:在語言中我們很難完全避免垃圾開銷。它的垃圾收集模式在根本上是不符合像游戲這樣的實時軟件需求的。此外,在所有可能的情況下避免向量對象如中的和屬性。

在 Javascript 語言中我們很難完全避免垃圾開銷。它的垃圾收集模式在根本上是不符合像游戲這樣的實時軟件需求的。在這篇文章中我們主要介紹了一些關(guān)于 javascript 垃圾回收的方法。

編輯于 2012 年 3 月 27 日: 哇,這篇文章已經(jīng)寫了有很長一段時間了,十分感謝那些精彩的回復(fù)!其中有一些對于一些技術(shù)的指正,如使用 ‘delete’ 。我知道了使用它可能會導(dǎo)致其他的降速問題,因此,我們在引擎中極少使用它。一如既往的你還需要對所有的事進(jìn)行權(quán)衡并且需要通過其他關(guān)注點來平衡垃圾回收機制,這也只是一個在我們引擎中發(fā)現(xiàn)的的實用、簡單的技術(shù)列表,它并不是一個完整的參考列表。但是我希望它還是有用的!

一個用 Javascript 編寫的 HTML5 游戲,要達(dá)到流暢體驗的一個最大阻礙就是垃圾回收 ( GC ) 卡頓。 Javascript 并沒有一個顯式的內(nèi)存管理,意味著你創(chuàng)造東西后卻不能釋放它們占用的內(nèi)存。因此遲早瀏覽器便會替你決定去清理它們:這時代碼執(zhí)行就會被暫停,瀏覽器會找出哪一部分內(nèi)存是現(xiàn)在仍在被使用的,并把其他所有東西占用的內(nèi)存釋放掉。這篇博文將會去探究避開GC開銷的技術(shù)細(xì)節(jié),這對方便進(jìn)行使用任何插件或是使用 Construct 2 進(jìn)行 Javascript SDK開發(fā)都應(yīng)該能派上用場。

瀏覽器有很多技術(shù)性手段來減少 GC 卡頓,但是如果你的代碼創(chuàng)造了許多垃圾,遲早瀏覽器也將會暫停并進(jìn)行清理。隨著對象逐步創(chuàng)建的過程中,之后瀏覽器又突然清理,這最后將導(dǎo)致內(nèi)存使用情況圖表呈現(xiàn) z 字形。例如,下面是 Chrome 在玩太空爆破手時的內(nèi)存使用情況。

當(dāng)在玩一個 Javascript 游戲時會呈現(xiàn) z 字形的內(nèi)存占用情況。這可能是一個內(nèi)存泄漏錯誤,但是實際上是 JavaScript 的正常操作。

此外,游戲以 60 fps 運行時只有 16 ms 的時間來渲染每一幀,但是 GC 會很輕易的產(chǎn)生最少 100 ms 以上明顯的卡頓,在更糟的情況下,這會導(dǎo)致不斷卡頓的游戲體驗,因此對于像游戲引擎一樣實時運行的 Javascript 代碼,解決辦法是努力嘗試在典型幀的持續(xù)時間內(nèi)你不要創(chuàng)建任何東西。這實際上是相當(dāng)困難的,因為有許多看上去無害的 Javascript 語句實際上卻創(chuàng)造了垃圾,它們都必須從每幀動畫的代碼路徑里刪除掉。在 Construct 2 中我們竭盡全力減少每一處引擎的垃圾開銷,但是你可以從圖表中看到上面仍然有許多小的對象被創(chuàng)建所以 Chrome 還會每隔數(shù)秒進(jìn)行一次清除。要注意這里只是一個小的清理 - 這里并沒有大量的內(nèi)存被清理出來,因為一個更高更極端的z曲線會更引起關(guān)注,但是它可能已經(jīng)足夠好了,因為小型的垃圾集合執(zhí)行會更快并且偶爾的小卡頓也一般不太引人注意 - 因此我們應(yīng)該看到了,有時我們確實很難避免產(chǎn)生新的資源分配。

同樣重要的包括第三方插件以及開發(fā)人員行為也需要遵守這些原則,否則,一個寫的不好的插件可以產(chǎn)生許多垃圾并會讓游戲十分卡頓,盡管主引擎 Construct 2 已經(jīng)是一個非常低垃圾開銷的引擎了。

簡單的技巧

首先,最明顯的是,關(guān)鍵詞 new 指示了資源的分配,例如 new Foo() 在可能的情況下,它會在啟動時嘗試創(chuàng)建一個對象,并且盡可能長時間、簡單的重新使用相同的對象。

不太明顯的是,這里有三種快捷語法方式來相似的調(diào)用 new :

{} (創(chuàng)建一個新對象)
[] (創(chuàng)建一個新數(shù)組)
function () { ... } (創(chuàng)建一個新函數(shù),也會被垃圾收集)

對于對象,用避免 {} 一樣的方式來避免 new - 嘗試去回收對象。請注意這包括像 { "foo": "bar" } 這樣帶屬性的對象,也就是我們在函數(shù)中常用的一次性返回多個值?;蛟S將每一次的返回值寫入一個相同的(全局)對象來返回的寫法是更好的 - 在文檔中要仔細(xì)記錄這一點,因為如果你保持引用這樣的返回對象,可能在每次調(diào)用改變的時候發(fā)生錯誤。

實際上你可以回收一個存在的對象(如果它沒有原型鏈)通過刪除它的所有屬性,將它還原為一個空的對象如 {} 一樣。為此你可以使用 cr.wipe(obj) 函數(shù),它的定義如下:

// remove all own properties on obj,
effectively reverting it to a new object
cr.wipe = function (obj)
{
    for (var p in obj)
    {
        if (obj.hasOwnProperty(p))
            delete obj[p];
    }
};

因此在某些情況下,你可以調(diào)用 cr.wipe(obj) 并為其再次添加屬性來重用一個對象。比起重新簡單分配 {} 現(xiàn)場清除一個對象可能需要更長的時間,但是在實時處理的代碼中更重要的是避免產(chǎn)生垃圾,從而減少未來可能產(chǎn)生的卡頓情況。

分配 [] 到一個數(shù)組中被經(jīng)常用來作為一個快捷方式去清除這個數(shù)組(例如 arr = [];),但請注意這將創(chuàng)建一個新的空數(shù)組并使舊的數(shù)組成為一個垃圾!更好的寫法是 arr.length = 0; ,這種方式具有相同的效果但卻繼續(xù)使用了相同的數(shù)組對象。

函數(shù)則有一點棘手,函數(shù)通常在執(zhí)行時創(chuàng)建并且不傾向于在運行時進(jìn)行過多分配 - 但這意味著它們在動態(tài)創(chuàng)建時很容易被忽視。一個例子是返回函數(shù)的函數(shù)。主要的游戲循環(huán)使用了 setTimeout 或者 requestAnimationFrame 方法來調(diào)用一個成員函數(shù)類似如下:

setTimeout((function (self) { return function () {
self.tick(); }; })(this), 16);

這看起來像是一個合理的方式來每 16ms 調(diào)用一次 this.tick() 。然而,這也意味著每一次執(zhí)行 tick 函數(shù)都會返回一個新函數(shù)!這可以通過永久存儲函數(shù)的方法來避免,例如:

// at startup
this.tickFunc = (function (self) { return function () {
self.tick(); }; })(this);

// in the tick() function
setTimeout(this.tickFunc, 16); 

這將在每次執(zhí)行 tick 函數(shù)時重復(fù)使用相同的函數(shù)來代替產(chǎn)生一個新的函數(shù)。這個方法可以應(yīng)用到任意其他地方的返回函數(shù)中或是運行創(chuàng)建的函數(shù)中。

進(jìn)階技巧

隨著我們的進(jìn)展,進(jìn)一步的避免產(chǎn)生垃圾變得更加困難,由于 Javascript 本身就是圍繞著 GC 所設(shè)計的。許多 Javascript 中方便的庫函數(shù)也總是創(chuàng)建了新的對象。這兒沒有什么你可以做的但是當(dāng)你返回文檔查閱那些返回值時。例如,數(shù)組中的 slice() 方法會返回一個數(shù)組(基于保持不變的原始數(shù)組范圍內(nèi)),字符串的 substr 會返回一個新的字符串(基于保持不變的原始字符串字符的范圍),等等。調(diào)用這些函數(shù)都會產(chǎn)生垃圾,而你能做的就是不要去調(diào)用它們,或是在極端情況下重寫你的函數(shù)使它們不再產(chǎn)生垃圾。例如在 Construct 2 這種引擎,由于各種原因一個經(jīng)常的操作是通過索引去刪除數(shù)組里的一個元素。這個方法的快捷使用方式如下:

var sliced = arr.slice(index + 1);
arr.length = index;
arr.push.apply(arr, sliced);

然而 slice() 返回一個原始數(shù)組的后半部分來組成了一個新的數(shù)組,并且在被(arr.push.apply)復(fù)制后產(chǎn)生了垃圾。由于這是我們引擎中一個生產(chǎn)垃圾的熱門處,它被改寫為了一個迭代版本:

for (var i = index, len = arr.length - 1; i < len; i++)
    arr[i] = arr[i + 1];

arr.length = len;

顯然重寫大量的庫函數(shù)是相當(dāng)痛苦的,所以你需要仔細(xì)的權(quán)衡需求實現(xiàn)的方便性以及垃圾產(chǎn)生之間的平衡。如果它在每幀中被調(diào)用了很多次,你可能最好重寫這個你需要的函數(shù)庫。

這里可以很容易的使用 {} 語法來沿著遞歸函數(shù)傳遞數(shù)據(jù)。通過一個數(shù)組來表示一個堆棧,在這個堆棧中對遞歸的每一級進(jìn)行 pushpop 是更好的。更好的是,實際上你并不需要在數(shù)組中 pop - 你應(yīng)該將數(shù)組中最后一個對象像垃圾一樣處理掉。來代替使用一個 ‘top index’ 變量進(jìn)行簡單減量。然后為了代替 pushing ,則增加 top index 并且如果有的話就重用數(shù)組中的下一個對象,否則執(zhí)行真正的 push

此外,在所有可能的情況下避免向量對象(如 vector2 中的 x 和 y 屬性)。雖然可能函數(shù)返回這些對象會讓它們立刻改變或返回這兩個值時會方便些,你可以在每一幀中輕松地結(jié)束數(shù)百個這樣的創(chuàng)建對象,這將導(dǎo)致可怕的 GC 性能。這些函數(shù)必須分離出來在每個多帶帶的組件中工作,例如:使用 getX()getY() 來代替 getPosition() 來返回一個 vector2 對象。

有時候你無法擺脫一個庫是一個產(chǎn)生垃圾的噩夢。 Box2Dweb 是一個典型的例子:它每一幀產(chǎn)生了數(shù)百個 b2Vec2 對象并且不斷的在瀏覽器產(chǎn)生垃圾,并最終導(dǎo)致垃圾處理器產(chǎn)生顯著的卡頓效果。在這種情況下最好的辦法是創(chuàng)建一個緩存回收機制。我們一直在測試 Box2D(Box2Dweb-closure) 的修正版本,它似乎可以使 GC 暫停進(jìn)行緩解(雖然沒有完全解決)。查閱 b2Vec2.js 的 Get 和 Free 代碼。這里有一個名字叫 ‘free cache’ 的數(shù)組,在之后的整個代碼中如果不再使用 b2Vec2,它就會在 free cache 中被釋放,當(dāng)需要請求一個新的 b2Vec2,而它如果在 free cache 中還存在那么它就會被重用,否則才會分配一個新的。這并不完美,在一些測試后通常只有一半的 b2Vec2s 被創(chuàng)建并回收,但它確實幫助 GC 緩解了壓力從而減少了頻繁的卡頓。

結(jié)論

在 Javascript 中很難去完全避免垃圾。它的垃圾收集模式根本上是不符合像游戲這樣的實時軟件的需求的。從 Javascript 代碼中需要進(jìn)行大量的工作來消除垃圾,因為有很多直接的代碼含有創(chuàng)建大量垃圾的副作用。然而,只要仔細(xì)小心一些,Javascript 也是可以在實時項目中不產(chǎn)生或是制造很少的垃圾開銷,而對于需要保持高度響應(yīng)性的游戲和應(yīng)用程序這也是至關(guān)重要的。

原文鏈接 : How to write low garbage real-time Javascript
譯文出自 : 掘金翻譯計劃

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/79821.html

相關(guān)文章

  • [譯文] JavaScript工作原理:V8引擎內(nèi)部+5條優(yōu)化代碼竅門

    摘要:本文將會深入分析的引擎的內(nèi)部實現(xiàn)。該引擎使用在谷歌瀏覽器內(nèi)部。同其他現(xiàn)代引擎如或所做的一樣,通過實現(xiàn)即時編譯器在執(zhí)行時將代碼編譯成機器代碼。這可使正常執(zhí)行期間只發(fā)生相當(dāng)短的暫停。 原文 How JavaScript works: inside the V8 engine + 5 tips on how to write optimized code 幾周前我們開始了一個系列博文旨在深入...

    dreamans 評論0 收藏0
  • JavaScript如何工作:深入V8引擎&編寫優(yōu)化代碼5個技巧

    摘要:第二篇文章將深入谷歌的引擎的內(nèi)部。引擎可以實現(xiàn)為標(biāo)準(zhǔn)解釋器,或者以某種形式將編譯為字節(jié)碼的即時編譯器。這個引擎是在谷歌中使用的,但是,與其他引擎不同的是也用于流行的。一種更復(fù)雜的優(yōu)化編譯器,生成高度優(yōu)化的代碼。不是唯一能夠做到的引擎。 本系列的 第一篇文章 主要介紹引擎、運行時和調(diào)用堆棧。第二篇文章將深入谷歌 V8 的JavaScript引擎的內(nèi)部。 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHu...

    Turbo 評論0 收藏0
  • JavaScript如何工作:深入V8引擎&編寫優(yōu)化代碼5個技巧

    摘要:第二篇文章將深入谷歌的引擎的內(nèi)部。引擎可以實現(xiàn)為標(biāo)準(zhǔn)解釋器,或者以某種形式將編譯為字節(jié)碼的即時編譯器。這個引擎是在谷歌中使用的,但是,與其他引擎不同的是也用于流行的。一種更復(fù)雜的優(yōu)化編譯器,生成高度優(yōu)化的代碼。不是唯一能夠做到的引擎。 本系列的 第一篇文章 主要介紹引擎、運行時和調(diào)用堆棧。第二篇文章將深入谷歌 V8 的JavaScript引擎的內(nèi)部。 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHu...

    DevWiki 評論0 收藏0
  • Flink 源碼解析 —— 深度解析 Flink 是如何管理好內(nèi)存

    摘要:減少垃圾收集壓力因為所有長生命周期的數(shù)據(jù)都是在的管理內(nèi)存中以二進(jìn)制表示的,所以所有數(shù)據(jù)對象都是短暫的,甚至是可變的,并且可以重用。當(dāng)然,并不是唯一一個基于且對二進(jìn)制數(shù)據(jù)進(jìn)行操作的數(shù)據(jù)處理系統(tǒng)。 showImg(https://segmentfault.com/img/remote/1460000020044119?w=1280&h=853); 前言 如今,許多用于分析大型數(shù)據(jù)集的開源系...

    Edison 評論0 收藏0
  • JS中垃圾回收與內(nèi)存泄漏

    摘要:介紹瀏覽器的具有自動垃圾回收機制,也就是說,執(zhí)行環(huán)境會負(fù)責(zé)管理代碼執(zhí)行過程中使用的內(nèi)存。中的內(nèi)存泄漏問題程序的內(nèi)存溢出后,會使某一段函數(shù)體永遠(yuǎn)失效取決于當(dāng)時的代碼運行到哪一個函數(shù),通常表現(xiàn)為程序突然卡死或程序出現(xiàn)異常。 showImg(https://segmentfault.com/img/remote/1460000018932880?w=4400&h=3080); 1. 介紹 瀏...

    xiaolinbang 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<