具有物件集區的靜態記憶體 JavaScript

Colt McAnlis
Colt McAnlis

簡介

因此,你會收到電子郵件通知,說明網頁遊戲 / 網頁應用程式在一段時間後的效能表現不佳,請詳讀程式碼,並在開啟 Chrome 的記憶體效能工具前看到任何值得注意的內容,例如:

記憶體時間軸中的快照

一位同事發現您碰到與記憶體相關的效能問題,因而感到困惑。

在記憶體圖表檢視畫面中,這個看不見的模式就透露了潛在的效能問題。隨著記憶體用量增加,時間軸擷取中的圖表區域也會持續增加。當圖表突然下降時,可能是垃圾收集器在執行期間,並清理參照的記憶體物件。

鋸齒形意義

在這類圖表中,您可以看到正在發生的「垃圾收集」事件,這可能會危害網頁應用程式的效能。本文將說明如何控管記憶體用量,進而降低對效能的影響。

垃圾收集和效能成本

JavaScript 的記憶體模型奠基於稱為垃圾收集器的技術。在許多語言中,程式設計師會直接負責配置及釋放系統的記憶體堆積中的記憶體。但是垃圾收集器系統會代表程式設計人員管理這項工作,這意味著當程式設計師對這項任務解除參照時,物件不會直接釋放在記憶體中,而是之後的 GC 經驗法則決定這麼做有益。這項決策過程需要時間,針對使用中和無效物件執行一些統計分析,而這需要時間才能對使用中和無效物件執行一些統計分析。

垃圾收集通常會比手動記憶體管理顯現,而程式設計師必須指定要釋出並返回記憶體系統的物件

GC 收回記憶體的程序不會釋放,且通常會藉由一段時間執行工作,導致無法使用效能;此外,系統本身也會決定執行的時機。您無法控管這項動作,執行程式碼期間隨時都可能會發生 GC 脈動,這個情形會阻止程式碼執行,直到程式碼完成為止。一般而言,您無法得知這項脈搏的持續時間;執行時間視程式在特定時間點使用記憶體的方式而定。

高效能應用程式仰賴一致的效能邊界,確保使用者享有流暢的使用體驗。垃圾收集器系統可以短暫執行此目標,因為它們可在隨機的持續時間內隨機執行,以達到應用程式達到效能目標所需的可用時間。

減少記憶體流失,減少垃圾收集稅

如上所述,在一組經驗法則判斷,有足夠閒置物件可有效接收脈衝時,系統就會進行 GC 脈搏。因此,減少垃圾收集器從應用程式花費的時間的關鍵,在於盡量避免建立和釋放過多物件。這種經常建立/釋放物件的程序稱為「記憶體抖動」。如果可以在應用程式的生命週期內減少記憶體抖動,也可減少執行 GC 所花費的時間。這表示您需要停止分配記憶體,以便有效移除 / 減少已建立及刪除的物件數量。

這項程序會將您的記憶體圖表從以下位置移出:

記憶體時間軸中的快照

改為:

靜態記憶體 JavaScript

在這個模型中,您可以看到圖表沒有像模式這樣的穩定,而是一開始就擴增了大量資料,然後慢慢增加。如果您因為記憶體流失而出現效能問題,您需要建立的圖表類型。

改用靜態記憶體 JavaScript

靜態記憶體 JavaScript 是一種技術,會在應用程式的開頭預先分配、生命週期所需的所有記憶體,以及在執行作業期間因不再需要物件而管理記憶體。我們可透過幾個簡單步驟達成這個目標:

  1. 對應用程式進行檢測,判斷不同使用情境的必要即時記憶體物件數量 (每個類型) 上限
  2. 請重新實作程式碼來預先分配上限,然後手動擷取/釋出數量,而不要前往主記憶體。

其實要完成第 1 大事,我們得先談談第 2 項原則,就讓我們開始吧。

物件集區

簡單來說,物件集區是指保留一組共用類型的未使用物件。如果您需要為程式碼使用新物件,而不是從系統記憶體堆積中分配新物件,而是應從集區中回收一個未使用的物件。使用物件完成外部程式碼後,系統不會將其釋出至主記憶體,而是將該程式碼傳回集區。由於物件絕不會從程式碼中「解除參照」 (即刪除),因此系統不會將該物件視為垃圾收集。使用物件集區可讓程式設計人員取回記憶體控制權,降低垃圾收集器對效能的影響。

由於應用程式會維護一組異質物件類型,因此如要正確使用物件集區,每種類型都須有一個集區,才能造成應用程式執行階段中高當機的問題。

var newEntity = gEntityObjectPool.allocate();
newEntity.pos = {x: 215, y: 88};

//..... do some stuff with the object that we need to do

gEntityObjectPool.free(newEntity); //free the object when we're done
newEntity = null; //free this object reference

對大多數應用程式而言,您最終還是會在需要分配新物件時達到一定程度。在應用程式一次的多次執行作業中,您應該能夠清楚知道上限為何,並在應用程式啟動時預先分配物件數量。

預先分配物件

在專案中實作物件集區,可讓您大致掌握應用程式執行階段中所需物件數量的理論上限。完成各種測試情境執行網站後,您可以瞭解將需要的記憶體需求類型,也可以將資料編入目錄並進行分析,瞭解應用程式的記憶體需求上限。

接著,在應用程式的出貨版本中,您可以設定初始化階段,將所有物件集區預先填入指定數量。這項操作會將所有物件初始化作業推送到應用程式前方,並減少執行期間動態的配置量。

function init() {
  //preallocate all our pools. 
  //Note that we keep each pool homogeneous wrt object types
  gEntityObjectPool.preAllocate(256);
  gDomObjectPool.preAllocate(888);
}

您選擇的金額與應用程式行為息息相關,但理論上限有時並不是最佳選項。舉例來說,選用平均上限可能會讓非進階使用者的記憶體用量較低。

原本是銀牌的

有鑑於所有應用程式都採用靜態的記憶體成長模式,不過,與 Chrome DevRel Renato Mangini 的其他創作者一樣,有一些缺點。

結論

JavaScript 是網路的理想利器之一,原因在於 JavaScript 是一種快速、有趣且容易上手的用語,這主要是因為語法限制不但較不容易,還無法代表您處理記憶體問題。您可以立即編寫程式碼,讓雜亂的工作交給我們處理。但對於高效能網頁應用程式 (例如 HTML5 遊戲),GC 通常能在極需要且需要的影格速率上補足負荷,從而降低使用者體驗。透過謹慎的檢測和採用物件集區,您可以減輕畫面更新率的負擔,還能利用這些時間創造更多令人驚豔的成果。

原始碼

網路上四處浮現物件集區的許多實作方式,因此我絕對不覺得自己。不過,我會引導你前往各個實作項目中的細微差異。由於每個應用程式使用情形都有特定的導入需求,因此這一點很重要。

參考資料