二進(jìn)制重排為什么會(huì)減少啟動(dòng)時(shí)間?
編譯器把源文件編譯成
Mach-O
可執(zhí)行文件時(shí), 是按照Build Phases -> Compile Sources
中的文件順序進(jìn)行編譯各個(gè)類文. 在 App 啟動(dòng)時(shí),DYLD
并不會(huì)把所有二進(jìn)制都加載到內(nèi)存中等待調(diào)用, 當(dāng)調(diào)用某個(gè)方法或者函數(shù)時(shí), 內(nèi)存中已經(jīng)存在的不需要重新加載, 如果不存在就去加載, 這個(gè)加載過(guò)程會(huì)堵塞主線程, 是個(gè)耗時(shí)過(guò)程, 這個(gè)加載過(guò)程叫缺頁(yè)加載(Page Fault)
, 每次缺頁(yè)加載大概是 6 - 8 ms(抖音給出的時(shí)間), 這個(gè)時(shí)間并不是確定的, 和程序的實(shí)際情況有關(guān)系
App 的啟動(dòng)時(shí)間是從點(diǎn)擊 App 圖標(biāo)開始到第一個(gè)界面展示出來(lái)的時(shí)間, 可以劃分為 main 函數(shù)之前和main
函數(shù)之后,Page Fault發(fā)生在main
函數(shù)之后, 二進(jìn)制重排可以讓編譯器 不 按照Build Phases -> Compile Sources
中的文件順序進(jìn)行編譯各個(gè)類文, 而是按照我們指定的順序, 把 main 函數(shù)到第一個(gè)界面展示這之間用到的類文件等放在最前邊, 盡量減少 Page Fault 次數(shù), 心達(dá)到減少啟動(dòng)時(shí)間的目的.
為什么用Clang插莊的方式實(shí)現(xiàn)二進(jìn)制重排?
抖音通過(guò)手動(dòng)插樁獲取的符號(hào)數(shù)據(jù),包括C++靜態(tài)初始化、+Load、Block等都需要針對(duì)性處理,就其復(fù)雜度來(lái)說(shuō)感覺性價(jià)比不高;手淘的方案比較特殊,通過(guò)修改 .o 目標(biāo)文件實(shí)現(xiàn)靜態(tài)插樁,需要對(duì)目標(biāo)代碼較為熟悉,通用性不高;最后決定采用 clang 插樁的方式實(shí)現(xiàn)二進(jìn)制重排。
runtime 的 Method Swizzling 只能 Hook 到 OC 的方法.
fishhook 只能 Hook 系統(tǒng)的 C 函數(shù).
Clang 靜態(tài)插樁可以 Hook 到所有 OC 方法, C 函數(shù), Block, Swift 函數(shù), 閉包.
具體實(shí)現(xiàn)
1.添加配置
如果是OC項(xiàng)目
Build Setting -> Other C Flags
和Build Setting -> Other C++ Flags
添加fsanitize-coverage=func,trace-pc-guard
添加C默認(rèn)添加C++
如果含有swift
Build Setting -> Other Swift Flags
添加 -sanitize-coverage=func -sanitize=undefined
2. 代碼相關(guān)
eg:代碼添加的位置為第一個(gè)頁(yè)面的控制器,由任意一個(gè)點(diǎn)擊事件或者
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
觸發(fā)
#Clang 插樁需要的頭文件
#import <dlfcn.h>
#import <libkern/OSAtomic.h>
#pragma mark - 靜態(tài)插樁代碼
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
uint32_t *stop) {
static uint64_t N; // Counter for the guards.
if (start == stop || *start) return; // Initialize only once.
printf("INIT: %p %p\n", start, stop);
for (uint32_t *x = start; x < stop; x++)
*x = ++N; // Guards should start from 1.
}
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
//if (!*guard) return; // Duplicate the guard check.
void *PC = __builtin_return_address(0);
SymbolNode * node = malloc(sizeof(SymbolNode));
*node = (SymbolNode){PC,NULL};
//入隊(duì)
// offsetof 用在這里是為了入隊(duì)添加下一個(gè)節(jié)點(diǎn)找到 前一個(gè)節(jié)點(diǎn)next指針的位置
OSAtomicEnqueue(&symbolList, node, offsetof(SymbolNode, next));
}
//原子隊(duì)列
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
//定義符號(hào)結(jié)構(gòu)體
typedef struct{
void * pc;
void * next;
}SymbolNode;
-(void)writeToFile{
NSMutableArray<NSString *> * symbolNames = [NSMutableArray array];
//遍歷出隊(duì)
while (true) {
//offsetof 就是針對(duì)某個(gè)結(jié)構(gòu)體找到某個(gè)屬性相對(duì)這個(gè)結(jié)構(gòu)體的偏移量
SymbolNode * node = OSAtomicDequeue(&symbolList, offsetof(SymbolNode, next));
if (node == NULL) break;
Dl_info info;
dladdr(node->pc, &info);
NSString * name = @(info.dli_sname);
// 添加 _
BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
NSString * symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
//去重
if (![symbolNames containsObject:symbolName]) {
[symbolNames addObject:symbolName];
}
}
//取反
NSArray * symbolAry = [[symbolNames reverseObjectEnumerator] allObjects];
NSLog(@"%@",symbolAry);
//將結(jié)果寫入到文件
NSString * funcString = [symbolAry componentsJoinedByString:@"\n"];
NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"binary.order"];
NSLog(@"filePathfilePath:%@",filePath);
NSData * fileContents = [funcString dataUsingEncoding:NSUTF8StringEncoding];
BOOL result = [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
if (result) {
NSLog(@"%@",filePath);
}else{
NSLog(@"文件寫入出錯(cuò)");
}
}
#調(diào)用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self writeToFile];
}
3. 下載order文件
真機(jī)運(yùn)行程序,屏幕正常顯示之后調(diào)用[self writeToFile];
方法,此時(shí)的binary.order
文件就寫入到手機(jī)系統(tǒng)文件夾下,如圖查找文件
顯示包內(nèi)容,文件夾下搜索
binary.order
4.加載排序文件
將binary.order
放入到項(xiàng)目根目錄(或者其它目錄,路徑配置對(duì)就可以),build setting -> order file
5.運(yùn)行項(xiàng)目對(duì)比前后default page次數(shù)
參考:
iOS 啟動(dòng)優(yōu)化之二進(jìn)制重排
抖音研發(fā)實(shí)踐:基于二進(jìn)制文件重排的解決方案 APP啟動(dòng)速度提升超15%
深入探索 iOS 啟動(dòng)速度優(yōu)化