iOS性能優化:Instruments使用實戰

最近採用Instruments 來分析整個應用程序的性能.發現不少有意思的點,以及性能優化和一些分析性能消耗的技巧,小結以下。html

Instruments使用技巧ios

關於Instruments官方有一個頗有用的用戶使用Guide,固然若是不習慣官方英文能夠在這裏找到中文本翻譯版本PDF參閱.Instruments 確實是一個很強大的工具,用它來收集關於一個或多個系統進程的性能和行爲的數據極爲方便,並能及時跟蹤隨着時間產生的數據.還能夠普遍收集不一樣類型的數據.關於Instrument工具基本使用不在贅述.以下重點說明一些使用技巧.編程

1.概覽緩存

工具經過Xcode工具欄中Product->Profile能夠啓動,啓動後界面以下:性能優化

122936-0f28916b25498021.png

Instrument概覽[via by chenkai]網絡

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

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

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

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

2.Time Profiler

選擇Time Profiler啓動.

time profile時間分析工具用來檢測應用CPU的使用狀況.能夠看到應用程序中各個方法正在消耗CPU時間.使用大量CPU不必定是個問題.相似咱們客戶端中不一樣場景的天氣動畫[相似大雨]的路徑就對CPU依賴就很是高,動畫自己也是很是苛刻且耗費資源較多的任務.

點擊Record 開始運行.

122936-57a0faf9fe2fcd4b.png

Time Profile 分析界面[via by chenkai]

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

23.png

性能數據[via by chenkai]

這裏顯示的是執行代碼完整路徑,其中系統和應用自己一些調用路徑徹底揉捏在一塊兒.徹底看不到咱們關心的應用程序中實際代碼執行耗時和代碼路徑實際所在位置.簡單的方式能夠快速勾選右邊Call Tree中Separate Thread和Hide System Libraries兩個選項[後面會解釋選項做用]:

24.png

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

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

25.png

代碼&耗時詳情

能夠直接定位到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初始化調用].當咱們拿到實際性能數據耗時佔用比時發現實際狀況並不是如此:

26.png

啓動耗時

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

而咱們要優化兩個啓動時間場景,不一樣在於.第一次進入應用須要通過新手教程、添加城市、請求城市數據、解析數據、初始化天氣首頁UI元素並加載場景動畫. 而從後臺進入時則從本地存儲DT文件中解析天氣數據、初始化天氣首頁UI元素並加載天氣動畫.

1.NSDateFormatter問題凸顯

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

27.png
NSDate(TimeAgo)getDateStrByTimeZone耗時

繼續跟蹤發現:

28.png
NSDate耗時

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

29.png
getDateStrByTimeZone方法

其中建立一個NSDateFormatter對象平均耗時33ms左右 而設置NSDateFormatter的3個屬性平均耗時也在30ms左右.由於首頁24小時天氣和將來幾天預報中.須要for循環中遍歷數據,致使這個方法別重複調用屢次,則消耗時間不斷疊加.

針對這個問題:

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

添加屬性方式以下:

30.png
屬性方式

針對NSDateFormatter時間開銷出了重用對象外,儘可能避免採用其處理多個日期格式.固然針對日期格式處理若是須要提升更多速度,能夠直接採用C,能夠採用第三方庫來規避這個問題..

2.UIImage緩存取捨

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

31.png
UIImage使用

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

UIImage加載圖片方式通常有兩種:

A:imagedNamed初始化

B:imageWithContentsOfFile初始化

兩者不一樣之處在於,imageNamed默認加載圖片成功後會內存中緩存圖片,這個方法用一個指定的名字在系統緩存中查找並返回一個圖片對象.若是緩存中沒有找到相應的圖片對象,則從指定地方加載圖片而後緩存對象,並返回這個圖片對象.

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

大量使用imageNamed方式會在不須要緩存的地方額外增長開銷CPU的時間來作這件事.當應用程序須要加載一張比較大的圖片而且使用一次性,那麼實際上是沒有必要去緩存這個圖片的,用imageWithContentsOfFile是最爲經濟的方式,這樣不會由於UIImage元素較多狀況下,CPU會被逐個分散在沒必要要緩存上浪費過多時間.

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

3.天氣首頁加載策略

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

32.png
天氣首頁WeatherView更新耗時

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

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

33.png

上半部分

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

34.png

下半部分

而如今整個首頁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:應用首次加載時間

應用首次啓動加載操做:

35.png

首次加載

首次加載坐了以下操做:

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確實是把分析代碼利器.目前沒有任何一個第三方工具能夠去替代.推薦各位使用

相關文章
相關標籤/搜索