一、啟動優(yōu)化
冷啟動:殺死app后的第一次啟動
熱啟動:app還在后臺運行,這個時候點開app
啟動優(yōu)化:一般講的是冷啟動
啟動階段:main函數(shù)之前、main函數(shù)之后
main 階段:
1、懶加載
2、發(fā)揮CPU的價值(多線程進行初始化)
3、啟動時避免使用Xib、stroyboard階段一、main函數(shù)之前
打印啟動時間
添加 DYLD_PRINT_STATISTICS


dylib loading :加載可執(zhí)行文件(App 的.o 文件的集合), 加載動態(tài)鏈接庫;(優(yōu)化:建議不要大于6個)
rebase/binding :對動態(tài)鏈接庫進行 rebase 指針調整和 bind 符號綁定; 修正內部偏移指針/外部符號綁定 (優(yōu)化:減少OC類) 優(yōu)化少
Objc setup :Objc 運行時的初始處理,包括 Objc 相關類的注冊、category 注冊、selector 唯一性檢查等;(優(yōu)化:減少OC類) 優(yōu)化少
initializer:包括了執(zhí)行 +load() 方法、attribute((constructor)) 修飾的函數(shù)的調用、創(chuàng)建 C++ 靜態(tài)全局變量 (優(yōu)化:使用懶加載)
a.減少加載動態(tài)庫的數(shù)量
談到優(yōu)化,映射到我們頭腦中的***個想法就是:少做事!減少進程啟動過程中的動態(tài)庫數(shù)量,就成了當務之急。這里介紹幾個方法:
(1)將一些無用的動態(tài)庫去掉。有些程序員為了自己編程方便,把一些動態(tài)庫不管是否真的使用全都鏈接上,這導致進程啟動過程中加載了一些無用的動態(tài)庫,浪費了時間。這些無用的動態(tài)庫應堅決去掉。
(2)重新組織動態(tài)庫的結構,力爭將進程加載動態(tài)庫的數(shù)量減到最少。對于使用標準C編寫的動態(tài)庫,可以考慮將幾個小動態(tài)庫合并為一個大的動態(tài)庫,減少進程加載動態(tài)庫的數(shù)量。
對于使用C++編寫的動態(tài)庫,由于涉及全局對象初始化的問題,筆者建議將大的動態(tài)庫拆分為若干個小的動態(tài)庫,進程根據自己的需要靈活加載所需要動態(tài)庫。對于那些經常一同出現(xiàn)的動態(tài)庫,可以考慮將其進行合并。
關于這點,可以參考2.1.5節(jié),那里有更加詳細的論述。
(3)將一些動態(tài)庫編譯成靜態(tài)庫,與進程或其他動態(tài)庫合并,從而減少加載動態(tài)庫的數(shù)量。其優(yōu)點是:
減少了加載動態(tài)庫的數(shù)量。
在與其他動態(tài)庫(或進程)合并之后,動態(tài)庫內部之間的函數(shù)調用不必再進行符號查找、動態(tài)鏈接,從而提高速度。
缺點是:
該動態(tài)庫如果被多個動態(tài)庫或進程所依賴的話,那么該動態(tài)庫將被復制多份合并到新的動態(tài)庫中,導致整體的文件大小增加,占用更多的Flash。
失去了動態(tài)庫原有的代碼段內存共享,因此可能會導致代碼段內存使用上的增加。
如果該動態(tài)庫被多個守護進程所使用,那么其代碼段很多代碼已經被加載到物理內存,那么進程在運行該動態(tài)庫的代碼時產生的page fault就少;如果該動態(tài)庫被編譯成靜態(tài)庫與其他動態(tài)庫合并,那么其代碼段被其他多個守護進程運行到的機會就少,在進程啟動過程中運行到新的動態(tài)庫時所產生的page fault就多,從而有可能影響進程的加載速度。
基于此,在考慮將動態(tài)庫改為靜態(tài)庫時,有以下原則:
對于那些只被很少進程加載的動態(tài)庫,要將其編譯成靜態(tài)庫,從而減少進程啟動時加載動態(tài)庫的數(shù)量;同時由于該動態(tài)庫代碼段很少被多個進程共享,所以不會增加內存方面的開銷。
對于那些守護使用的動態(tài)庫,其代碼段大多已經被加載到內存,運行時產生的page fault要少,故其為動態(tài)庫反而有可能要比靜態(tài)庫速度更快。
(4)使用dlopen動態(tài)加載動態(tài)庫。進程所依賴的動態(tài)庫,并不一定在進程啟動時都要用到。不需要的動態(tài)庫,要在進程啟動時加載動態(tài)庫的清單中去掉,從而加快進程的啟動速度。在需要該動態(tài)庫時,再使用dlopen來動態(tài)加載動態(tài)庫。
dlopen的優(yōu)點是:可以精確控制動態(tài)庫的生存周期,一方面可以減少動態(tài)庫數(shù)據段的內存使用,另一方面可以減少進程啟動時加載動態(tài)庫的時間。
其缺點是:程序員編寫程序將變得很麻煩。
減少動態(tài)庫的個數(shù),如果太多就使用合并的方式控制,這樣可以節(jié)約dylib loading及rebase/binding的時間
清理項目中未用到的類、類別、方法等,這樣可以節(jié)約Objc setup的時間
對于可以不在+load中處理的邏輯可以放到其他的函數(shù)中去處理,比如:+initialize;控制 C++ 全局變量的數(shù)量;這樣可以節(jié)約initializer的時間
階段二、main函數(shù)之后
main 開始 到 第一個界面。
打點,使用BLStopwatch.h和BLStopwatch.m這個類

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[BLStopwatch sharedStopwatch] start];
int a = 0;
for (int i = 0; i < 10000000; i++) {
a++;
}
[[BLStopwatch sharedStopwatch] splitWithDescription:@"didFinishLaunchingWithOptions"];
return YES;
}
- (void)viewDidLoad {
[super viewDidLoad];
//刷新時間:
[[BLStopwatch sharedStopwatch] refreshMedianTime];
int a = 0;
for (int i = 0; i < 10000000; i++) {
a++;
};
[[BLStopwatch sharedStopwatch] splitWithDescription:@"viewDidLoad"];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
//刷新時間:
[[BLStopwatch sharedStopwatch] refreshMedianTime];
int a = 0;
for (int i = 0; i < 10000000; i++) {
a++;
};
[[BLStopwatch sharedStopwatch] splitWithDescription:@"viewDidAppear"];
[[BLStopwatch sharedStopwatch] stopAndPresentResultsThenReset];
}

二、二進制重排
二進制重排是在main函數(shù)之前
物理內存
虛擬內存 : 解決安全問題、解決內存使用率問題


解決安全問題:映射表(頁表)(虛擬頁表)
解決內存使用率問題:內存分頁管理。缺頁中斷,然后加載到物理內存,加載之前會簽名加載的頁;如果啟動的時候要加載的代碼分別在不同的頁,那么缺頁中斷時間就比較長,這時就出現(xiàn)了二進制重排(把啟動要加載的代碼放在前面幾頁)。使用內存分頁后,就會導致代碼的加載都是從0開始的,為了防止黑客,就出現(xiàn)了ASLR。
內存分頁技術
MacOS 、linux (4K為一頁)
iOS(16K為一頁)

PageFault(缺頁中斷)
進程如果能直接訪問物理內存無疑是很不安全的,所以操作系統(tǒng)在物理內存的上又建立了一層虛擬內存。為了提高效率和方便管理,又對虛擬內存和物理內存又進行分頁(Page)。當進程訪問一個虛擬內存Page而對應的物理內存卻不存在時,會觸發(fā)一次缺頁中斷(Page Fault),分配物理內存,有需要的話會從磁盤mmap讀人數(shù)據。
通過App Store渠道分發(fā)的App,Page Fault還會進行簽名驗證,所以一次Page Fault的耗時比想象的要多:

- 2.3 重排
編譯器在生成二進制代碼的時候,默認按照鏈接的Object File(.o)順序寫文件,按照Object File內部的函數(shù)順序寫函數(shù)。
- 靜態(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
那么我們需要將啟動時候調用的函數(shù)進行重排,讓它們盡可能的分配在同一個頁;比如load方法我們就將其找出來,放到一起;LLVM支持我們通過設置order來達到這個效果

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