国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

Shisui / 2671人閱讀

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

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

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

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

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

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

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

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

簡單的技巧

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

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

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

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

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

// 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];
    }
};

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

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

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

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

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

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

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

這將在每次執行 tick 函數時重復使用相同的函數來代替產生一個新的函數。這個方法可以應用到任意其他地方的返回函數中或是運行創建的函數中。

進階技巧

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

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

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

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

arr.length = len;

顯然重寫大量的庫函數是相當痛苦的,所以你需要仔細的權衡需求實現的方便性以及垃圾產生之間的平衡。如果它在每幀中被調用了很多次,你可能最好重寫這個你需要的函數庫。

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

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

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

結論

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

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

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/79821.html

相關文章

  • [譯文] JavaScript工作原理:V8引擎內部+5條優化代碼竅門

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

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

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

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

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

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

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

    Edison 評論0 收藏0
  • JS中垃圾回收與內存泄漏

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

    xiaolinbang 評論0 收藏0

發表評論

0條評論

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