Unity學習-優化_卡頓原因定位以及優化方案


除了Unity的一些組件優化技巧之外,更多的細節處於代碼層面上

最近學習優化,看到一篇文章,寫的很詳細,從底層原理到我們

的實際處理,都有一些非常好的建議,可以推薦給小伙伴們看看

https://www.jianshu.com/p/289de89a6609

===========如何定位程序的哪一個環節產生了過大的開銷============

使用Uinty的Profiler工具,可以比較精准快速的定位程序的哪一個位置產生了大開銷

首先在build setting里面勾選Autoconnect Profiler

然后在windows欄選擇proflier即可

 

打開之后即可監控游戲每一幀的運行狀態了,可以根據FPS和每一幀運行的時間判斷游戲的卡頓程度

確定某一幀的占用率過高之后

 

點擊波狀圖,就會暫停游戲

下方就會列出當前游戲運行的消耗詳細信息了

跟進total欄的百分比,即可定位是哪個位置產生了最大的消耗,從而進行相應優化了

這個工具是用來定位優化點的,具體的優化方案可以參考下面的優化方式

由於篇幅有限,而且這個工具應該大部分人都知道而且會用,就不過多贅述了。

不明白的可以參閱這篇作者的 https://www.jianshu.com/p/a7cee5e548cf 

寫的非常詳細

==================代碼層優化====================

一、內存管理

  1:GC原理

      C#的垃圾回收是自動托管的,垃圾回收系統也有一套生命周期和統計流程,下面就是關於GC的整體流程:

    1)一次GC的過程分為2個階段:

                標記清除階段,GC會假設堆中所有對象都可以被回收,然后找出不能回收的對象,打上清除標記,剩下的就是要被回收的了。找的過程就是檢查對象  有沒有被其他的對象引用的過程,如果這個對象在程序中沒有被引用到,那么就會被打上清除的標記。

               重新地址排列階段,標記清除的對象被清除之后,堆里面的空間就會變成不是連續的了,GC的第二部就會開始重新排列還存在的對象,使堆中的地址  分配變成連續的。

    2)整個.net的GC流程:  

                在進行.Net的GC階段,是不止一次GC操作的,C#采用了分代算法,先對程序里面的內存進行分代管理,再根據不同的代,進行不同力度的清理。

這里面的生命周期為3代, 第0代是新創建的代碼,在達到了0代集合的閾值之后,觸發0代的GC,幸存的對象會進入1代集合, 同理,在1代集合達到了閾值    的時候,也會進行GC,但是這次GC是 0代和1代一起執行,以此類推,2代集合GC的時候也會進行1代 2代的CG操作。按消耗量的比例應該是 1:10:100

這種分代算法是基於 老的對象生命周期一般都比新的對象生命周期長,就像公司的員工一樣,時間較長的員工公司看來一般都比新進來的員工穩定性要大。

      2:優化策略

    明白了大致GC的流程之后會發現,GC會消耗大量的CPU性能,因為這里面會經歷很多次的運算以及遍歷等

    接下來是弄清楚GC什么時候被觸發,以及如何規避影響用戶體驗的GC操作

    何時會觸發GC?
    三種情況下會觸發GC:

    1:跟進分代算法,在容量達到閾值的時候會發生,

    2:GC會不時的自動運行(頻率因平台而異)。

    3:手動強制調用GC

        大致的優化思路就是 降低每一次GC的運行時間(減少垃圾對象,使GC的過程中盡量少的遍歷),降低GC的頻率(降低觸發GC機制的次數),在加載地圖等需要用戶等待的游戲流程里面主動GC。

     接下來就是跟進3種觸發機制做我們代碼上的優化了

  1)、盡量少new不必要的字段

 1 object obj = null;
 2 update()
 3 {
 4      object = somebody;
 5 }
 6 
 7 update()
 8 {
 9     object obj =somebody;
10 }

      上面的賦值方式只會在開辟一個內存空間,第二種會反復開辟內存空間,這些空間一般在一代GC里面就會被釋放掉。屬於最無用的代碼方式(沒必要的  情況下)。

  2).使用對象池

       對象池的使用會大大降低新內存空間的使用,他會在一個內存空間反復給新的值。

       3).盡量使用緩存機制,少使用Instantiate實例化新對象,因為這里Unity會初始化他身上的組件以及各種序列化的操作,各個物體會根據自身的組件,創建耗時都  不相同

   

       4).字符串的操作

            string的拼接操作是在內部重新new 一個新的出來,因為string在C#是不可變更到,所以在每一次的+= 就相當於new了一個新的字符串出來,如果出現比較頻繁   的拼接操作,stringBuilder會比string 更好,但是string在對字符串的操作上會比stringBuider好,至於使用哪一種就看具體的需求了。

       5).在地圖加載等需要等待的過程中主動進行GC。

避免內存消耗還有很多的方式,我也是剛剛開始比較全面的學習性能優化,在以上也是我在網上搜集了一些自己能理解的處理方式。

CPU性能管理:

  一、代碼層:

     1、 盡量避免空的Update(),只要寫了Update(),不管是不是空的,Unity都會去執行,這里會增加開銷

     2、Find  getcomponent 等查找的方法,Unity都會去遍歷場景對象和組件,在大型項目中,場景對象一旦變多就會產生很大的開銷了,盡量在start里面調用而不  是update里面反復使用

         3、Update里面盡量少做遍歷,一次UpDate 就會遍歷一次,這是一個很大的開銷了。

         4、在業務需求不影響的情況下,可以讓Update里面的邏輯使用計時器,增加間隔,1秒調用一次,2秒調用一次等。

         5、在可以知道觸發條件的情況下,盡量使用委托的方式處理觸發效果,而不是一次次的遍歷目標的觸發狀態

         6、和上面一樣,盡量在設計代碼的時候,使用觀察者模式,理論上來說,游戲的大部分邏輯都可以使用觸發后再執行,特別是UI。(在使用lua熱更新的項目      中,大部分的游戲邏輯流程都是通過事件消息的觸發來完成的,因為里面沒有Uinty那么專業的生命周期)

        7、for 和 foreach 的取舍 :

    在固定長度或長度不需要計算的時候for循環效率高於foreach.

    在不確定長度,或計算長度有性能損耗的時候,用foreach比較方便

 二、渲染層:

      渲染層的優化包括 圖集的結構設計、模型的處理、LOD、mipMap、烘焙等、陰影處理

     圖集的結構設計是為了減少額外的draw call,盡量以模塊划分,因為原則上每個模塊之間的UI元素是不會互相耦合的。Unity的texture的大小是根據2的冪來計算到。如果你的圖片真實大小是1025,那么他會創建一塊2048的texture,也是浪費開銷的行為。

模型的處理要結合LOD的使用,在固定視角、固定攝像機深度的游戲中,LOD的發揮效果不是很大,更多的是制作模型的時候就確定了模型精度

    LOD和MipMap的使用會帶來很大的性能提升,但是會受項目影響,具體看項目而定

    烘焙就是在游戲發布之前,場景融合光照提前產生新的貼圖,使這個場景不用進行動態光照計算 就可以達到光照的效果,降低了CPU計算的開銷,但是對用戶的體驗肯定沒有實時光照那么好,一般都是出現在手游中

陰影和烘焙的道理差不多,為了降低實時光照的計算量,可以將陰影設置為假陰影,陰影在是圓的情況,不會產生旋轉的變化,同時因為烘焙的原因,光線沒有入射角的變化,也不會產生陰影的投射角度變化,也是在優化性能上比較常用的方式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM