這篇會介紹 Unity 內建的 Profiling 工具,Unity 的 Profiling 工具可以分析在 Unity 編輯器裡執行的遊戲,也可以分析裝在機器上的 Unity 遊戲,算是蠻方便的。一般的分析流程跟除錯流程一樣,都是先在編輯器環境排除大部分的問題,然後再安裝到手機上看看有沒有手機特有的問題。不過 Unity 在 GPU 分析上面仍有不足之處,所以之後會介紹手機 GPU 晶片原廠的 GPU Profiler。

一個 Profiling 的基本概念是「測不準原理」,因為 Profiler 要在遊戲裡面不斷做紀錄然後回傳給在電腦上的工具,這樣你才有資料可以看,所以你看到的數字跟實際上玩家接觸的最終版本一定會有落差。你在 Profiler 裡面看到記憶體用了 50 MB ,並不代表實際上在玩家手機上就是會在同一時間點吃 50 MB。所以看 Profile 結果主要是看趨勢或者是比例的。比較新做法是比舊做法好還是差,好的話大概是多少百分比這樣。

Editor Game Window Stats

Editor Stats

首先要介紹的其實不是 Profiler 的一部分,是編輯器遊戲視窗右上角有個 Stats 的按鈕,按下去後會有目前在編輯器裡面跑的遊戲的概略數據。雖然有點陽春,但是 FPS 還有最佳化 Rendering 常常看的 Batch count 這邊有。如果需要個即時概略數據這是還蠻方便的小工具。

Profiler

如果沒有用過 Profiler 的話請到 Window/Profiler 開啟 Profiler 視窗,Unity 4 需要 Pro license ,而 Unity 5 免費版就能用了:

Editor Stats

如果遊戲有在執行打開之後大概是長這個樣子:

Profiler

共用控制 UI

由上面按鈕開始介紹

  • Record
    接收訊息的開關按鈕,Unity Profiler 還蠻吃記憶體的,如果有開 Deep Profile 記憶體的用量會更高。在抓到想要的 frame 之後可以停止接收資料避免 Unity 卡住。

  • Deep Profile
    如果點開這個選項的話所有使用者自己寫的腳本都會被分析,這是最詳細的模式,我自己通常是會開著,但也會讓 Unity 非常的卡。如果不想要分析所有的東西可以用 Profiler.BeginSample 跟 Profiler.EndSample 這兩個 Unity API 來隔出自己想要分析的區間,等一下在 CPU 分析會介紹用法。以 Unity 給的 Angry Bot 範例,在沒有開 Deep Profile 的情況下 CPU 下面的 BehaviourUpdate 只有這樣:
    Profiler No Deep Profile
    可以跟開啟之後的狀態做比較: Profiler Deep Profile

  • Profile Editor
    這個是只有在分析對象是編輯器的時候才有用的選項,會把編輯器 UI 的執行資料也顯示出來。這個用到的機會還蠻少的,除非你是在開發非常消耗效能的編輯器工具。

  • Active Profiler
    這個就是讓你切換你要分析編輯器裡跑的遊戲還是手機上跑的遊戲的地方,如果要分析手機上跑的遊戲遊戲要是 Development build 還要先執行起來再從 Profiler 對接。如果連結手機上的遊戲有問題,一個排除方法是關掉所有 Unity 編輯器視窗然後手動重建 adb tunnel:

    1
    2
    3
    4
    5
    6
    
    adb kill-server
    adb start-server
    
    # 重建 Tunnel,舊版本是 54999,新的都是 port 34999
    # 參考:https://docs.unity3d.com/Manual/profiler-profiling-applications.html
    adb forward tcp:34999 localabstract:Unity-<你的遊戲 bundle ID>
    

    另一個方法是上一篇有提到的改用 TCPIP 模式

    剩下的是控制 Frame 的 UI

    Clear 會把目前保存的資料都清除,左箭頭會移動到上一個 Frame ,右箭頭會移動到下一個 Frame ,Current 會移動到最新的 Frame 。不過最常用的是直接在圖表上點滑鼠,就可以直接定在想看的 Frame 上,如果是編輯器環境的話點下去會同時暫停遊戲。
    Timeline

CPU Usage

點在 CPU 區域就會看到 CPU 相關的資料,顏色代表遊戲這個 Frame 每個元件所花的時間,疊加起來就是這個 Frame 花了多久。注意到最左邊有三個數線分別是 66ms、33ms、16ms,分別代表的就是要達到 15 FPS (一秒 = 1000ms,1000ms / 15 = 66ms)、 30 FPS (1000ms / 30 = 33ms) 跟 60 FPS (1000ms / 60 = 16ms)的話這個 frame 要在多少時間內完成的標準線。雖然說這個叫做 CPU Usage ,但是他有包括「CPU 等 GPU」的時間,所以拿這裡的時間當作總時間算是沒有問題的。但就是 GPU 的詳細資料只有 CPU 準備 Draw call 的資料,GPU 真的在做事的部分就沒有詳細資料了。

CPU

底下幾個大項都看名字可以知道它代表的東西 ,Total 是這個函式跟他呼叫的所有函式(列在這個樹狀結構的子項目裡) 花的時間占總時間的百分比,Self 就是自己但不包含自己呼叫的函式的百分比。Time ms / Self ms 是以絕對時間計算的版本。這些數字應該有取某種近似值,所以自己跟子項目的 Self 加總起來跟 Total 數字不一樣不要太在意。Calls 是被呼叫次數,然後非常重要的是 GC Alloc ,這代表了這個函式呼叫產生了多少需要 GC 的記憶體,GC Allocation 越多,Garbage Collector 需要出來清記憶體的頻率就會越高,會影響到遊戲的流暢度。

一般自己寫的腳本都會歸類在 BehaviourUpdate 項目下,如果沒有開 Deep Profile 的話只會出現大項,除非你自己有使用 Profiler.BeginSample 跟 Profiler.EndSample 來標記你想要分析的範圍,使用方法如下:

1
2
3
Profiler.BeginSample("How long does it take?");
// 這裡有你想要知道跑多久、製造多少 GC 的程式
Profiler.EndSample();

BeginSample 的第一個參數可以自己命名,之後用自己取的名字找資料。試著在 Unity 給的範例裡隨便一段 Update 用 BeginSample 跟 EndSample 框起來,結果看起來會像這樣:

CPU Custom Sample

可以看到樹狀結構多了 Profiler.BeginSample 底下包含了 BeginSample 到 EndSample 之間所有的函式呼叫的資料。我平常會開 Deep Profile 所以不需要自己框每個函式,但如果我懷疑特定部分像是某個迴圈跑太久的時候這個東西就很好用。

然後有兩個很重要跟繪圖有關的大項:

  • WaitForTargetFPS
  • Gfx.WaitForPresent

WaitForTargetFPS 代表事情都做完了在發呆的時間,如果你看到這個就可以收工了,當然在電腦上編輯器裡看到不算,實機才算。Gfx.WaitForPresent 則是 GPU 繪圖用的時間太久了拖到 CPU ,CPU 做完自己的事情後花了多久等 GPU 。網路上有很多「解決」Gfx.WaitForPresent 的教學,像是關掉 VSync 或是 Multi-Threaded Rendering ,但實際上這些都是在特定版本上 Unity 的 bug 造成使用特定功能的時候 GPU 效能突然爆炸。如果不是特定版本的 Unity 的問題,Gfx.WaitForPresent 就是指你的繪圖部分效率不夠該最佳化了。

CPU Usage Timeline

CPU Timeline

在 Unity 5 ,CPU Profiler 加了新的 Timeline 模式,從上圖左上角的選單可以切換過去。這個功能本來主要是要用在 CPU Multi-threading 的分析上面的。但是因為 Unity API 本身不是 Thread-safe ,所以使用者自己開 Thread 不能跟 Unity 互動,能做的事情很少。我自己是拿來只看 Main thread ,還算是個不錯的時間軸圖表,哪個部分用多久還蠻清楚的。美中不足的是很多東西小到要一直放大才看的到名字,縮放的控制就是滑鼠滾輪。

如果要看真的在 Unity 裡面做出 Multi-threading 的範例,可以看看 Unity Japan 這篇 ハードウェア性能を引き出して60fpsを実現するプログラミング・テクニック 演講投影片。懶人包就是:全部自己重寫一遍吧。

Rendering Usage

Rendering

Rendering 這個區塊主要是看 Draw call 跟繪圖記憶體的用量還有頂點數量。這個跟 3D 遊戲最佳化比較有關,但我比較沒有經驗。只是用在 2D 遊戲最佳化上也是有點太過宏觀,我自己並不常用。

GPU Usage

GPU

在 Add Profiler 選單裡面其實還藏了一項 GPU Usage ,它提供了概略的 GPU 使用資訊,但這個好像只能在接在手機上的 Profiler 才能開出來。依照 Unity 的說明,應該是因為開啟這個項目會用掉更多資源,所以預設是隱藏的。不過這邊的資訊分類還蠻粗略的,然後在目前 5.3.5 版本 Per-object breakdown 的名稱顯示不出來 ,所以實用性不高。

Memory Usage

剩下的還有 Audio、Physics、Networking,不過這三個我沒有特別有心得所以就不介紹了。但是最後一個要介紹的 Memory Usage 應該是跟 CPU Usage 不相上下重要的工具。

Memory Simple

在預設的 Simple 模式下就只是個記憶體用量的概略統計,可以比較一下有記憶體洩漏跟正常釋放的走勢,如果看到記憶體像上面那一條一飛衝天就要抓漏了。

Memory Timeline

不過通常的用法是切換到 Detailed 然後按 Take Sample:

Memory Detail

等待一陣子之後 Unity 就會列出所有存在在記憶體裡的資源,它們用了多少空間,如果你是在分析編輯器的話連點每個項目 Unity 都會試著幫你找出在專案資料夾裡的原始檔案。通常如果遊戲有記憶體用量問題,就要從清單裡開始找哪些東西現在應該被釋放掉了但是結果還是存在記憶體裡被 Profiler 抓到。如果你看到不對的項目的話,再看一下右邊的 Reference By 欄位,這邊會跟你說還有哪些元件正在使用這個資源,通常有這些資訊問題就呼之欲出了。

Frame Debugger

這個也是 Unity 5 的新功能,它不是 Profiler 的一部分而在 Window / Frame Debugger 可以找到。它能列出所有 Draw Call 然後一步一步拆解繪圖的過程給你看。

Frame Debugger

像這個例子我停在 polySurface5096 這步,遊戲視窗會顯示這個 Frame 從開始到這步為止的繪圖結果,然後它跟 Profiler 一樣可以去接在在手機上跑的遊戲,這是有必要的,因為有些時候機器上產生的 Draw call 跟編輯器上是不一樣的。

如果有使用到深度或是 Render Target ,下面有選單可以切換,右邊則是 RGBA Channel 開關,最後最下面是這個 Draw Call Shader 用的參數。

這個工具在處理螢幕上出現你無法理解的東西的時候非常好用,你就一步一步往前看那個東西到底是哪一步出現的。同時這也是檢查 Batching 的好工具,如果你覺得應該一起畫的東西在這裡變成兩項,那就是 Batching 失敗。但是這個工具沒有最重要的「每一步用掉了多少 GPU 資源」這項資料,所以最後我們還是會需要別的工具支援。

以上就是 Unity 內建的 Profiler 的概略介紹。

參考資料

The Profiler Window

http://docs.unity3d.com/Manual/Profiler.html

Profiler API

http://docs.unity3d.com/ScriptReference/Profiler.html

gfx.WaitForPresent 討論串

http://forum.unity3d.com/threads/gfx-waitforpresent.211166/