其實二進制文件重排很簡單啊,重點在於生成 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
須要先用 hf-prod-collect.sh 收集數據,而後塞給 hfsort 生成 hotfuncs.txt 文件。很好很強大,不過對於編程小白來講有必定的使用成本。node
PS:此方案來自於我寫了這篇文章後,jmpews 大神丟給我了個連接,受益不淺。(其實我啥都看不懂)git
在 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