iOS效能優化過程淺析
這一系列文章是我的讀書筆記,整理一下,也算是溫故而知新。
一:效能優化策略
效能問題的處理流程
- 發現/重現問題
- 利用工具剖析
- 形成假設
- 改進程式碼和設計
在以上的四個步驟中迴圈反覆,直到問題解決。
Profile!不要猜!
效能優化的主要策略:
- 不要做無用功:不要在啟動時花幾百ms來做logging,不要為同樣的資料做多次查詢
- 試圖重用:對於建立過程昂貴的物件,要重用而不是重新建立
- Table View的cell
- Date/Number的formatter
- 正規表示式
- SQLite語句
- 使用更快的方式設計、程式設計:選擇正確的集合物件和演算法來進行程式設計、選擇適合的資料儲存格式(plist、SQLite)、優化SQLite查詢語句
- 事先做優化
- 對於昂貴的計算,要進行事先計算。iCal中的重複事件,是預先計算出來的,並儲存到資料庫中。
- 事先計算並快取一些物件,可能會佔用大量的記憶體。注意不要將這些物件宣告為static並常駐記憶體。
- 事後做優化:非同步載入、懶載入
- 為伸縮性而做優化:當資料有10條、100條、1000條甚至更多的時候,應用程式的效能不應該對應的呈數量級式的增長,否則無法使用。
說起來慚愧,我真的很少遇到效能問題。以前假設中的效能問題,很多是根本不存在的。事前計劃也杜絕了不了效能問題的產生,所以不如暫時忘記它吧。當然對於一些常識性的提高效能的設計,仍然是必須的。
二:iOS應用啟動速度優化
很多app的開發者都不重視app的啟動速度,這對於碎片化使用情景的使用者來說,簡直是災難。
iOS應用的啟動速度
應用啟動時,會播放一個放大的動畫。iPhone上是400ms,iPad上是500ms。最理想的啟動速度是,在播放完動畫後,使用者就可以使用。
如果應用啟動過慢,使用者就會放棄使用,甚至永遠都不再回來。拋開程式碼不談,如果抱著PC端遊和單機遊戲的思維,在遊戲啟動時強加公司Logo,啟動動畫,並且使用者不可跳過,也會使使用者的成功使用率大大降低。
iOS系統的“看門狗”
為了防止一個應用佔用過多的系統資源,開發iOS的蘋果工程師門設計了一個“看門狗”的機制。在不同的場景下,“看門狗”會監測應用的效能。如果超出了該場景所規定的執行時間,“看門狗”就會強制終結這個應用的程式。開發者們在crashlog裡面,會看到諸如0x8badf00d
這樣的錯誤程式碼(“看門狗”吃了壞的食物,它很不高興)。
場景 | “看門狗”超時時間 |
---|---|
啟動 | 20秒 |
恢復執行 | 10秒 |
懸掛程式 | 10秒 |
退出應用 | 6秒 |
後臺執行 | 10分鐘 |
值得注意的是,Xcode在Debug的時候,會禁止“看門狗”。
如何測試啟動時間
兩種方法:一種使用NSLog,另外一種使用Time Profiler。
- 使用NSLog
CFAbsoluteTime StartTime; int main(int argc, char **argv) { StartTime = CFAbsoluteTimeGetCurrent(); // ... 5 } 6 - (void)applicationDidFinishLaunching:(UIApplication *)app { dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"Launched in %f sec", CFAbsoluteTimeGetCurrent() - StartTime); }); // ... }
- 使用Time Profiler
- Instruments->Time Profiler
- Profile你的app
- 切換到CPU strategy view,找到你的app啟動的第一幀
- 搜尋
-[UIApplication _reportAppLaunchFinished]
- 找到包含
-[UIApplication _reportAppLaunchFinished]
的最後一幀,即可計算出啟動時間
iOS App啟動過程
- 連結並載入Framework和static lib
- UIKit初始化
- 應用程式callback
- 第一個Core Animation transaction
連結並載入Framework及static lib時需要注意:
- 每個Framework都會增加啟動時間和佔用的記憶體
- 不必要的Framework,不要連結
- 必要的Framework,不要票房為Optional
- 只在使用在Deployment Target之後釋出的Framework時,才使用Optional(比如你的Deployment Target是iOS 3.0,需要連結StoreKit的時候)
- 避免建立全域性的C++物件
初始化UIKit時需要注意:
- 字型、狀態列、user defaults、main nib會被初始化
- 保持main nib儘可能的小
- User defaults本質上是一個plist檔案,儲存的資料是同時被反序列化的,不要在user defaults裡面儲存圖片等大資料
應用程式的回撥:
application:willFinishLaunchingWithOptions:
- 恢復應用程式的狀態
application:didFinishLaunchingWithOptions:
我一直認為設計的本質是折衷。當你為了100ms的啟動速度優化歡欣不已,而無視那長達10秒的啟動動畫時,應該想想究竟什麼是應該做的。做正確的事情比把事情做好更重要。
三:事件處理-拯救主執行緒
使用者經常評論app的一個用詞是“卡頓”,很大的因素是因為主執行緒被佔用了。使用者的事件是在主執行緒被處理的,包括點選、滾動、加速計、Proximity Sensor。
為了保證事件的平滑處理,需要進行如下優化:
- 最小化主執行緒的CPU佔用
- 將工作“搬離”主執行緒
- 不要阻塞主執行緒
最小化主執行緒的CPU佔用
前面兩篇文章,我們接觸到了Time Profiler。使用它可以剖析不同執行緒的CPU使用情況,並給出呼叫堆疊的CPU時間佔用百分比。如果app“卡頓”,並且在Time Profiler的結果可以找到明確的高佔用堆疊,你需要把它優化掉。
將工作“搬離”主執行緒 – 隱式併發
為了得到更流暢的互動體驗,iOS已經幫我們做了很多事情,Android就沒有這麼好運了。iOS將以下這些事情搬離了主執行緒:
- View和layer的動畫(動畫繪製前的計算,而不是drawing過程)
- Layer的組合計算(drawing後的疊加)
- PNG的解碼(是的,你沒看錯;而且利用了CPU的多核心)
注意滾動(Scrolling)不是一個動畫,而是在Main Run Loop中不斷接收事件並且處理。
將工作“搬離”主執行緒 – 顯式併發
這裡是需要開發者們搞定的部分。磁碟、網路等I/O會阻塞執行緒,不要把它們放到主執行緒裡。常用的技術有:
- Grand Central Dispatch(GCD)
- NSOperationQueue
- NSThread
iOS 4.0後,易用的GCD技術被廣泛使用。例如:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // do something in background dispatch_async(dispatch_get_main_queue(), ^{ // do something on main thread }); });
GCD的陷阱
GCD其實就是執行緒,只不過提供了一個更高層次的抽象。過多的執行緒一定會帶來效能損失,因此GCD設計了一個最高允許的執行緒值(對開發者透明,不用管到底有多少)。那麼如何解決這個問題呢?
- 將佇列序列化
- 使用Dispatch sources
- 使用帶有限制的NSOperationQueue
- 使用Cocoa Touch提供的非同步方法
另外一個陷阱是執行緒安全:
- UIKit必須要在主執行緒使用,除了UIGraphics,UIBezierPath,UIImage
- 大多數CG、CA、Foundation的類,不是執行緒安全的
- 如果你使用了ojbc runtime來進行introspection,由於它是thread safe的,可能會導致競爭
此外,iOS 4.3新增了DISPATCH_QUEUE_PRIORITY_BACKGROUND
,它擁有非常低的優先順序。這個優先順序只用於不太關心完成時間的真正的後臺任務,如果要表示較低的優先順序,你通常需要的是DISPATCH_QUEUE_PRIORITY_LOW
。
不要阻塞主執行緒
即使佔用了很少的CPU時間(如果你在Time Profiler中看到這些的資料),也可能會阻塞主執行緒。磁碟、網路、Lock、dispatch_sync以及向其它程式/執行緒傳送訊息都會阻塞主線 程。Time Profiler只能檢測出佔用CPU過多的堆疊,但檢測不了這些IO的問題。
大多數的阻塞事件,都會伴隨著一個系統呼叫,如:
read/write
- 讀寫檔案send/recv
- 收發網路資料psynch_mutex_wait
- 獲得鎖mach_msg
- IPC
System Trace
這個Instrumentor,記錄了所有的系統呼叫,以及每次呼叫的等待時間。如果你在System Trace裡面發現了CPU Time很低,但Wait Time很高的呼叫,說明在主執行緒處理I/O已經嚴重損害了app的效能。
保證主執行緒的低CPU佔用,將I/O移至其它執行緒,可以大大地提高主執行緒對互動事件的處理能力。我建議開發者朋友們寫程式碼的時候,除非是以前遇到過的問題,都沒有必要假設問題存在。80%的優化都是不必要的。
相關文章
- iOS 介面效能優化淺析iOS優化
- js效能優化淺析JS優化
- TCP 效能優化淺析TCP優化
- 淺析EF效能優化優化
- Kotlin 效能優化利器 —— Sqeuence 原理淺析Kotlin優化
- iOS開發過程中 效能監控及優化iOS優化
- [譯] React效能優化-虛擬Dom原理淺析React優化
- RecyclerView 之 Adapter 的簡化過程淺析ViewAPT
- 效能優化的過程學習優化
- ORACLE關閉過程淺析Oracle
- ORACLE啟動過程淺析Oracle
- C程式編譯過程淺析C程式編譯
- iOS效能優化iOS優化
- iOS Block淺淺析iOSBloC
- iOS元件化通用工具淺析iOS元件化
- 瀏覽器渲染過程與效能優化瀏覽器優化
- 一次效能優化調整過程.優化
- 淺析Java程式的執行過程Java
- Spring MVC實現過程淺析SpringMVC
- 淺析渲染引擎與前端優化前端優化
- Java 效能優化之——效能優化的過程方法與求職面經總結Java優化求職
- JavaScript 的效能優化:程式碼載入和執行模式淺析JavaScript優化模式
- IOS效能優化篇iOS優化
- iOS 效能優化套路iOS優化
- IOS代理淺析iOS
- 前端效能優化之http請求的過程前端優化HTTP
- MySQL伺服器連線過程淺析MySql伺服器
- 淺析Android Activity的啟動過程Android
- 從底層原始碼淺析Mybatis的SqlSessionFactory初始化過程原始碼MyBatisSQLSession
- MySQL效能最佳化淺析及線上案例MySql
- iOS 效能篇一一UITableView效能優化iOSUIView優化
- 淺談小程式效能優化優化
- 效能優化,實踐淺談優化
- 淺談優化程式效能(下)優化
- iOS 效能優化備忘iOS優化
- iOS 圖形效能優化iOS優化
- ios效能優化相關iOS優化
- iOS 效能優化的探索iOS優化