91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

[Unity 3D] Unity 3D 性能優化 (一)

發布時間:2020-08-01 20:45:16 來源:網絡 閱讀:268 作者:蓬萊仙羽 欄目:開發技術

聽到過很多用Unity 3D開發游戲的程序員抱怨引擎效率太低,資源占用太高,包括我自己在以往項目的開發中也頭疼過。最近終于有了空閑,可以仔細的研究一下該如何優化Unity 3D下的游戲性能。其實國外有不少有關U3D優化的資料,Unity官方的文檔中也有簡略的章節涉及這方面的內容,不過大多都是以優化美術資源為主,比如貼圖的尺寸,模型靜態及動態的batch以減少draw call,用lightmap替代動態光影,不同渲染模式在不同環境下的性能等等。鑒于此,加上美術資源方面的東西本人不是特別了解,所以都撇開不談,這里先試著分析分析U3D腳本中常用代碼段的執行效率


GetComponent


這是一個U3D腳本中使用頻率最高的函數之一,這一族函數包括GetComponent,GetComponents,GetComponentInChildren,GetComponentsInChildren以及他們的泛型版本,此外GameObject類以及Component類上的很多屬性也可以歸于這一范疇,比如Component類的gameObject屬性,GameObject類和Component類都有的transform屬性等等這一系列從GameObject實例以及Component實例上獲取其他掛載的內建組件的屬性接口。

先來看看GetComponent函數的幾種重載形式:

[csharp] view plaincopy
  1. Component GetComponent(Type type);  
  2. T GetComponent<T>() where T : Component;  
  3. Component GetComponent(string type);  

通過ILSpy查看UnityEngine部分源碼,發現泛型形式的GetComponent其實不過是在函數體中對泛型類型T調用了typeof,然后就直接調用了非泛型形式的GetComponent,因此在此不對泛型形式的GetComponent函數做討論。下面設計一個小實驗來看看兩種不同GetComponent函數的效率,以及對GetComponent的不同使用方式會帶來什么樣的影響:


設計實驗——實驗執行的主要過程是對同一個gameObject連續獲取同一類型的Component 8×1024×1024次,統計不同方法下的時間開銷,單位是毫秒。在實驗用的gameObject上一共掛在了五個各不相同的組件,所有的實驗操作都是獲取這五個組件中的第一個。

方案一,最直接的方式,直接在循環中對gameObject調用GetComponent(Type type)方法;

方案二,同樣直接的方式,直接在循環中對gameObject調用GetComponent(string type)方法;

方案三,在循環外事先以GetComponent獲取gameObject上的Component并緩存引用,然后在循環中直接訪問緩存的引用;

方案四,利用C#擴展方法,對GameObject類添加擴展方法,以一個靜態字典Dictionary<GameObject, Component>存儲gameObject和gameObject上要取用的Component的鍵值對,然后在擴展方法里做字典查詢以獲得Component;

實驗結果——方案一約1700ms,方案二約18500ms,方案三約30ms,方案四約1500ms。

(可能有人會對方案四抱有懷疑,擔心字典中gameObject數量會影響查詢效率,雖然我可以直接告訴你正常游戲里可能同時存在的GameObject數據量下對字典查詢根本沒有能夠被覺察到的影響,但還是以數據來說明問題:

繼續設計子實驗,針對方案四,調整場景中gameObject的數量,每個gameObject上都掛載上述實驗里的五個組件,并且都向字典中注冊,對每種gameObject數量的情況都執行上述實驗里的8×1024×1024次組件訪問。

子實驗結果——1個gameObject時約1500ms,5個gameObject時約1500ms,10個gameObject時約1500ms,100個gameObject約1500ms,1000個gameObject時約1500ms,10000個gameObject時還是約1500ms,此時向字典中注冊所消耗的時間已經遠遠大于之后進行的循環的消耗。其實熟悉C#字典表的人根本不會有疑問,字典是散列表,查詢復雜度O(1)。)


由上述實驗可以得出結論,如果要獲取一個gameObject上掛載的某個組件,在邏輯允許或者架構允許的情況下盡量事先緩存這個組件的引用,這是最高效的做法,開銷可以忽略不計;假如情況不允許事先緩存引用,那么在調用頻率不是很頻繁的情況下可以使用GetComponent<T>()或者GetComponent(Type type)的重載形式;如果確實調用比較頻繁,那么最好是自己對GameObject或者Component類進行擴展,以字典查詢代替每次的GetComponent調用,畢竟效率稍微高那么一點點(當然了,如果組件是動態的,那么這個辦法就不適用了,還是乖乖的用GetComponent);而GetComponent(string type)這個重載如無必要就不要使用,因為它每次調用時都必須進行類型反射,以至于效率只有另外兩個重載形式的十分之一不到,即便是只能以字符串的形式得知所需組件的類型,也可以事先手動進行類型反射,而不是在頻繁的GetComponent時直接傳遞字符串參數,只有一種情況下不得不使用GetComponent(string type)這個重載形式,那就是:每一次調用前都只能以字符串的形式的到組件類型,而且每一次調用前所獲得到的組件類型是無法預測的,這中情況下手動做類型反射跟直接調用GetComponent沒有區別。



看完GetComponent族函數之后,接下來就是GameObject類和Component類內置的組件訪問屬性。

在實際腳本代碼編寫中,你是否經常這樣一長串代碼就輕易寫出來了:

[csharp] view plaincopy
  1. Vector3 pos = gameObject.transform.position;  
  2. gameObject.collider.enabled = false;  

以我們的直覺,GameObject類和Component類所提供的這些屬性應該都是直接訪問的事先緩存好的組件引用,因此對這些屬性的使用便無所顧忌。但是事情真的是如我們所想的那樣嗎?如果我告訴你,有時候哪怕是用GetComponent函數的string參數形式都會比使用這些屬性來的要快,你相信么?還是用實驗數據說話吧。


設計實驗——對某gameObject上的Transform組件,采用不同的方法,訪問8×1024×1024次。

方案一,實現緩存gameObject上transform組件的引用,然后所有訪問都直接取用緩存的引用;

方案二,在腳本中直接以Component類的transform屬性調用的方式訪問(U3D腳本都是從MoniBehaviour類派生,而MonoBehaviour又派生自Component類,所以在腳本中可以直接訪問transform屬性,這一點相信很多人都知道);

方案三,在腳本中以gameObject.transform的形式訪問組件(注意哦,很多人都有這個習慣,覺得組件是gameObject的組件,所以訪問時都喜歡加上gameObject);

方案四,在腳本中以GetComponent<Transform>()函數訪問組件;

實驗結果——方案一約30ms,方案二約550ms,方案三約850ms,方案四約1700ms。


吃驚吧?transform屬性訪問的開銷居然比直接訪問引用要大這么多!而且通過gameObject轉一道手之后開銷居然又增加了這么多!不過還好,直接屬性調用還是比用GetComponent要快的多……別太早下結論,Transform組件在每個GameObject實例上都有,對它的訪問是不會失敗的,那么如果被訪問的組件在GameObject上不存在的時候呢?比如訪問一個Rigidbody組件,而gameObject上沒有掛載這樣的組件,這時有會怎樣?接著看實驗。


設計實驗——嘗試對某gameObject上的Rigidbody組件進行訪問8×1024×1024次。

方案一,gameObject上確實掛載了Rigidbody組件,事先緩存組件的引用,訪問時取用緩存的引用;

方案二,gameObject上確實掛載了Rigidbody組件,腳本中以Component類的rigidbody屬性訪問組件;

方案三,gameObject上確實掛載了Rigidbody組件,腳本中以gameObject.rigidbody的方式訪問組件;

方案四,gameObject上確實掛載了Rigidbody組件,腳本中以GetComponent<Rigidbidy>()訪問組件;

方案五,gameObject上沒有Rigidbody組件,事先緩存組件(當然獲取到的是null),訪問時取用引用;

方案六,gameObject上沒有Rigidbody組件,腳本中以Component類的rigidbidy屬性訪問組件;

方案七,gameObject上沒有Rigidbody組件,腳本中以gameObject.rigidbody方式訪問組件;

方案八,gameObject上沒有Rigidbody組件,腳本中以GetComponent<Rigidbody>()訪問組件;

實驗結果——方案一約30ms,方案二約800ms,方案三約1200ms,方案四約1700ms,方案五約30ms,方案六不少于60000ms,方案七不少于60000ms,方案八約1700ms。


更吃驚了吧?這一次的實驗,前四組跟上一次實驗差別不太大,但對rigidbody屬性的訪問還是要比transform屬性慢了一點,后四組數據才是吃驚的根源,在組件不存在的情況下,通過屬性訪問組件居然會有如此大的額外開銷!相比之下,GetComponent方法倒是不在乎組件是否真的存在,開銷一如既往。

由于屬性實現的代碼無法通過ILSpy查看,所以在這里我只能用猜的了。首先是,U3D在實現這些組件訪問屬性的時候,必然做了各種查詢和容錯處理,絕非簡單的緩存和取用引用那么簡單,這也是屬性訪問比事先緩存引用的訪問方式要慢那么多的原因;其次,Transform組件在每個GameObject實例上都必然存在,因此transform屬性的實現比其他組件訪問屬性的實現必然要少那么一些步驟,這就造成對transform屬性的訪問要比其他組件屬性快上一些;最后,當組件不存在時,對組件屬性的訪問應該是走入了大量的容錯處理代碼,這就造成這種情況下屬性訪問開銷大增。

從這個實驗又可以得出結論,我們的腳本代碼里經常會需要訪問gameObject引用或者某個組件的引用,最好的方式當然是在腳本Awake的時候就把這些可能訪問的東西都緩存下來;如果需要訪問臨時gameObject實例的某屬性或者臨時某組件的gameObject實例,在能夠確保組件一定存在的情況下,可以用屬性訪問,畢竟它們比GetComponent要快上一倍,但是如果不能確定組件是否存在,甚至是需要對組件的存在性做判斷時,一定不要用對屬性訪問結果判空的方式,而要用GetComponent,這里面節省的開銷不是一點半點。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

un %d
AI

汝南县| 恭城| 德昌县| 和平区| 阜平县| 台山市| 新丰县| 定陶县| 溧阳市| 丰县| 永胜县| 莎车县| 洛川县| 昆山市| 沙田区| 洛阳市| 聂荣县| 阿城市| 桐柏县| 大兴区| 右玉县| 嵊州市| 资阳市| 嘉峪关市| 固始县| 武陟县| 夏津县| 封丘县| 泰安市| 桦川县| 杭锦旗| 清镇市| 汝南县| 定远县| 阜康市| 长葛市| 互助| 图木舒克市| 临高县| 合作市| 辽中县|