對于iOS App的首次啟動優(yōu)化,主要關注兩個點,一個是main之前的耗時,一個就是main函數到root VC viewWillAppear執(zhí)行完之間的耗時
針對main函數到首頁展示之間的耗時
- 避免在這之間有阻塞主線程的任務
- 避免大量的IO操作
- 能延后的任務就延后再處理
針對main函數之前的耗時
1.pre-main耗時檢測
我們可以通過設置環(huán)境變量來統(tǒng)計pre-main的耗時
- 操作路徑:Edit Scheme -- 選擇一個Scheme(比如:Run)-- 選擇Arguments -- Environment Variables -- 點擊添加 -- 設置 name:
DYLD_PRINT_STATISTICSvalue :${DEBUG_ACTIVITY_MODE}
具體如下圖所示:

啟動app看下打印如下:
Total pre-main time: 276.51 milliseconds (100.0%)
dylib loading time: 43.44 milliseconds (15.7%)
rebase/binding time: 170.22 milliseconds (61.5%)
ObjC setup time: 24.74 milliseconds (8.9%)
initializer time: 37.80 milliseconds (13.6%)
slowest intializers :
libSystem.B.dylib : 8.75 milliseconds (3.1%)
libMainThreadChecker.dylib : 13.70 milliseconds (4.9%)
libViewDebuggerSupport.dylib : 6.38 milliseconds (2.3%)
通過日志我們發(fā)現,在main之前有哪些時間消耗
- dylib loading :加載可執(zhí)行文件(App 的.o 文件的集合), 加載動態(tài)鏈接庫;
- rebase/binding :對動態(tài)鏈接庫進行 rebase 指針調整和 bind 符號綁定;
- Objc setup :Objc 運行時的初始處理,包括 Objc 相關類的注冊、category 注冊、selector 唯一性檢查等;
- initializer:包括了執(zhí)行 +load() 方法、attribute((constructor)) 修飾的函數的調用、創(chuàng)建 C++ 靜態(tài)全局變量
那么我們知道在pre-main有哪些時間消耗階段,針對性的對癥下藥就可以優(yōu)化一些啟動的時間:
- 減少動態(tài)庫的個數,如果太多就使用合并的方式控制,這樣可以節(jié)約
dylib loading及rebase/binding的時間 - 清理項目中未用到的類、類別、方法等,這樣可以節(jié)約
Objc setup的時間 - 對于可以不在
+load中處理的邏輯可以放到其他的函數中去處理,比如:+initialize;控制 C++ 全局變量的數量;這樣可以節(jié)約initializer的時間
當我們做了以上的工作,對pre-main的時間有所優(yōu)化之后,如果還想再進行優(yōu)化,那就需要借助LLVM為我們提供的優(yōu)化方式了,下面就介紹兩種方式:二進制重排、PGO
2.二進制重排
二進制重排,主要是優(yōu)化我們啟動時需要的函數非常分散在各個頁,啟動時就會多次Page Fault造成時間的損耗
2.1 Page Fault
進程如果能直接訪問物理內存無疑是很不安全的,所以操作系統(tǒng)在物理內存的上又建立了一層虛擬內存。為了提高效率和方便管理,又對虛擬內存和物理內存又進行分頁(Page)。當進程訪問一個虛擬內存Page而對應的物理內存卻不存在時,會觸發(fā)一次缺頁中斷(Page Fault),分配物理內存,有需要的話會從磁盤mmap讀人數據。
通過App Store渠道分發(fā)的App,Page Fault還會進行簽名驗證,所以一次Page Fault的耗時比想象的要多:

2.2 如何查看app啟動產生的Page Faults
2.3 重排
編譯器在生成二進制代碼的時候,默認按照鏈接的Object File(.o)順序寫文件,按照Object File內部的函數順序寫函數。
靜態(tài)庫文件.a就是一組.o文件的ar包,可以用ar -t查看.a包含的所有.o

簡化問題:假設我們只有兩個page:page1/page2,其中綠色的method1和method3啟動時候需要調用,為了執(zhí)行對應的代碼,系統(tǒng)必須進行兩個Page Fault。
但如果我們把method1和method3排布到一起,那么只需要一個Page Fault即可,這就是二進制文件重排的核心原理。

2.4 Xcode配置Order
那么我們需要將啟動時候調用的函數進行重排,讓它們盡可能的分配在同一個頁;比如load方法我們就將其找出來,放到一起;LLVM支持我們通過設置order來達到這個效果

2.4.1 首先打開Write Link Map File查看
Link Map File中文直譯為鏈接映射文件,它是在Xcode生成可執(zhí)行文件的同時生成的鏈接信息文件,用于描述可執(zhí)行文件的構造部分,包括了代碼段和數據段的分布情況
我們可以在Xcode的配置中將Write Link Map File設置為YES來生成Map File

Run下一app,查看Map File
Map File路徑:
$(TARGET_TEMP_DIR)/$(PRODUCT_NAME)-LinkMap-$(CURRENT_VARIANT)-$(CURRENT_ARCH).txt
可以選中app,Show In Finder -- 找到build目錄 -- 按照下面的舉例的路徑就可以找到:
Build/Intermediates.noindex/RuntimeLearning.build/Debug-iphoneos/RuntimeLearning.build/RuntimeLearning-LinkMap-normal-arm64.txt
打開Map File可以看到load方法分散的很開,當啟動執(zhí)行的函數很多的話,那就可能load分散在不同的頁了。

2.4.2 將load方法設置order再看看Map File
- 新建xxx.order文件
- Xcode的Build Setting中設置Order文件

- 編輯order文件如下:
+[UIViewController(Test) load]
+[UIFont(Test) load]
+[SubTestUnsafeSwizzle load]
+[TestCategorySwizzle(Log) load]
+[TestCategorySwizzle(EventTrack) load]
+[TestCategorySwizzle(ClassMethod) load]
+[NSObject(RLSafe) load]
-[AppDelegate application:didFinishLaunchingWithOptions:]
-[AppDelegate applicationWillResignActive:]
-[AppDelegate applicationDidEnterBackground:]
-[AppDelegate applicationWillEnterForeground:]
-[AppDelegate applicationDidBecomeActive:]
-[AppDelegate applicationWillTerminate:]
-[AppDelegate window]
-[AppDelegate setWindow:]
-[AppDelegate .cxx_destruct]
-
再次運行,查看Map File文件
圖片.png
可以看到Map File已經按照我們order配置的順序了;這里只是講述了如何使用order,具體的細節(jié)、原理和實踐可以參照抖音二進制重排實踐;他們的數據是啟動優(yōu)化了15%。
3. PGO
Profile Guided Optimization簡稱PGO,這個也是LLVM提供的一個優(yōu)化,我們可以直接在Xcode中進行配置;它是一種改進應用程序的編譯器優(yōu)化的方法。PGO利用應用程序的特殊工具構建來生成有關最常用代碼路徑和方法的配置文件信息。然后,編譯器使用此配置文件信息將優(yōu)化工作集中在最常用的代碼上,從而利用有關程序通常如何表現的額外信息來更好地完成優(yōu)化工作
配置文件引導式優(yōu)化(PGO)是一項高級功能,可讓您從應用中獲取所有性能的最后一點點。它并不難使用,但是需要一些額外的構建步驟和一些注意事項來收集良好的配置文件信息。根據您應用程序代碼的性質,PGO可以將性能提高5%到10%,但并非所有應用程序都會從??中受益。如果您對性能敏感的代碼需要進行額外的優(yōu)化,則PGO可以提供幫助。
原理上簡單說如下:
- 編譯一個所有方法插樁了的可執(zhí)行文件,
- 運行可執(zhí)行文件(啟動一次app),此時插樁的方法會把執(zhí)行過的方法都記錄下來,并記錄方法的執(zhí)行頻率。例如下圖的profdata文件。
- 重新build(原則上只是鏈接時需要profdata),讓鏈接器按照profdata的信息把(啟動中用到的、或者頻率高的方法)放到一起。
此時這個新的可執(zhí)行文件就完成了二進制文件重排,減少了page交換(加載)的次數,提高了系統(tǒng)加載和運行app二進制文件的IO性能,加快了執(zhí)行速度;這個相比較order的方式,這個優(yōu)化還考慮了函數調用頻率的問題
Xcode配置
-
打開Use Optimization Profile
圖片.png -
選擇Action生成Profile
圖片.png

- Run之后,會在根目錄自動創(chuàng)建一個文件
OptimizationProfiles
圖片.png
PGO也是官方提供的一個優(yōu)化手段,具體細節(jié)可以參照蘋果官方文檔:Xcode Profile Guided Optimization;對于有多大的提升我這邊跑了幾次發(fā)現每次數據都有些出入,加上demo比較小,看著也不是很明顯,感興趣的可以在自己的項目中使用這項LLVM的優(yōu)化
如何獲取app啟動都調用了哪些函數
iOS App啟動時間優(yōu)化--Clang插樁獲取啟動調用的函數符號



