iOS性能優化:Instruments使用實戰

標簽:

采用Instruments 來分析整個應用程序的性能.發現很多有意思的點,以及性能優化和一些分析性能消耗的技巧,小結如下。

Instruments使用技巧

關于Instruments官方有一個很有用的用戶使用Guide,當然如果不習慣官方英文可以在這里找到中文本翻譯版本PDF參閱.Instruments 確實是一個很強大的工具,用它來收集關于一個或多個系統進程的性能和行為的數據極為方便,并能及時跟蹤隨著時間產生的數據.還可以廣泛收集不同類型的數據.關于Instrument工具基本使用不在贅述.如下重點說明一些使用技巧.

1.概覽

工具通過Xcode工具欄中Product->Profile可以啟動,啟動后界面如下:

Instrument概覽[via by chenkai]

當點擊Time Profiler應用程序開始運行后.就能獲取到整個應用程序運行消耗時間分布和百分比.為了保證數據分析在統一使用場景真實行有如下點需要注意:

在開始進行應用程序性能分析的時候,一定要使用真機,模擬器運行在Mac上,然而Mac上的CPU往往比iOS設備要快。相反,Mac上的GPU和iOS設備的完全不一樣,模擬器不得已要在軟件層面(CPU)模擬設備的GPU,這意味著GPU相關的操作在模擬器上運行的更慢,尤其是使用CAEAGLLayer來寫一些OpenGL的代碼時候. 這就導致模擬器性能數據和用戶真機使用性能數據相去甚運.

另外在開始性能分析前另外一件重要的事情是,應用程序運行一定要發布配置 而不是調試配置.

在發布環境打包的時候,編譯器會引入一系列提高性能的優化,例如去掉調試符號或者移除并重新組織代碼.另iOS引入一種"Watch Dog"[看門狗]機制.不同的場景下,“看門狗”會監測應用的性能。如果超出了該場景所規定的運行時間,“看門狗”就會強制終結這個應用的進程.開發者可以crashlog看到對應的日志.但Xcode在調試配置下會禁用"Watch Dog".

2.Time Profiler

選擇Time Profiler啟動.

time profile時間分析工具用來檢測應用CPU的使用情況.可以看到應用程序中各個方法正在消耗CPU時間.使用大量CPU不一定是個問題.類似我們客戶端中不同場景的天氣動畫[類似大雨]的路徑就對CPU依賴就非常高,動畫本身也是非常苛刻且耗費資源較多的任務.

點擊Record 開始運行.

Time Profile 分析界面[via by chenkai]

剛開始我們拿到分析數據時往往是這樣的:

性能數據[via by chenkai]

這里顯示的是執行代碼完整路徑,其中系統和應用本身一些調用路徑完全揉捏在一起.完全看不到我們關心的應用程序中實際代碼執行耗時和代碼路徑實際所在位置.簡單的方式可以快速勾選右邊Call Tree中Separate Thread和Hide System Libraries兩個選項[后面會解釋選項作用]:

拆分后性能數據[via by chenkai]

可以看到直接能夠看到應用程序各個方法調用耗時直接路徑,剔除掉了系統相關方法和反向調用樹路徑.清爽很多.如果覺得這還不夠直觀,選擇任意一個耗時方法分支[這里選擇WeatherViewController viewDidLoad]雙擊進入會看到:

代碼&耗時詳情

可以直接定位到viewDidLoad的代碼,也可以直觀的看到改方法下調用其他方法耗時的時間.類似[self loadCityWeatherScroollerView]耗時是121x,x既耗時單位這里為ms毫秒.當然如果直接在Instrument找到問題覺得不方便修改,可以直接點擊右上方Xcode按鈕會直接定位Xcode對應調用方法入口.這樣很容易能夠快速定位代碼占用CPU最多的方法.也可以打開Xcode快速修改并重新運行Profile來看修改后耗時前后對比.簡單便捷.

這里對右側call tree選項有必要做一下說明[官方user guide翻譯]:

Separate By Thread:線程分離,只有這樣才能在調用路徑中能夠清晰看到占用CPU最大的線程.

Invert Call Tree:從上到下跟蹤堆棧信息.這個選項可以快捷的看到方法調用路徑最深方法占用CPU耗時,比如FuncA{FunB{FunC}},勾選后堆棧以C->B->A把調用層級最深的C顯示最外面.

Hide Missing Symbols:如果dSYM無法找到你的APP或者調用系統框架的話,那么表中將看到調用方法名只能看到16進制的數值,勾選這個選項則可以隱藏這些符號,便于簡化分析數據.

Hide System Libraries:這個就更有用了,勾選后耗時調用路徑只會顯示app耗時的代碼,性能分析普遍我們都比較關系自己代碼的耗時而不是系統的.基本是必選項.注意有些代碼耗時也會納入系統層級,可以進行勾選前后前后對執行路徑進行比對會非常有用.

關于其他方法不再贅述.

性能分析&代碼優化

我們這次性能優化主要針對如下兩個使用場景:

A:應用程序第一次啟動到進入天氣首頁的時間.

B:從后臺切到前臺天氣首頁占用時間.

在還沒有拿到性能分析數據之前,一直認為第一次啟動耗時主要浪費AppDelegate中第三方框架初始化上[類似WeiBo&WeChat 相關SDK初始化調用].當我們拿到實際性能數據耗時占用比時發現實際情況并非如此:

啟動耗時

如上可以看到應用程序啟動初始化工作主要會在MJAppDelegate如下兩個方法展開:willFinishLaunchingWithOptions和didFinishLaunchingWithOptions,其中第三方框架初始化工作主要是willFinishLaunchingWithOptions中完成的.而實際情況耗時占比非常小.基本可以忽略不計.

而我們要優化兩個啟動時間場景,不同在于.第一次進入應用需要經過新手教程、添加城市、請求城市數據、解析數據、初始化天氣首頁UI元素并加載場景動畫. 而從后臺進入時則從本地存儲DT文件中解析天氣數據、初始化天氣首頁UI元素并加載天氣動畫.

1.NSDateFormatter問題凸顯

針對這點重點分析應用啟動&天氣首頁耗時. 在AB兩個場景均發現加載首頁元素發現如下問題:

NSDate(TimeAgo)getDateStrByTimeZone耗時

繼續跟蹤發現:

NSDate耗時

在AB兩個場景里均出現加載MJLineChartView 和 TendencyChartView 時獲取時區對應時間上耗時較大.而耗時主要在getDateStrByTimeZone這個方法調用上.

getDateStrByTimeZone方法

其中創建一個NSDateFormatter對象平均耗時33ms左右 而設置NSDateFormatter的3個屬性平均耗時也在30ms左右.因為首頁24小時天氣和未來幾天預報中.需要for循環中遍歷數據,導致這個方法別重復調用多次,則消耗時間不斷疊加.

針對這個問題:

NSDateFormatter對象本身初始化很慢,同樣還有NSCalendar也是如此.然而在一些使用場景中不可避免要使用他們,比如Json數據解析中.使用這個對象同時避免其性能開銷帶來性能開銷,一般比較好的方式是通過添加屬性(推薦)或創建靜態變量保持該對象只被初始化一次,而被多次復用.不得不值得一提的是設置一個NSDateFormatter屬性速度差不多是和創建新的實例對象一樣慢!

添加屬性方式如下:

屬性方式

針對NSDateFormatter時間開銷出了重用對象外,盡量避免采用其處理多個日期格式.當然針對日期格式處理如果需要提高更多速度,可以直接采用C,可以采用第三方庫來規避這個問題..

2.UIImage緩存取舍

在項目代碼中看到大量使用如下代碼:

UIImage使用

在Main Thread中發現不同動畫場景中Image IO 開銷和耗時所占比例均不一,在UIImage元素較多總體疊加耗時也會占用一定比例.內存開銷也會明顯增高.

UIImage加載圖片方式一般有兩種:

A:imagedNamed初始化

B:imageWithContentsOfFile初始化

二者不同之處在于,imageNamed默認加載圖片成功后會內存中緩存圖片,這個方法用一個指定的名字在系統緩存中查找并返回一個圖片對象.如果緩存中沒有找到相應的圖片對象,則從指定地方加載圖片然后緩存對象,并返回這個圖片對象.

而imageWithContentsOfFile則僅只加載圖片,不緩存.

大量使用imageNamed方式會在不需要緩存的地方額外增加開銷CPU的時間來做這件事.當應用程序需要加載一張比較大的圖片并且使用一次性,那么其實是沒有必要去緩存這個圖片的,用imageWithContentsOfFile是最為經濟的方式,這樣不會因為UIImage元素較多情況下,CPU會被逐個分散在不必要緩存上浪費過多時間.

使用場景需要編程時,應該根據實際應用場景加以區分,UIimage雖小,但使用元素較多問題會有所凸顯.

3.天氣首頁加載策略

在AB兩種場景把性能數據對比分析發現:

天氣首頁WeatherView更新耗時

天氣首頁WeatherView初始化耗時一直300ms-450ms之間,占據首頁耗時很大一部分.且一直固定的開銷.占據Main Thread3分之一.

而用戶進入最先看到是天氣首頁上半部分:

上半部分

而下半部分需要滾動才能看到下半部分.且不一定觸發:

下半部分

而現在整個首頁View的初始化和更新全部放到主線程來做.其中WeatherInfoView updateAllInfo方法更新耗時最長.更多的view意味著更多的渲染,也就是意味更多的CPU和內存消耗,對于我們天氣首頁在UIScrollView里邊嵌套了很多view更是如此。

而針對這種情況不要在主線程承載過多的操作.uikit渲染,用戶輸入回應都需要主進程上完成.主線程被意外block或者加載響應耗時過多都會影響到用戶體驗.而針對資源消耗過大操作,處理原則是最小化主線程的CPU占用,將工作“搬離”主線程, 不要阻塞主線程.類似本地一些IO完全移到其他線程來做.

調試time profiler過程中發現,即使占用了很少的CPU時間(如果你在Time Profiler中看到這些的數據),也可能會阻塞主線程。磁盤、網絡、Lock、dispatch_sync以及向其它進程/線程發送消息都會阻塞主線 程。Time Profiler只能檢測出占用CPU過多的堆棧,但檢測不了這些IO的問題.很奇怪.在System Trace里面突然發現了CPU Time很低,但Wait Time很高的調用,說明在主線程處理I/O已經嚴重損害了app的性能,這個時候考慮把這個操作優化了.

而針對我們應用首頁ui中多個view,在加載策略完全可以采用多線程進行同步加載,只把上半部分放在主線程中加載,下班可以同時開一個線程進行同步加載.這樣可以大大降低組線程初始化和更新時間,當首頁初始化完畢已經呈現是,下半部分其實已經另外一個線程處理完畢.

另外針對單個view 盡量不要在viewWillAppear費時的操作,viewWillAppear在 view 顯示之前被調用,出于效率考慮,在這個方法中不要處理復雜費時的事情;只應該在這個方法設置 view 的顯示屬性之類的簡單事情,比如背景色,字體等。不然,用戶會明顯感覺到 view 顯示遲鈍.

4:應用首次加載時間

應用首次啟動加載操作:

首次加載

首次加載坐了如下操作:

A: 鏈接和載入:可以在Time Profile中顯示dyld載入庫函數,庫會被映射到地址空間,同時完成綁定以及靜態初始化.

B: UIKit初始化:如果應用的Root View Controller是由XIB實現的,也會在啟動時被初始化.

C: 應用回調:調用UIApplicationDeleagte的回調:application:didFinishLaunchingWithOptions.

D: 第一次Core Animation調用:在啟動后的方法-[UIApplication _resportAppLaunchFinished]中調用CA::Transaction::commit實現第一幀畫面的繪制.

應用程序首次加載中啟動方法willFinishLaunchingWithOptions和didFinishLaunchingWithOptions只做應用程序首次啟動必須的要操作,而針對_dyid_start在初始化庫framework函數的操作.不必要的Framework不要鏈接,避免首次加載耗時.

小結如上.很多地方代碼調用和底層機制看的不是特別明白,整理總結關于優化部分實在有限,如上僅供各位參考.另外Instruments確實是把分析代碼利器.目前沒有任何一個第三方工具可以去替代.推薦各位使用.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容

  • 最近采用Instruments 來分析整個應用程序的性能.發現很多有意思的點,以及性能優化和一些分析性能消耗的技巧...
    ny5168閱讀 779評論 0 0
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,762評論 25 708
  • 參考了chenkai 的簡書 本文總結了包括Time Profiler、leaks、Allocation本人認為前...
    RunnerFL閱讀 813評論 0 1
  • 江南柳,垂下萬絳絲 秋去良人肩帶露,春歸燕子嘴銜泥 誰與話凄凄 胭脂后,對鏡著紅衣 無事窗前觀驛馬,抬頭才曉日偏西...
    趙蓮貴閱讀 682評論 3 17
  • Switch組件在分析的時候,主要需要關注三個點。如何實現自定義寬度、如何實現自定義背景顏色,以及html的整體架...
    maneo閱讀 2,543評論 0 0