如何精確度量 iOS App 的啟動時間
iOS啟動分為兩個時間:
- pre-main時間
- main時間
一、pre-main時間檢測
Xcode 提供了一個很贊的方法倦卖,只需要在 Edit scheme -> Run -> Arguments 中將環(huán)境變量 DYLD_PRINT_STATISTICS 設(shè)為 1咨察,就可以看到 main 之前各個階段的時間消耗
Total pre-main time: 341.32 milliseconds (100.0%)
dylib loading time: 154.88 milliseconds (45.3%)
rebase/binding time: 37.20 milliseconds (10.8%)
ObjC setup time: 52.62 milliseconds (15.4%)
initializer time: 96.50 milliseconds (28.2%)
slowest intializers :
libSystem.dylib : 4.07 milliseconds (1.1%)
libMainThreadChecker.dylib : 30.75 milliseconds (9.0%)
AFNetworking : 19.08 milliseconds (5.5%)
LDXLog : 10.06 milliseconds (2.9%)
Bigger : 7.05 milliseconds (2.0%)
還有一個方法獲取更詳細(xì)的時間馋袜,只需將環(huán)境變量 DYLD_PRINT_STATISTICS_DETAILS 設(shè)為 1 就可以柬祠。
total time: 2.8 seconds (100.0%)
total images loaded: 488 (471 from dyld shared cache)
total segments mapped: 61, into 24958 pages
total images loading time: 1.1 seconds (40.6%)
total load time in ObjC: 92.39 milliseconds (3.2%)
total debugger pause time: 794.39 milliseconds (28.2%)
total dtrace DOF registration time: 0.00 milliseconds (0.0%)
total rebase fixups: 921,005
total rebase fixups time: 109.77 milliseconds (3.9%)
total binding fixups: 694,265
total binding fixups time: 766.41 milliseconds (27.2%)
total weak binding fixups time: 9.05 milliseconds (0.3%)
total redo shared cached bindings time: 768.13 milliseconds (27.3%)
total bindings lazily fixed up: 0 of 0
total time in initializers and ObjC +load: 690.73 milliseconds (24.5%)
libSystem.B.dylib : 11.67 milliseconds (0.4%)
libBacktraceRecording.dylib : 12.06 milliseconds (0.4%)
libobjc.A.dylib : 6.09 milliseconds (0.2%)
libMainThreadChecker.dylib : 59.50 milliseconds (2.1%)
libViewDebuggerSupport.dylib : 7.66 milliseconds (0.2%)
libglInterpose.dylib : 286.97 milliseconds (10.2%)
libMTLCapture.dylib : 4.28 milliseconds (0.1%)
AWUnityFramework : 103.15 milliseconds (3.6%)
AiWayFashionCar : 365.65 milliseconds (12.9%)
total symbol trie searches: 1594338
total symbol table binary searches: 0
total images defining weak symbols: 63
total images using weak symbols: 133
1. 優(yōu)化(dylib loading time):
在項目優(yōu)化實踐中鸟赫,我們移除了一個沒有必要的動態(tài)庫淤毛,并將幾個動態(tài)庫合成為一個動態(tài)庫秋度,減少動態(tài)庫數(shù)量
第一個階段:pre-main time 中第一個階段 dylib loading time : 動態(tài)庫加載階段
***注: ***
如何查看動態(tài)庫的個數(shù): Products 中.app中會有Frameworks文件夾 里面即App需要引入的動態(tài)庫
舉例:Flutter優(yōu)化日志
這里區(qū)分兩種方式加載Flutter:
區(qū)別:
1. 第一種方式會將flutter依賴的第三方插件做成pod子倉的形式直接引入的源碼,
App.framework Flutter.framework
2. 第二種方式會將flutter依賴的第三方插件做成framework,之后將所有的framework做成pod倉庫
App.framework, FlutterPluginRegistrant.framework ,shared_preferences.framework, wakelock.framework , FMDB.framework , flutter_boost.framework , sqflite.framework , webview_flutter.framework, Flutter.framework , path_provider.framework , video_player.framework
造成的后果是: 第二種主工程會引入很多framework,造成的影響是動態(tài)庫加載時間變長
第一種:采取直接污染主工程方式: 由于日志較多,僅展示敏感數(shù)據(jù)
Total pre-main time: 685.23 milliseconds (100.0%) dylib loading time: 152.81 milliseconds (22.3%) rebase/binding time: 74.06 milliseconds (10.8%) ObjC setup time: 44.86 milliseconds (6.5%) initializer time: 413.48 milliseconds (60.3%) Total pre-main time: 980.53 milliseconds (100.0%) dylib loading time: 163.32 milliseconds (16.6%) rebase/binding time: 86.59 milliseconds (8.8%) ObjC setup time: 223.50 milliseconds (22.7%) initializer time: 507.11 milliseconds (51.7%)
第二種:采取pod倉庫形式
Total pre-main time: 818.21 milliseconds (100.0%) dylib loading time: 243.54 milliseconds (29.7%) rebase/binding time: 72.09 milliseconds (8.8%) ObjC setup time: 39.28 milliseconds (4.8%) initializer time: 463.27 milliseconds (56.6%) Total pre-main time: 1.3 seconds (100.0%) dylib loading time: 270.75 milliseconds (20.3%) rebase/binding time: 69.74 milliseconds (5.2%) ObjC setup time: 282.78 milliseconds (21.2%) initializer time: 708.54 milliseconds (53.2%)
對比結(jié)果: dylib loading time 時間拉長了100多毫秒
技術(shù)選擇:
- 第一種方式污染主工程: 可以看到結(jié)果是framework變少
- 第二種方式極少的污染主工程: 結(jié)果是framework變多
2. 優(yōu)化(rebase/binding time):
這一階段系統(tǒng)主要注冊 Objc 類。所以钱床,指針數(shù)量越少越好荚斯。這一步能做的優(yōu)化有:
- 清理項目中無用的類
- 刪減沒有被調(diào)用到或者已經(jīng)廢棄的方法
- 刪減一些無用的靜態(tài)變量
核心思想是在進(jìn)行動態(tài)庫的重定位和綁定(Rebase/binding)(ASLR:dylib會被加載到隨機地址,這個隨機的地址跟代碼和數(shù)據(jù)指向的舊地址會有偏差查牌,dyld 需要修正這個偏差事期,做法就是將 dylib 內(nèi)部的指針地址都加上這個偏移量) 過程中減少指針修正;
減少Objective-C類數(shù)量纸颜,減少分類兽泣,減少實例變量和函數(shù)(刪除不用的類以及冗余代碼,再深一點就是減少第三方工具的使用胁孙,可以查看源碼唠倦,自己實現(xiàn));
減少C++虛函數(shù)涮较;
3.優(yōu)化(ObjC setup time):
Objc Setup Time
這一步主要做了以下操作
注冊O(shè)bjc類 (class registration)
把category的定義插入方法列表 (category registration)
保證每一個selector唯一 (selctor uniquing)
前兩部做好之后這一步就沒有什么可以有優(yōu)化的
4. 優(yōu)化(initializer time):
第一個階段:pre-main time 中第4個階段 initializer time :
這一階段 dyld開始運行程序的初始化函數(shù),調(diào)用每個Obj類和分類的+load方法,
這一階段稠鼻,dyld 開始運行程序的初始化函數(shù),調(diào)用每個 Objc 類和分類的 +load 方法狂票,調(diào)用 C/C++ 中的構(gòu)造器函數(shù)候齿。initializer階段執(zhí)行完后,dyld 開始調(diào)用 main() 函數(shù)。在這一步慌盯,檢查 +load 方法周霉,盡量把事情推遲到 +initiailize 方法里執(zhí)行。
- 使用initialize替代load方法
- 減少使用c/c++的attribute((constructor))亚皂;推薦使用dispatch_once(),peathrd_once(), std:once()等方法
- 不要在初始化中創(chuàng)建線程
- 推薦使用swift
優(yōu)化前
Total pre-main time: 731.83 milliseconds (100.0%)
dylib loading time: 250.62 milliseconds (34.2%)
rebase/binding time: 50.32 milliseconds (6.8%)
ObjC setup time: 37.81 milliseconds (5.1%)
initializer time: 393.07 milliseconds (53.7%)
slowest intializers :
libSystem.B.dylib : 6.20 milliseconds (0.8%)
libMainThreadChecker.dylib : 28.41 milliseconds (3.8%)
libglInterpose.dylib : 187.20 milliseconds (25.5%)
AiWayFashionCar : 244.98 milliseconds (33.4%)
優(yōu)化后
Total pre-main time: 667.68 milliseconds (100.0%)
dylib loading time: 237.69 milliseconds (35.6%)
rebase/binding time: 53.64 milliseconds (8.0%)
ObjC setup time: 36.47 milliseconds (5.4%)
initializer time: 339.86 milliseconds (50.9%)
slowest intializers :
libSystem.B.dylib : 6.05 milliseconds (0.9%)
libMainThreadChecker.dylib : 28.70 milliseconds (4.2%)
libglInterpose.dylib : 152.15 milliseconds (22.7%)
AiWayFashionCar : 218.33 milliseconds (32.7%)
// 經(jīng)全局查找看到有一個load方法里面執(zhí)行了IO操作相關(guān)API, 優(yōu)化之后 initializer time 有所優(yōu)化
二俱箱、main 之后的時間度量
main 到 didFinishLaunching 結(jié)束或者第一個 ViewController 的viewDidAppear 都是作為 main 之后啟動時間的一個度量指標(biāo)。
這個時間統(tǒng)計直接打點計算就可以灭必,不過當(dāng)遇到時間較長需要排查問題時匠楚,只統(tǒng)計兩個點的時間其實不方便排查,目前見到比較好用的方式就是為把啟動任務(wù)規(guī)范化厂财、粒子化芋簿,針對每個任務(wù)都有打點統(tǒng)計,這樣方便后期問題的定位和優(yōu)化璃饱。
以此記錄優(yōu)化啟動日志