PerfDog 助力微信小遊戲最佳化-----實踐
背景:
我們的引擎是 Egret,使用的是原生的 EUI,轉微信小遊戲;
工程第一版出來後使用 PerfDog 測試一波資料。結果發現很多問題,本文主要分三部分
第一部分主要介紹透過 PerfDog 發現問題,
第二部分主要介紹透過 PerfDog 的資料定位並解決問題。
第三部分介紹原生小遊戲常見最佳化的地方
PerfDog 具體操作方法不再贅述,這裡可以看文件PerfDog 使用說明
第一部分————資料分析
第一次測試資料
FPS 感人(我們限幀 60)
Cpu 勉強還過得去
【圖一】
【圖二】
記憶體堪憂
有的同學可能發現 App 的 CPUusage 比 total cpuusage 低很多(圖一),是因為我選擇測試的是微信 app,小遊戲 是作為子程序而存在的,所以後來選擇 PerfDog 的子程序進行測試,得到的資料會更加的精準;
==深色表示正在執行的頂層程序==
再次測試就是正常的資料啦(圖二),於是我們切換到子執行緒開始進行第二次測試
第二次測試資料
FPS 資料:
CPU 資料:
記憶體資料:
這時我們在測試過程中發現記憶體不斷上升,沒有呈現一個正常的記憶體趨勢,所以調整了一下測試策略
測試資料組成:
我們在測試過程中做了一些特殊操作:
1.戰鬥掛機【為了判斷是否是戰鬥過程中觸發的記憶體洩露】
2.反覆開啟關閉 UI【為了判斷 UI 建立與銷燬是否存在記憶體洩露】
3.靜止在某一 UI 頁面【為了與其他場景作區分】
4.息屏掛機【為了判斷是否是由影像資源引起的記憶體洩露還是程式碼資源引起的洩露】
GPU 壓力山大
我們透過 FPS 資料發現在遊戲過程 Jank 十分嚴重,FPS 波動過於劇烈,尤其是集中在 UI 開啟或者關閉的時候,這個時候我們進行資料排查發現 GPU 的使用率也變得異常高,基本上已經爆表,很明顯渲染的壓力很大,而我們遊戲 UI 開啟時實際上戰鬥也會被渲染,這和我們遊戲的設計有關,所以渲染的壓力很大。
再來看看記憶體:
記憶體資料:
我們透過 PerfDog 的資料發現記憶體是呈現一直上升的狀態,尤其是 VSS(VirtualMemory),如脫韁的野馬一發不可收拾,這樣下去最終的結果就是被 System Kill 掉。
其實現在已經可以確定是發生了記憶體洩露,在 72 分鐘的時間裡記憶體從 726M 到了 956M,而且還在不斷上升;
現在綜合兩次測試資料得出結論
結論:
1.FPS 波動過於劇烈,很不穩定,尤其是在 uI 建立與關閉時候;
2.存在記憶體洩露
3.其實還有一些其他小問題,不過優先解決這兩個
第二部分————問題定位
記憶體洩露問題分析
有了 PerfDog 以上的資料,接下來我們就要開始定位排查問題啦,
專案區域性架構:
1.我們的專案的基礎架構是所有的基礎功能都呼叫的同一份基礎 class(祖傳程式碼),例如通訊類等等;
2.我們發現記憶體在一直上升,無論是角色在什麼環境下,甚至是在息屏的時候記憶體也在上升,那麼我們其實可以大機率定位是專案內部的基礎 class 內部出了問題;
接下來開始細細排查;
記憶體洩露排查
首先要先了解一些 JS 的記憶體管理機制
回收機制
JS 中記憶體的分配和回收都是 VM 自動完成的,不需要像 C/C++ 為每一個 new/malloc 操作去寫配對的 delete/free 程式碼,JS 引擎中對變數的儲存主要是在棧記憶體,堆記憶體。記憶體洩漏的實質是一些物件出現意外而沒有被回收,而是常駐記憶體。
GC 原理
JavaScript 虛擬機器有一個特點,就是物件建立的開銷遠遠大於物件計算的開銷,並且物件建立會導致垃圾回收,而垃圾回收會導致遊戲不定期卡頓。
在堆中檢視無用的物件,把這些物件佔用的記憶體空間進行回收。瀏覽器上的 GC(Gabage Collection 垃圾回收) 實現,大多是採用可達性演算法,關於可達性的物件,便是能與 GC Roots 構成連通圖的物件。當一個物件到 GC Roots 沒有任何引用鏈時,則會成為垃圾回收器的目標,系統會在合適的時候回收它所佔的記憶體。
我這裡使用的谷歌瀏覽器的 Head Profiling,或者你也可以使用白鷺引擎的 profiler:
使用很簡單:
1.開啟 Google 瀏覽器,開啟要監控的網頁,win 下按 F12 彈出開發者工具
2.切換到 Memory,選擇堆型別,選中 Take Heap SnapShot 開始進行快照
3.右邊的檢視列出了 heap 裡的物件列表,點選物件可以看到物件的引用層級關係
4.進入遊戲後拍下快照,開啟某個介面,關閉介面,拍下快照
5.將新的快照轉換到 Comparsion 對比檢視,進行記憶體對比分析
==需要額外注意的是:
每次拍快照前,都會先自動執行一次 GC,保證檢視裡的物件都是 root 可及的。GC 的觸發是依賴瀏覽器的,所以不能透過時時觀察記憶體峰值而判斷是否有記憶體洩漏。==
我們可以每隔一段時間來拍一次快照(由於公司專案原因,我就不展示真實專案了,此處僅作為教學):
我們可以開啟谷歌瀏覽器的記憶體分析工具後有三個選項,我們可以根據自己的除錯方式交替使用;
1.Heap snapshot - 用以列印堆快照,堆快照檔案顯示頁面的 javascript 物件和相關 DOM 節點之間的記憶體分配
2.Allocation instrumentation on timeline - 在時間軸上記錄記憶體資訊,隨著時間變化記錄記憶體資訊。
3.Allocation sampling - 記憶體資訊取樣,使用取樣的方法記錄記憶體分配。此配置檔案型別具有最小的效能開銷,可用於長時間執行的操作。它提供了由 javascript 執行堆疊細分的良好近似值分配。
這裡舉例使用堆快照分析,
右側檢視詳細資訊
可見 rect 物件一直在增高,那麼我們可以檢視一下導致 rect 物件未被釋放的原因:
是由於 Rect 物件中存在一個屬性 rect 一直被引用導致記憶體無法釋放,那麼我們到程式碼對應的位置去找,就可以較快的定位原因;最終我們發現是因為在自定義的一個全域性事件監聽器中例項化了一個物件,但是這個物件的一些屬性會持續被這個事件監聽器所引用而不會被回收
當然為了更快的定位哪個函式,我們也可以使用
一般結果是這個樣子
Overview 的 HEAP(堆) 曲線圖表示 JS 堆。
Call Stack 通常來說,垂直方向並沒有太大的意義,僅僅表示函式巢狀比較深而已,但是橫向表示呼叫時間,如果呼叫時間太長,那麼就需要最佳化最佳化了。錄製結果的呼叫堆疊,橫向表示時會出現帶有更多詳情的浮窗間,垂直方向表示呼叫棧,從上往下表示函式呼叫。滑動滑鼠滾輪可以檢視某段時間的呼叫棧資訊。把滑鼠放到 Call Stacks 呼叫棧的某個函式上面可以檢視函式詳細資訊。這個一般是效能最佳化時關注,對於記憶體洩漏,主要用於幫助定位進行了什麼操作。
Counter(計數器) 窗格。在這裡你可以看到記憶體使用情況(與 Overview(概述) 窗格中的 HEAP(堆) 曲線圖相同),分別顯示以下內容:JS heap(JS 堆),documents(文件),DOM nodes(DOM 節點),listeners(偵聽器) 和 GPU memory(GPU 記憶體)。勾選或取消勾選核取方塊可以將其從圖表中顯示或隱藏。
主要關注第三個的 JS 堆記憶體、節點數量、監聽器數量。滑鼠移到曲線上,可以在左下角顯示具體資料。這些資料若有一個在持續上漲,沒有下降趨勢,都有可能是洩漏。
由於篇幅原因,這裡不過多介紹這些工具的使用,網上有很多相關教程;
卡頓最佳化
我們發現在遊戲執行時 drawcall 過多,而且每幀的渲染耗時比較長,所以會呈現一種卡頓的現象;
關於檢視 drawcall 等可以透過白鷺自身的 FPS 皮膚檢視 白鷺 debug 文件
在最佳化前首先要了解 egret 在渲染的一幀裡做了什麼工作內容
細分的話又可以分成
每一幀的工作內容:
1.執行一次 EnterFrame,此時,引擎會執行遊戲中的邏輯。並且丟擲 EnterFrame 事件
2.引擎會執行一個 clear。將上一幀的畫面全部擦除
3.Egret 核心會遍歷遊戲場景中的所有 DisplayObject,並重新計算所有顯示物件的 transform
4.所有的影像全部 draw 到畫布
現在來最佳化一下:
首先要降低 drawcall:
1 把小圖全都換成圖集
2.實現文字合批,透過自定義字型,使用圖片字型的方式代替原生的字型
3.動靜分離,將需要變化的和不變的分別放在不同的層級下,比如背景層、圖示層和動態變化層
4.動畫儘量使用 dragon bones 幀動畫而不是 spine 動畫
5.使用 cacheAsBitmap,把向量圖在執行時以點陣圖形式進行計算
降低幀事件的開銷:
1.不要的 DisplayObject,直接 removeChild 而不是設定他的 visible 屬性為 false,否則在第三步還會參與計算
2.不在主迴圈裡建立任何物件,遊戲中的人物、怪物、技能特效統統做成物件池
3.不在 EnterFrame 事件中做過多的操作,非要用可以自定義一些事件
我們可以用以下的函式統計建立的 gameobject 的數量
它是顯示了每一秒鐘去拿一個 hashCount 跟上一個 hashCount 作對比,這個 hashCount 是由白鷺引擎內部 API,用於統計引擎物件的建立數量。如果遊戲靜止放置不動,理論上 hashCount diff 的結果應該是 0,實際上要儘可能控制在 120 以下,如果超標,只需要在引擎的 HashObject 的建構函式這裡新增一個斷點,在執行時去檢查呼叫堆疊就排查就可以了。
第三部分————原生小遊戲最佳化
小小遊戲中避免頻繁使用 setData,這裡是最容易出問題的地方
setData 原理:
每一次 setData, 邏輯層向渲染層的發起一次通訊,這個通訊還不是直接傳給 webView, 而是透過走了 native 層,渲染層收到通訊後,還需要重新渲染出來,
一次 setData 會帶來兩次開銷:通訊的開銷 + webview 更新的開銷。
儘量避免頻繁使用 setData
小程式和原生小遊戲有很多相同的地方:
補充:
微信小程式公開課
相關文章
- 效能測試實踐 | PerfDog 助力微信小遊戲 / 小程式效能調優遊戲
- 效能測試實踐 | PerfDog助力微信小遊戲/小程式效能調優遊戲
- 微信小遊戲優化實踐遊戲優化
- 微信小遊戲和白鷺引擎開發實踐遊戲
- 微信小遊戲首度亮相 ChinaJoy,助力開發者玩轉小遊戲遊戲
- 實戰 PerfDog 優化小遊戲效能優化遊戲
- 微信小遊戲分包遊戲
- Cocos Creator - 微信小遊戲 實戰分享遊戲
- 微信小程式--遊戲demo微信小程式遊戲
- 微信小遊戲開發小記遊戲開發
- 誰在玩微信小遊戲?遊戲
- 微信小遊戲開發(1)遊戲開發
- 微信小遊戲開發(2)遊戲開發
- 微信小遊戲開發(3)遊戲開發
- 微信小程式:拼圖遊戲微信小程式遊戲
- 微信小程式入門與實踐微信小程式
- 微信IAA小遊戲入局攻略遊戲
- 微信小遊戲開發(6)-Adapter遊戲開發APT
- 微信小遊戲開發總結遊戲開發
- iOS 如何測試微信小遊戲&小程式?iOS遊戲
- Android如何測試微信小遊戲&小程式?Android遊戲
- iOS如何測試微信小遊戲&小程式?iOS遊戲
- 微信小程式Video元件實踐總結微信小程式IDE元件
- 微信小遊戲接入激勵視訊遊戲
- 微信小遊戲開放域之helloworld遊戲
- 【微信小程式】掃碼付小程式優化實踐微信小程式優化
- 微信小程式授權登入最佳實踐微信小程式
- 2019上半年微信小程式:小遊戲佔42.1%微信小程式遊戲
- 微信吸粉小遊戲怎麼製作?中秋節微信公眾號吸粉小遊戲製作教程遊戲
- PerfDog可以助力高幀率遊戲生態更全面發展遊戲
- 寓教於樂:教育類遊戲入局微信小遊戲遊戲
- 微信小遊戲如何組織集中體驗遊戲
- 使用Laya引擎開發微信小遊戲(上)遊戲
- 使用Laya引擎開發微信小遊戲(下)遊戲
- 微信小遊戲開發(8)-模組化遊戲開發
- 微信小遊戲開發(9)- 分包載入遊戲開發
- 微信小遊戲開發(10)-音訊播放遊戲開發音訊
- 【微信小程式開發】梔子手作花花微信小程式商城開發最佳實踐微信小程式