Unity3D中使用Profiler精確定位性能熱點的優化技巧


本文由博主(SunboyL)原創,轉載請注明出處:http://www.cnblogs.com/xsln/p/BeginProfiler.html

簡介

    在使用Profiler定位代碼的性能熱點時,很多同學往往忽略Profiler的提供接口,當發現某個Update函數特別耗時時,沒有有效的手段進一步定位熱點出自該Update函數的哪一個模塊或哪一段代碼。 

    使用Profiler評估客戶端性能時,推薦使用Profiler提供的性能采樣接口,來更精確地分析定位客戶端存在的性能問題。

 
    舉個例子說明精確定位的優勢:
    測試源代碼:
 1 using UnityEngine;
 2 using System.Collections;
 3 using System.Collections.Generic;
 4 
 5 public class TestProfiler : MonoBehaviour {
 6     int t = 10000;
 7     
 8     // 每幀Update都會進行校驗和運行
 9     void Update () {
10         Check(t); // 校驗
11         Run(); // 運行
12     }
13 
14     void Check(int n) {
15         ProfilerSample.BeginSample("Check");
16         CheckA(); // 校驗模塊A
17 
18         ProfilerSample.BeginSample("Calculate b");
19         // 數值運算
20         int b = n - 100;
21         if (b < 10)
22             b = 10;
23         ProfilerSample.EndSample();
24 
25         CheckB(b); // 校驗模塊B
26         ProfilerSample.EndSample();
27     }
28 
29     void CheckA() {
30         ProfilerSample.BeginSample("CheckA");
31         Debug.Log("校驗模塊A");
32         ProfilerSample.EndSample();
33     }
34 
35     void CheckB(int loopCount) {
36         ProfilerSample.BeginSample("CheckB(loopCount={0})", loopCount);
37         Debug.Log("校驗模塊B");
38 
39         ProfilerSample.BeginSample("new List<string>");
40         List<string> strList = new List<string>();
41         ProfilerSample.EndSample();
42 
43         for (int i = 0; i < loopCount; ++i) {
44             ProfilerSample.BeginSample("Add str to list");
45             string str = string.Format("CheckB:{0}", i);
46             strList.Add(str);
47             ProfilerSample.EndSample();
48         }
49 
50         Debug.Log(string.Format("list count: {0}", strList.Count));
51         ProfilerSample.EndSample();
52     }
53 
54     void Run() {
55         ProfilerSample.BeginSample("Run");
56         Debug.Log("開始運行");
57         DoSomething();
58         ProfilerSample.EndSample();
59     }
60 
61     void DoSomething() {
62     }
63 
64     void OnGUI() {
65         GUILayout.BeginVertical();
66         if (GUILayout.Button("Enable/Disable ProfierSample.")) {
67             ProfilerSample.EnableProfilerSample = !ProfilerSample.EnableProfilerSample;
68         }
69 
70         if (GUILayout.Button("Enable/Disable Profier sting format.")) {
71             ProfilerSample.EnableFormatStringOutput = !ProfilerSample.EnableFormatStringOutput;
72         }
73     }
74 }
View Code

    關閉和開啟Profiler性能采樣接口的對比截圖:

   
    如上圖,使用性能采樣接口,可以精確發現該Update的耗時熱點在Check函數下CheckB函數中,主要由List.Add()導致,被調用了9900次。
    使用Profiler.BeginSample、Profiler.EndSample配對,可以有效定位用戶自己編寫的代碼的性能熱點。
   
 
除此之外,我封裝了一套自己的接口,代碼在本文最后面。之所以封裝一層,原因如下:
    1、提供Profiler性能采樣開關,可隨時關閉
    2、提供字符串格式化功能,可在Profiler中顯示自定義的文本,方便定位問題( 使用時需要謹慎,后文敘述
 
關於格式化字符串
    有時候光知道熱點代碼位置是不夠的,還需要知道代碼中變量數據
    例如處理網絡協議的OnRecv函數,該函數會根據不同的協議號調用不同的委托函數。此時,我們就 可以在Profiler窗口中輸出協議號、或者委托的具體函數名,以方便我們定位具體熱點。
   
 
慎用格式化字符串
    如果在Update函數中使用格式化輸出,很有可能該Update函數每隔一段時間就會出現一次GC.Alloc。但這可能不是由於Update的實際操作導致,而是由於使用性能采樣時的格式化輸出導致的。
    需要注意格式化字符串本身會帶來內存分配開銷,使用格式化字符串采樣接口時需考慮自身對代碼帶來的影響。
 
使用經驗:
1、在可能的熱點函數上插入性能采樣代碼,建議 編譯手機版本來分析結果。當然,在熟悉代碼的前提下,可以方便使用PC測試分析GC Alloc等問題。原因如下:
    1)PC性能相對太好,一些手機上的瓶頸函數在PC上幾乎不耗時,導致無法准確分析;
    2)一些代碼,特別是插件代碼,PC和手機的執行流程不同,PC分析的結果不能准確表明手機也是同樣結果。
2、 在插入性能采樣代碼時,特別留意函數中是否存在多個return的現象。這些return如果沒有處理好,就有可能導致性能采樣的Begin和End不匹配,導致Profiler顯示的結果有誤。
3、 對於協程函數,BeginSample、EndSample之間注意不能存在yeild return null,否則可能導致Unity客戶端卡死、手機卡死等現象。個人分析:Begin和End配對分析的是單幀結果,出現yeild return null代表該區間將會分兩幀甚至多幀完成。
 
封裝好的性能采樣接口代碼(ProfilerSample.cs):
 1 using UnityEngine;
 2 using System;
 3 
 4 public class ProfilerSample { // by SunboyL
 5     public static bool EnableProfilerSample = true;
 6     public static bool EnableFormatStringOutput = true;// 是否允許BeginSample的代碼段名字使用格式化字符串(格式化字符串本身會帶來內存開銷)
 7 
 8     public static void BeginSample(string name) {
 9 #if ENABLE_PROFILER
10         if (EnableProfilerSample){
11             Profiler.BeginSample(name);
12         }
13 #endif
14     }
15 
16     public static void BeginSample(string formatName, params object[] args) {
17 #if ENABLE_PROFILER
18         if (EnableProfilerSample) {
19             // 必要時很有用,但string.Format本身會產生GC Alloc,需要慎用
20             if (EnableFormatStringOutput)
21                 Profiler.BeginSample(string.Format(formatName, args));
22             else
23                 Profiler.BeginSample(formatName);
24         }
25 #endif
26     }
27 
28     public static void EndSample() {
29 #if ENABLE_PROFILER
30         if (EnableProfilerSample) {
31             Profiler.EndSample();
32         }
33 #endif
34     }
35 }

 

 


免責聲明!

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



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