2015年1月22日 星期四

使用xperf解決Unity5測試版貼圖匯入過慢問題

作者:ARAS PRANCKEVIČIUS 原文連結
翻譯:十月,Deezy

這篇文章是Unity的工程師Aras碰到一個Unity5 Beta版本中貼圖匯入緩慢的Bug,於是他嘗試去解決問題的過程。文章中提及的xperf是Windows下的檢測軟體,Mac下您可以使用Apple's Instruments。

Aras看到一個bug報告:Unity5.0測試版中貼圖匯入出奇的慢。其中一個貼圖的匯入花了比平常多10倍的時間。為什麼貼圖匯入會如此慢?這份bug報告讓他第一次使用了xperf(又名Windows性能工具檢測器)。

故事是這樣的

我們得到一個案例報告,在最新的Unity5.0測試版中一個TGA格式的貼圖(2048*2048,12mb未壓縮)匯入花費了10秒,但是在Unity4.6只需要大約1秒。

第一個猜測:是不是有人不小心關閉了貼圖的多線程壓縮?
第二個猜測:我們使用FreeImage來匯入貼圖。也許有人動到了版本?

首先,我們將使用profiler檢測問題所在。


我們發現所有的時間都花在WinAPI ReadFile函式上?!或測試的TAG文件有什麼特別?讓我們重新使用一個同樣大小,未被壓縮的PNG再次嘗試。

PNG的匯入花費了108ms,而TGA花費是9800ms。(我已經禁用了DXT壓縮,以確保只會影響匯入時間。)在Unity4.6中,同樣的測試是116ms(PNG)和310ms(TGA)。文件大小大致相同。

使用xperf

於是我咨詢一位深諳Windows的同事:“為什麼讀取一個文件,ReadFile函數會花費如此多的時間,但是另一個同樣大小的文件卻讀取非常快?” 他給出答案,“你應該嘗試使用xperf來看看”。

設定Windows性能記錄器,勾選“CPU usage”、“Disk I/O activity”和“File I/O activity”,然後點擊“Start”。

在Unity中匯入貼圖,點擊“Save”,然後“Open in WPA”按鈕.

側欄給出了各項指標的統計圖。一個詭異的現像是:無論是CPU還是存儲的圖都沒顯示出什麼消耗的活動?感覺問題變得棘手起來!


CPU使用情況分析

雙擊Computation的圖,顯示出每一個進程的CPU使用時間曲線。查看Unity.exe在圖中亮的那段時間內的CPU占用情況。


下一件事,需要了解具體是什麼占用CPU。右擊左側的黃色分配器,然後選擇需要的訊息,工具會根據我們的選擇,來排列右側顯示的細節。現在需要注意的是呼叫樹狀結構。所以,在右鍵選單中選擇“Stack”:


噢,現在對了。得到有用的訊息,我們需要告訴xperf載入對應的訊息。所以需要去Trace->Configure Symbols Paths,增加一個Unity文件夾,然後Trace->Load Symbols。經過一段時間的等待,就可以得到我們要的資訊了。

然後你得到呼叫堆疊的資料!不太確定"n/a"代表什麼,我猜是一些未使用或休眠的CPU線程之類的

進入另一個標簽呼叫堆疊,我們看到所有的時間果然是花費在ReadFile。可是我們已經透過profiler知道了這一點。


查看I/O 使用

還記得這個側欄中的“Storage”圖沒有顯示什麼消耗性活動?現在,我們來展開它獲得更多的細節。


突然發現有點眉目了!當我們匯入TGA文件的時候,“File I/O”的總圖上顯示出了劇烈的活動。現在只需要來搞清楚到底發生了什麼。
雙擊側邊欄的圖來獲取更詳細的I/O訊息:

現在您也許看到了發生什麼事。我們讀取了一堆文件,實際上約有400,000之多。

同CPU部分一樣,工具是通過左側的黃色的分配器的訊息來組織列訊息。把“Process”拖拽到左側;下圖表明了所有的這些讀取的確都是由Unity觸發的。


展開來看看誰是真正的罪魁禍首:

原來我們正在開啟的這些圖,一次只讀取3個bytes。

Bug是怎麼出現?怎麼解決?

但是為什麼我們三個bytes三個bytes的讀取一個12mb的TGA文件?我們已經好久沒更新過圖片文件讀取的流程了,那這些bug是哪來的?

在程式碼中找到我們讀取進FreeImage的地方。看來我們正在建立自己的I/O函數,然後給FreeImage使用。


檢查了歷史版本:果然,一些星期前,這段程式碼發生了一些改變,從原本的“從文件路徑載入圖片” 變成了 “使用這些I/O回call函數載入圖片”。

一般這些改動都是有意義的。如果我們有自己的文件系統函數,合理的使用就可以讀取一些特別文件。(例如封存檔或者壓縮文件等。)在這個案例中,我們是為了支援在光照貼圖上暫存LZ4壓縮。(FreeImage不需要知道他們頭部是否存在LZ4壓縮,能直接匯入貼圖文件。)

本來好好的。但是這個改變卻導致了一個完全意外的性能bug。

當你不直接傳遞I/O過程到FreeImage,他會使用C的標准輸入輸出作為預設設置。


C的標準流程預設做I/O緩衝……我們的流程沒做。所以FreeImage的TAG載入函數做了大量的讀取操作,一次讀取一個像素。

解決方案

給FreeImage設一個帶有緩衝的I/O流程。

需要確認的是,是否這就是真正的罪魁禍首,修好後不會再出現“TGA文件匯入太慢“,所以我做了一個修補程序,直接讀取整個文件到記憶體,從記憶體進行載入。


一次讀取整個圖片到記憶體好嗎?視情況而定,我猜測95%的情況下是好的,尤其64bit的Unity編輯器。對於大多數未壓縮的圖像文件,最終數據大小會比文件大很多。也許唯一的例外是.PSD文件,它會攜帶很多圖層訊息,但是我們只對“合併圖像”感興趣。所以,這也是為什麼我說“hotfix”。要解決這問題是要寫一個支援有緩衝的I/O流程,或者將FreeImage升級。

這樣匯入TGA和PNG會比以前快很多:TGA花費75ms,PNG花費87ms。(Unity 4.6中TGA是310ms,PNG是116ms,當前測試版中TGA是9800ms,PNG是108ms。)耶~

結論

當使用你自己的方法來取代內建的函數庫時需要謹慎。(例如標準的輸入輸出,記憶體分配,登入,或者函數庫的流程。)他們可能會有不同的性能特性。

xperf是一套在Windows上很棒的軟體!可以看看Bruce Dawson的部落格更多的說明(英文)

如果要在Mac上檢測,Apple’s Instruments是一套很像xperf的工具,未來我也會試試看

我當初看到報告的時候早該想到這應該是太多的檔案讀取呼叫所導致的問題,沒任何藉口,但我下次就知道了!

沒有留言:

張貼留言

著作人