iOSApp 二進制文件重排

其實二進制文件重排很簡單啊,重點在於生成 order 文件。我基於 Clang SanitizerCoverage 和業界已有的經驗,整了個 AppOrderFiles,一個調用搞定!Enjoy it!php

1
2
3
AppOrderFiles(^(NSString *orderFilePath) {
    NSLog(@"OrderFilePath:%@", orderFilePath);
});

蘋果官方文檔的古老方案

蘋果的官方文檔很早就給了二進制文件重排的方案:Improving Locality of Reference,『早』到甚至被蘋果提示這份文檔已經年久失修,部分工具和連接失效了。文檔的過期不只體如今仍是 GCC 時代,連工具鏈好比像 gprof 也不能用了,不過 Google 也給出了 macOS 上的替代品,有興趣的能夠去研究下。html

Facebook 的 hfsort

須要先用 hf-prod-collect.sh 收集數據,而後塞給 hfsort 生成 hotfuncs.txt 文件。很好很強大,不過對於編程小白來講有必定的使用成本。node

PS:此方案來自於我寫了這篇文章後,jmpews 大神丟給我了個連接,受益不淺。(其實我啥都看不懂)git

基於 Clang SanitizerCoverage 的方案

在 Clang 10 documentation 中能夠看到 LLVM 官方對 SanitizerCoverage 的詳細介紹,包含了示例代碼。github

簡單來講 SanitizerCoverage 是 Clang 內置的一個代碼覆蓋工具。它把一系列以 __sanitizer_cov_trace_pc_ 爲前綴的函數調用插入到用戶定義的函數裏,藉此實現了全局 AOP 的大殺器。其覆蓋之廣,包含 Swift/Objective-C/C/C++ 等語言,Method/Function/Block 全支持。編程

開啓 SanitizerCoverage 的方法是:在 build settings 裏的 「Other C Flags」 中添加 -fsanitize-coverage=func,trace-pc-guard。若是含有 Swift 代碼的話,還須要在 「Other Swift Flags」 中加入 -sanitize-coverage=func 和 -sanitize=undefined。全部連接到 App 中的二進制都須要開啓 SanitizerCoverage,這樣才能徹底覆蓋到全部調用。app

基於 Clang SanitizerCoverage 我寫了個工具 AppOrderFiles。CocoaPods 接入,一行調用生成 Order File。啥也不說了,全在 GayHub 裏了:https://github.com/yulingtianxia/AppOrderFiles函數

固然這也不徹底是個人原創,對照着 Clang 文檔的同時,還參考了 Improving App Performance with Order Files 這篇文章的代碼。人家這篇文章雖然早就給出了,不過仍是有一些 bug 和優化空間的。工具

原理就是在 SanitizerCoverage 的回調函數裏將地址先收集到隊列裏,調用 AppOrderFiles() 後會中止收集,並將隊列中的 PC 地址依次翻譯符號,最後去重。反正代碼也很少,直接貼核心代碼:優化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static OSQueueHead queue = OS_ATOMIC_QUEUE_INIT;

static BOOL collectFinished = NO;

typedef struct {
    void *pc;
    void *next;
} PCNode;

// The guards are [start, stop).
// This function will be called at least once per DSO and may be called
// more than once with the same values of start/stop.
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
                                         uint32_t *stop) {
    static uint32_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.
}

// This callback is inserted by the compiler on every edge in the
// control flow (some optimizations apply).
// Typically, the compiler will emit the code like this:
//    if(*guard)
//      __sanitizer_cov_trace_pc_guard(guard);
// But for large functions it will emit a simple call:
//    __sanitizer_cov_trace_pc_guard(guard);
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    if (!*guard) return;  // Duplicate the guard check.
    if (collectFinished) {
        return;
    }
    // If you set *guard to 0 this code will not be called again for this edge.
    // Now you can get the PC and do whatever you want:
    //   store it somewhere or symbolize it and print right away.
    // The values of `*guard` are as you set them in
    // __sanitizer_cov_trace_pc_guard_init and so you can make them consecutive
    // and use them to dereference an array or a bit vector.
    *guard = 0;
    void *PC = __builtin_return_address(0);
    PCNode *node = malloc(sizeof(PCNode));
    *node = (PCNode){PC, NULL};
    OSAtomicEnqueue(&queue, node, offsetof(PCNode, next));
}

後記

蘋果官方也提供了 PGO 的詳細文檔,並且操做很簡單。不過它跟二進制文件重排仍是有區別的,這裏不展開講了。畢竟相對於對業務代碼加載優先級的優化來講,PGO 對啓動優化性價比沒那麼高,應該就是高頻調用函數內聯之類的。

參考:抖音研發實踐:基於二進制文件重排的解決方案 APP啓動速度提高超15% https://mp.weixin.qq.com/s/Drmmx5JtjG3UtTFksL6Q8Q

相關文章
相關標籤/搜索