導讀
隨着Lua在項目中的大量使用,它所帶來的性能問題也逐步成爲了項目運行時的重大性能瓶頸之一。特別是內存相關的性能問題,不管是內存分配過大仍是內存泄露沒法回收,目前都已經在很多研發項目中集中爆發。html
UWA推出的GOT Online中的Lua模式已經慢慢成爲研發團隊對Lua進行平常性能監控的有效手段。所以,也有愈來愈多的團隊反饋,在監控到table數持續上漲,引用Mono對象持續增多等等問題時,應該如何快速地解決?性能優化
本次博物納新推薦的開源庫項目:LuaProfiler-For-Unity,相信能夠幫助到你們。
Lua Profiler For Unity支持XLua、SLua、ToLua,該工具是基於遠程Socket的Profiler工具,所以它支持Android,iOS的真機Profiler。服務器
小編將結合實際項目中遇到的問題,爲你們介紹這款開源庫的用法。框架
開源庫連接:https://lab.uwa4d.com/lab/5bf38db072745c25a80c1276ide
做者Blog:https://www.zhihu.com/people/ElPsyConGree/activities函數
操做流程
1、部署和安裝
參考項目中的Readme文檔闡述的詳細流程:
一、打開兩個Unity項目,一個放進遊戲客戶端,一個用於展現數據
二、打開LuaProfiler文件夾工具
LuaProfiler文件目錄性能
三、將 LuaProfilerClient 文件夾複製到遊戲項目內,若是您的C#Lua腳本位於Plugins文件夾中,則將 LuaProfilerClient 複製到插件。此工具必須確保該代碼必須位於具備C#Lua代碼的同一DLL中。
四、使用 Unity5.6 or newer version Unity版本將 LuaProfilerServer 做爲Unity項目打開。
五、若是Unity版本低於5,請在開始遊戲前調用如下代碼。優化
MikuLuaProfiler.HookLuaSetup.OnStartGame();
注意:不要在static變量聲明裏面啓動Lua流程(好比XLua的Demo),請在Awake或者Start裏面進行調用。ui
2、使用教程
(該小節內容所有來自於項目Readme文檔,想要閱讀更詳細教程的讀者可訪問項目主頁:https://lab.uwa4d.com/lab/5bf38db072745c25a80c1276)
一、配置客戶端
Lua Profiler Client界面
LuaProfilerClient插件所在的遊戲項目工程,經過Editor界面的Window->Lua Profiler Window打開客戶端設置界面。選擇想要分析器的代碼類型,C#代碼顏色爲綠色,Lua代碼顏色爲藍色。
二、配置服務器端
LuaProfilerServer插件所在的項目工程,經過Editor界面的Window->Lua Profiler Window打開服務器數據顯示界面。
Lua Profiler Server界面
單擊OpenService,等待客戶端鏈接。
操做流程示意圖
三、相關功能操做
使用示意圖
3.1 相關基礎數據統計
折線走勢圖展現了PSS、Mono內存、Lua內存、FPS的走勢:
折線走勢圖
數據列表
數據列表中列舉了當前幀下圖所示的相關數據:
3.2 監控註冊表
註冊表引用的Lua對象統計
此處區域會顯示當前被註冊表引用的類型爲Function和table的Lua對象。
3.3 Diff兩個不一樣時期的Lua變量
選取一個適當的時機,好比配置表加載完後,準備打開一個新的UI的時候點擊MarkLuaRecord按鈕。
操做步驟
打開UI而後關閉並卸載掉UI資源,點擊DiffRecord,工具將會對Mark時候的Lua變量與DiffRecord時候的變量進行差別比較。
數據顯示界面
點擊ShowLog按鈕,將會把文件存盤打開以後將把對於變量的類型以及引用路徑打印出來。注意_G表示全局表,_R表示被C#引用的對象。
引用鏈
3.4 Destroy null values統計
該模塊展現了Unity已經將資源釋放,而Lua仍然引用的變量。
引用鏈
功能原理詳解
1、相關基礎數據統計
查看這部分數據時,除了關注排序中開銷較大、內存佔用較高的函數外還能夠關注一些重要的字段,例如:
require字段,比較廣泛的性能問題在於:加載配置表時產生一個內存佔用較大的table,能夠經過搜索require字段並進行排序查找內存佔用較大的配置表,並對它進行鍼對性優化。
關於配置表的優化方案能夠閱讀《Lua配置表存儲優化方案》。
[Lua]字段,按照Lua內存的self進行排序,能夠定位GC比較嚴重的Lua函數,進行鍼對性優化。也能夠查看調用次數等。
在搜索框中搜索[Lua]
善用搜索功能,會揪出不少意想不到的性能問題,好比:老生常談的Vertex3。
2、監控註冊表
以SLua框架的示例Circle.cs 爲例:
//Circle.cs public class Circle : MonoBehaviour { LuaSvr svr; LuaSvr s2; LuaTable self; LuaFunction update; [CustomLuaClass] public delegate void UpdateDelegate(object self); UpdateDelegate ud; void Start () { svr = new LuaSvr(); svr.init(null, () => { self = (LuaTable)svr.start("circle/circle"); update = (LuaFunction)self["update"]; ud = update.cast<UpdateDelegate>(); } ); ......
//Circle.lua local class={} function main() local slider = GameObject.Find("Canvas/Slider"):GetComponent(UI.Slider) local counttxt = GameObject.Find("Canvas/Count"):GetComponent(UI.Text) slider.onValueChanged:AddListener( function(v) class:init(v) counttxt.text=string.format("cube:%d",v) end ) class.root = GameObject("root") class.ftext = GameObject.Find("Canvas/Text"):GetComponent(UI.Text) class.r=10 class.cubes={} class.t=0 class.f=0 class.framet=0 class.max=0 class:init() return class end function class:update() -- gc alloc is zero0 ......
C# 層建立的變量self、svr、update等,引用了Lua層的class、update等。
使用該工具獲得的引用關係如圖所示:
其中「@circle/circle&line:15"的函數爲:
function(v) class:init(v) counttxt.text=string.format("cube:%d",v) end
被添加到UI組件Slider的事件監聽中。
「@circle/circle&line:65"的函數爲:function class:update(),被一個LuaFunction類型對象update引用。
其中具備key爲:bgCurrent、init、root、t等值的table,爲Lua代碼中的:
local class={}
(其他被引用的table和function是框架初始化時產生的)
C#層是一個ref,內存佔用較小,可是Lua層會是一個較爲複雜的table或者函數調用等,內存佔用較大。當再也不使用的C#對象沒有被徹底釋放時,因爲C#層內存佔用較小,並不會及時進行GC,使得Lua層仍然存在引用,沒法進行GC,致使大量內存滯留。
3、Destroy null values統計
在任意一種Lua插件中,都存在相似的機制:在C#層維護一個Cache來引用那些被Lua訪問過的C#層對象,防止出現如下的問題:當Lua中再次訪問該C#對象時,該對象可能已經被C#層的GC回收掉了,從而致使邏輯錯誤。因此,在Lua中始終保留某個C#層對象的引用,將會致使其沒法被釋放,當這樣的引用愈來愈多,就會致使C#層的內存泄漏。
其中比較常見的例子即是:應該被申明爲local的對象忘記寫local。
function main() cube = GameObject.CreatePrimitive(PrimitiveType.Cube) ......
而後切換場景,是用工具檢測獲得被引用對象爲:Cube,如圖:
具體引用鏈爲:
當切換場景時,雖然場景中沒有了Cube對象,但對象池中還有,致使仍然有引用而沒法GC。此時Cube對象是一個做爲UnityEngine.Object爲空,而做爲System.Object不爲空的對象,緣由就是Lua對其的引用不爲空,會致使泄漏。解決方案也較爲簡單,將Cube變量申明爲local局部變量,解除引用便可:
function main() local cube = GameObject.CreatePrimitive(PrimitiveType.Cube) ......
更多實戰例子能夠閱讀:https://zhuanlan.zhihu.com/p/89912209
4、Diff兩個不一樣時期的Lua變量
對兩個時期的Lua State作兩次完整的快照,經過比較兩次快照的數據,能夠獲得相關增長與減小的變量。獲得疑似泄露的地方。經過快照獲取到的引用鏈定位泄漏的變量。
該工具獲得引用鏈中:_G表示全局表,_R表示被C#引用的對象
引用鏈
想要更加深刻了解該工具、深刻了解Lua性能優化方案的讀者,推薦閱讀:
一、做者書寫的工具介紹與性能檢測思路的文章,其中詳細解釋了Lua、Mono雙GC系統、以及Mono對象、Lua對象、Unity對象三者的釋放流程以及Lua、C#、C++整個調用流程結構:https://www.zhihu.com/question/307064711
二、UWA Blog中對於Lua性能優化的文章:
《Lua性能優化—Lua內存優化》
《Lua的CPU開銷性能優化》