本文由博主(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 }
關閉和開啟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 }