免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
用好lua+unity,讓性能飛起來



前言









在看了uwa之前發(fā)布的《Unity項(xiàng)目常見Lua解決方案性能比較》,決定動(dòng)手寫一篇關(guān)于lua+unity方案的性能優(yōu)化文。

整合lua是目前最強(qiáng)大的unity熱更新方案,畢竟這是唯一可以支持ios熱更新的辦法。然而作為一個(gè)重度ulua用戶,我們踩過了很多的坑才將ulua上升到一個(gè)可以在項(xiàng)目中大規(guī)模使用的狀態(tài)。事實(shí)上即使到現(xiàn)在lua+unity的方案仍不能輕易的說可以肆意使用,要用好,你需要知道很多。

因此,這篇文章是從一堆簡(jiǎn)單的優(yōu)化建議里頭,逐步挖掘出背后的原因。只有理解了原因,才能很清楚自己做的優(yōu)化,到底是為了什么,有多大的效果。



從最早的lua純反射調(diào)用c#,以及云風(fēng)團(tuán)隊(duì)嘗試的純c#實(shí)現(xiàn)的lua虛擬機(jī),一直發(fā)展到現(xiàn)在的各種luajit+c#靜態(tài)lua導(dǎo)出方案,lua+unity才算達(dá)到了性能上實(shí)用的級(jí)別。

但即使這樣,實(shí)際使用中我們會(huì)發(fā)現(xiàn),比起cocos2dx時(shí)代luajit的發(fā)揚(yáng)光大,現(xiàn)在lua+unity的性能依然存在著相當(dāng)?shù)钠款i。僅從《性能比較》的test1就可以看到,iphone4s下二十萬次position賦值就已經(jīng)需要3000ms,如果是coc這樣類型的游戲,不處理其他邏輯,一幀僅僅上千次位置賦值(比如數(shù)百的單位、特效和血條)就需要15ms,這顯然有些偏高。

是什么導(dǎo)致lua+unity的性能并未達(dá)到極致,要如何才能更好的使用?我們會(huì)一些例子開始,逐步挖掘背后的細(xì)節(jié)。




由于我們項(xiàng)目主要使用的是ulua(集成了topameng的cstolua,但是由于持續(xù)的性能改進(jìn),后面已經(jīng)做過大量的修改),本文的大部分結(jié)論都是基于ulua+cstolua的測(cè)試得出來的,slua都是基于其源碼來分析(根據(jù)我們分析的情況來看,兩者原理上基本一致,僅在實(shí)現(xiàn)細(xì)節(jié)上有一些區(qū)別),但沒有做過深入測(cè)試,如有問題的話歡迎交流。





既然是lua+unity,那性能好不好,基本上要看兩大點(diǎn):

lua跟c#交互時(shí)的性能如何

純lua代碼本身的性能如何

因?yàn)檫@兩部分都各有自己需要深入探討的地方,所以我們會(huì)分為多篇去探討整個(gè)lua+unity到底如何進(jìn)行優(yōu)化。



lua與c#交互篇



1.從致命的gameobj.transform.position = pos開始說起


像gameobj.transform.position = pos這樣的寫法,在unity中是再常見不過的事情

但是在ulua中,大量使用這種寫法是非常糟糕的。為什么呢?



因?yàn)槎潭桃恍写a,卻發(fā)生了非常非常多的事情,為了更直觀一點(diǎn),我們把這行代碼調(diào)用過的關(guān)鍵luaapi以及ulua相關(guān)的關(guān)鍵步驟列出來(以u(píng)lua+cstolua導(dǎo)出為準(zhǔn),gameobj是GameObject類型,pos是Vector3):

第一步:


GameObjectWrap.get_transform    lua想從gameobj拿到transform,對(duì)應(yīng)gameobj.transform

  LuaDLL.luanet_rawnetobj            把lua中的gameobj變成c#可以辨認(rèn)的id

  ObjectTranslator.TryGetValue      用這個(gè)id,從ObjectTranslator中獲取c#的gameobject對(duì)象

  gameobject.transform                準(zhǔn)備這么多,這里終于真正執(zhí)行c#獲取gameobject.transform了

  ObjectTranslator.AddObject         給transform分配一個(gè)id,這個(gè)id會(huì)在lua中用來代表這個(gè)transform,transform要保存到ObjectTranslator供未來查找

  LuaDLL.luanet_newudata            在lua分配一個(gè)userdata,把id存進(jìn)去,用來表示即將返回給lua的transform

  LuaDLL.lua_setmetatable            給這個(gè)userdata附上metatable,讓你可以transform.position這樣使用它

  LuaDLL.lua_pushvalue                返回transform,后面做些收尾

  LuaDLL.lua_rawseti

  LuaDLL.lua_remove

第二步:


TransformWrap.set_position                     lua想把pos設(shè)置到transform.position


  LuaDLL.luanet_rawnetobj                       把lua中的transform變成c#可以辨認(rèn)的id

  ObjectTranslator.TryGetValue                 用這個(gè)id,從ObjectTranslator中獲取c#的transform對(duì)象

  LuaDLL.tolua_getfloat3                          從lua中拿到Vector3的3個(gè)float值返回給c#

     lua_getfield + lua_tonumber 3次         拿xyz的值,退棧

     lua_pop

  transform.position = new Vector3(x,y,z) 準(zhǔn)備了這么多,終于執(zhí)行transform.position = pos賦值了


就這么一行代碼,竟然做了這么一大堆的事情!如果是c++,a.b.c = x這樣經(jīng)過優(yōu)化后無非就是拿地址然后內(nèi)存賦值的事。但是在這里,頻繁的取值、入棧、c#到lua的類型轉(zhuǎn)換,每一步都是滿滿的cpu時(shí)間,還不考慮中間產(chǎn)生了各種內(nèi)存分配和后面的GC!

下面我們會(huì)逐步說明,其中有一些東西其實(shí)是不必要的,可以省略的。我們可以最終把他優(yōu)化成:


lua_isnumber + lua_tonumber 4次,全部完成




2.在lua中引用c#的object,代價(jià)昂貴


從上面的例子可以看到,僅僅想從gameobj拿到一個(gè)transform,就已經(jīng)有很昂貴的代價(jià)

c#的object,不能作為指針直接供c操作(其實(shí)可以通過GCHandle進(jìn)行pinning來做到,不過性能如何未測(cè)試,而且被pinning的對(duì)象無法用gc管理),因此主流的lua+unity都是用一個(gè)id表示c#的對(duì)象,在c#中通過dictionary來對(duì)應(yīng)id和object。同時(shí)因?yàn)橛辛诉@個(gè)dictionary的引用,也保證了c#的object在lua有引用的情況下不會(huì)被垃圾回收掉。

因此,每次參數(shù)中帶有object,要從lua中的id表示轉(zhuǎn)換回c#的object,就要做一次dictionary查找;每次調(diào)用一個(gè)object的成員方法,也要先找到這個(gè)object,也就要做dictionary查找。

如果之前這個(gè)對(duì)象在lua中有用過而且沒被gc,那還就是查下dictionary的事情。但如果發(fā)現(xiàn)是一個(gè)新的在lua中沒用過的對(duì)象,那就是上面例子中那一大串的準(zhǔn)備工作了。

如果你返回的對(duì)象只是臨時(shí)在lua中用一下,情況更糟糕!剛分配的userdata和dictionary索引可能會(huì)因?yàn)閘ua的引用被gc而刪除掉,然后下次你用到這個(gè)對(duì)象又得再次做各種準(zhǔn)備工作,導(dǎo)致反復(fù)的分配和gc,性能很差。

例子中的gameobj.transform就是一個(gè)巨大的陷阱,因?yàn)?transform只是臨時(shí)返回一下,但是你后面根本沒引用,又會(huì)很快被lua釋放掉,導(dǎo)致你后面每次.transform一次,都可能意味著一次分配和gc。



3.在lua和c#間傳遞unity獨(dú)有的值類型(Vector3/Quaternion等)更加昂貴



既然前面說了lua調(diào)用c#對(duì)象緩慢,如果每次vector3.x都要經(jīng)過c#,那性能基本上就處于崩潰了,所以主流的方案都將Vector3等類型實(shí)現(xiàn)為純lua代碼,Vector3就是一個(gè){x,y,z}的table,這樣在lua中使用就快了。

但是這樣做之后,c#和lua中對(duì)Vector3的表示就完全是兩個(gè)東西了,所以傳參就涉及到lua類型和c#類型的轉(zhuǎn)換,例如c#將Vector3傳給lua,整個(gè)流程如下:

1.c#中拿到Vector3的x,y,z三個(gè)值

2.push這3個(gè)float給lua棧

3.然后構(gòu)造一個(gè)表,將表的x,y,z賦值

4.將這個(gè)表push到返回值里

一個(gè)簡(jiǎn)單的傳參就要完成3次push參數(shù)、表內(nèi)存分配、3次表插入,性能可想而知。

那么如何優(yōu)化呢?我們的測(cè)試表明,直接在函數(shù)中傳遞三個(gè)float,要比傳遞Vector3要更快。

例如void SetPos(GameObject obj, Vector3 pos)改為void SetPos(GameObject obj, float x, float y, float z)

具體效果可以看后面的測(cè)試數(shù)據(jù),提升十分明顯。












4.lua和c#之間傳參、返回時(shí),盡可能不要傳遞以下類型:


嚴(yán)重類: Vector3/Quaternion等unity值類型,數(shù)組

次嚴(yán)重類:bool string 各種object

建議傳遞:int float double

雖然是lua和c#的傳參,但是從傳參這個(gè)角度講,lua和c#中間其實(shí)還夾著一層c(畢竟lua本身也是c實(shí)現(xiàn)的),lua、c、c#由于在很多數(shù)據(jù)類型的表示以及內(nèi)存分配策略都不同,因此這些數(shù)據(jù)在三者間傳遞,往往需要進(jìn)行轉(zhuǎn)換(術(shù)語parameter mashalling),這個(gè)轉(zhuǎn)換消耗根據(jù)不同的類型會(huì)有很大的不同。

先說次嚴(yán)重類中的bool string類型,涉及到c和c#的交互性能消耗,根據(jù)微軟官方文檔,在數(shù)據(jù)類型的處理上,c#定義了Blittable Types和Non-Blittable Types,其中bool和string屬于Non-Blittable Types,意思是他們?cè)赾和c#中的內(nèi)存表示不一樣,意味著從c傳遞到c#時(shí)需要進(jìn)行類型轉(zhuǎn)換,降低性能,而string還要考慮內(nèi)存分配(將string的內(nèi)存復(fù)制到托管堆,以及utf8和utf16互轉(zhuǎn))。


可以參考https://msdn.microsoft.com/zh-cn/library/ms998551.aspx,這里有更詳細(xì)的關(guān)于c和c#交互的性能優(yōu)化指引。





而嚴(yán)重類,基本上是ulua等方案在嘗試lua對(duì)象與c#對(duì)象對(duì)應(yīng)時(shí)的瓶頸所致。

Vector3等值類型的消耗,前面已經(jīng)有所提及。

而數(shù)組則更甚,因?yàn)閘ua中的數(shù)組只能以table表示,這和c#下完全是兩碼事,沒有直接的對(duì)應(yīng)關(guān)系,因此從c#的數(shù)組轉(zhuǎn)換為lua table只能逐個(gè)復(fù)制,如果涉及object/string等,更是要逐個(gè)轉(zhuǎn)換。




5.頻繁調(diào)用的函數(shù),參數(shù)的數(shù)量要控制


無論是lua的pushint/checkint,還是c到c#的參數(shù)傳遞,參數(shù)轉(zhuǎn)換都是最主要的消耗,而且是逐個(gè)參數(shù)進(jìn)行的,因此,lua調(diào)用c#的性能,除了跟參數(shù)類型相關(guān)外,也跟參數(shù)個(gè)數(shù)有很大關(guān)系。一般而言,頻繁調(diào)用的函數(shù)不要超過4個(gè)參數(shù),而動(dòng)輒十幾個(gè)參數(shù)的函數(shù)如果頻繁調(diào)用,你會(huì)看到很明顯的性能下降,手機(jī)上可能一幀調(diào)用數(shù)百次就可以看到10ms級(jí)別的時(shí)間。



6.優(yōu)先使用static函數(shù)導(dǎo)出,減少使用成員方法導(dǎo)出


前面提到,一個(gè)object要訪問成員方法或者成員變量,都需要查找lua userdata和c#對(duì)象的引用,或者查找metatable,耗時(shí)甚多。直接導(dǎo)出static函數(shù),可以減少這樣的消耗。

像obj.transform.position = pos。

我們建議的方法是,寫成靜態(tài)導(dǎo)出函數(shù),類似

class LuaUtil{

  static void SetPos(GameObject obj, float x, float y, float z){obj.transform.position = new Vector3(x, y, z); }

}

然后在lua中LuaUtil.SetPos(obj, pos.x, pos.y, pos.z),這樣的性能會(huì)好非常多,因?yàn)槭〉袅藅ransform的頻繁返回,而且還避免了transform經(jīng)常臨時(shí)返回引起lua的gc。




7.注意lua拿著c#對(duì)象的引用時(shí)會(huì)造成c#對(duì)象無法釋放,這是內(nèi)存泄漏常見的起因


前面說到,c# object返回給lua,是通過dictionary將lua的userdata和c# object關(guān)聯(lián)起來,只要lua中的userdata沒回收,c# object也就會(huì)被這個(gè)dictionary拿著引用,導(dǎo)致無法回收。

最常見的就是gameobject和component,如果lua里頭引用了他們,即使你進(jìn)行了Destroy,也會(huì)發(fā)現(xiàn)他們還殘留在mono堆里。

不過,因?yàn)檫@個(gè)dictionary是lua跟c#的唯一關(guān)聯(lián),所以要發(fā)現(xiàn)這個(gè)問題也并不難,遍歷一下這個(gè)dictionary就很容易發(fā)現(xiàn)。ulua下這個(gè)dictionary在ObjectTranslator類、slua則在ObjectCache類







8.考慮在lua中只使用自己管理的id,而不直接引用c#的object


想避免lua引用c# object帶來的各種性能問題的其中一個(gè)方法就是自己分配id去索引object,同時(shí)相關(guān)c#導(dǎo)出函數(shù)不再傳遞object做參數(shù),而是傳遞int。

這帶來幾個(gè)好處:

  1.函數(shù)調(diào)用的性能更好;

  2.明確地管理這些object的生命周期,避免讓ulua自動(dòng)管理這些對(duì)象的引用,如果在lua中錯(cuò)誤地引用了這些對(duì)象會(huì)導(dǎo)致對(duì)象無法釋放,從而內(nèi)存泄露

  3.c#object返回到lua中,如果lua沒有引用,又會(huì)很容易馬上gc,并且刪除ObjectTranslator對(duì)object的引用。自行管理這個(gè)引用關(guān)系,就不會(huì)頻繁發(fā)生這樣的gc行為和分配行為。

例如,上面的LuaUtil.SetPos(GameObject obj, float x, float y, float z)可以進(jìn)一步優(yōu)化為L(zhǎng)uaUtil.SetPos(int objID, float x, float y, float z)。然后我們?cè)谧约旱拇a里頭記錄objID跟GameObject的對(duì)應(yīng)關(guān)系,如果可以,用數(shù)組來記錄而不是dictionary,則會(huì)有更快的查找效率。如此下來可以進(jìn)一步省掉lua調(diào)用c#的時(shí)間,并且對(duì)象的管理也會(huì)更高效。



9.合理利用out關(guān)鍵字返回復(fù)雜的返回值


在c#向lua返回各種類型的東西跟傳參類似,也是有各種消耗的。

比如

Vector3 GetPos(GameObject obj)

可以寫成

void GetPos(GameObject obj, out float x, out float y, out float z)

表面上參數(shù)個(gè)數(shù)增多了,但是根據(jù)生成出來的導(dǎo)出代碼(我們以u(píng)lua為準(zhǔn)),會(huì)從:

LuaDLL.tolua_getfloat3(內(nèi)含get_field + tonumber 3次)

變成

isnumber + tonumber 3次

get_field本質(zhì)上是表查找,肯定比isnumber訪問棧更慢,因此這樣做會(huì)有更好的性能。



實(shí)測(cè)


好了,說了這么多,不拿點(diǎn)數(shù)據(jù)來看還是太晦澀

為了更真實(shí)地看到純語言本身的消耗,我們直接沒有使用例子中的gameobj.transform.position,因?yàn)檫@里頭有一部分時(shí)間是浪費(fèi)在unity內(nèi)部的。

我們重寫了一個(gè)簡(jiǎn)化版的GameObject2和Transform2。

class Transform2{

  public Vector3 position = new Vector3();

}

class GameObject2{

   public Transform2 transform = new Transform2();

}

然后我們用幾個(gè)不同的調(diào)用方式來設(shè)置transform的position

方式1:gameobject.transform.position = Vector3.New(1,2,3)

方式2:gameobject:SetPos(Vector3.New(1,2,3))

方式3:gameobject:SetPos2(1,2,3)

方式4:GOUtil.SetPos(gameobject, Vector3.New(1,2,3))

方式5:GOUtil.SetPos2(gameobjectid, Vector3.New(1,2,3))

方式6:GOUtil.SetPos3(gameobjectid, 1,2,3)

分別進(jìn)行1000000次,結(jié)果如下(測(cè)試環(huán)境是windows版本,cpu是i7-4770,luajit的jit模式關(guān)閉,手機(jī)上會(huì)因?yàn)閘uajit架構(gòu)、il2cpp等因素干擾有所不同,但這點(diǎn)我們會(huì)在下一篇進(jìn)一步闡述):

 

方式1:903ms

方式2:539ms

方式3:343ms

方式4:559ms

方式5:470ms

方式6:304ms



可以看到,每一步優(yōu)化,都是提升明顯的,尤其是移除.transform獲取以及Vector3轉(zhuǎn)換提升更是巨大,我們僅僅只是改變了對(duì)外導(dǎo)出的方式,并不需要付出很高成本,就已經(jīng)可以節(jié)省66%的時(shí)間。

實(shí)際上能不能再進(jìn)一步呢?還能!在方式6的基礎(chǔ)上,我們可以再做到只有200ms!

這里賣個(gè)關(guān)子,下一篇luajit集成中我們進(jìn)一步講解。一般來說,我們推薦做到方式6的水平已經(jīng)足夠。

這只是一個(gè)最簡(jiǎn)單的案例,有很多各種各樣的常用導(dǎo)出(例如GetComponentsInChildren這種性能大坑,或者一個(gè)函數(shù)傳遞十幾個(gè)參數(shù)的情況)都需要大家根據(jù)自己使用的情況來進(jìn)行優(yōu)化,有了我們提供的lua集成方案背后的性能原理分析,應(yīng)該就很容易去考慮怎么做了。



下一篇將會(huì)寫lua+unity性能優(yōu)化的第二部分,luajit集成的性能坑

相比起第一部分這種看導(dǎo)出代碼就能大概知道性能消耗的問題,luajit集成的問題要復(fù)雜晦澀得多。




附測(cè)試用例的c#代碼:








public class Transform2{    public Vector3 position = new Vector3();}public class GameObject2{    public Transform2 transform = new Transform2();    public void SetPos(Vector3 pos)    {        transform.position = pos;    }    public void SetPos2(float x, float y, float z)    {        transform.position.x = x;        transform.position.y = y;        transform.position.z = z;    }} public class GOUtil{    private static List<GameObject2> mObjs = new List<GameObject2>();    public static GameObject2 GetByID(int id)    {        if(mObjs.Count == 0)        {            for (int i = 0; i < 1000; i++ )            {                mObjs.Add(new GameObject2());            }        }        return mObjs[id];    }    public static void SetPos(GameObject2 go, Vector3 pos)    {        go.transform.position = pos;    }    public static void SetPos2(int id, Vector3 pos)    {        mObjs[id].transform.position = pos;    }    public static void SetPos3(int id, float x, float y ,float z)    {        var t = mObjs[id].transform;        t.position.x = x;        t.position.y = y;        t.position.z = z;    }}


 



本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
unity3d紅外瞄準(zhǔn)器
Unity3D研究院之將場(chǎng)景導(dǎo)出XML或JSON或二進(jìn)制并且解析還原場(chǎng)景(四十二) | 雨松MOMO程序研究院
Unity從青銅 到 王者!只差這篇讓你學(xué)會(huì)Unity中最重要的部分——腳本組件
unity的相關(guān)技巧
預(yù)設(shè)體 unity3D
Unity3中的攝像機(jī)跟隨主角
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服