iOS App冷启动优化:二进制重排

原理

二进制文件中方法的加载顺序, 取决于方法在代码文件中的书写顺序,而不是调用顺序。 应用程序启动时会调用到的方法是有限的,但可能分散在很多个。 由于内存是分页管理的,要加载就要 整页加载**。** 这就导致很多完全还用不到的方法,会在应用启动时就会被加载到内存,这需要开辟大量内存页,进而增加大量启动耗时。
如果将BeforeMain阶段用到的方法都找出来出来,让他们在同一或相近的内存页中顺序提前加载,就可以大大减少应用启动时缺页异常出现的概率。

技术要点

**(**1)枚举出应用启动时会调用到的方法,将其保存到.order文件中

可行的方案是Clang插桩:

首先,我们在项目的Build Settings中搜索并设置 Other C Flags/ Other C++ Flags 为 -fsanitize-coverage=func,trace-pc-guard。如果有swift代码,需要设置 Other Swift Flags 设置为 **** -sanitize-coverage=func -sanitize=undefined, 这样应用启动时所有方法都的边缘都被插入了__sanitizer_cov_trace_pc_guard(...)方法的调用。

然后, 我们只需要实现__sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop)方法, 然后使用dladdr()方法, 就可以将被调用的方法信息枚举出来,然后将其写入.order文件中即可 。

objectivec 复制代码
//原子队列
static  OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
//定义符号结构体
typedef struct {
    void *pc;
    void *next;
}SYNode;

- (void)viewDidLoad {
    [super viewDidLoad];

    [self genrateOrderFile];
}
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) {
    void *PC = __builtin_return_address(0);
    SYNode *node = malloc(sizeof(SYNode));
    *node = (SYNode){PC,NULL};
    //写入队列
    OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
}
// 解析队列并生成.order文件
- (void) genrateOrderFile{
    NSMutableArray <NSString *> * symbolNames = [NSMutableArray array];
    while (YES) {
        SYNode * node = OSAtomicDequeue(&symbolList, offsetof(SYNode, 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];
        [symbolNames addObject:symbolName];
    }
    //取反
    NSEnumerator * enumerator = [symbolNames reverseObjectEnumerator];
    //去重
    NSMutableArray<NSString *> *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
    NSString * name;
    while (name = [enumerator nextObject]) {
        if (![funcs containsObject:name]) {
            [funcs addObject:name];
        }
    }
    [funcs removeObject:[NSString stringWithFormat:@"%s",__FUNCTION__]];
    
    //写入.order文件
    NSString * funcStr = [funcs  componentsJoinedByString:@"\n"];
    NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"demo.order"];
    NSData * fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
    NSLog(@"%@",funcStr);
}
@end

(2)设置应用在链接时, 根据.order文件顺序 加载其中的符号

只需在项目的Build Settings中搜索"Order File",配置.order文件即可

相关推荐
_饭团25 分钟前
字符串函数全解析:12 种核心函数的使用与底层模拟实现
c语言·开发语言·学习·考研·面试·蓝桥杯
前端摸鱼匠38 分钟前
面试题4:多头注意力(MHA)相比单头注意力的优势是什么?Head数如何影响模型?
人工智能·ai·面试·职场和发展·求职招聘
呆瑜nuage1 小时前
【复习系列】高频C/C++库函数手写实现指南与自定义类型的理解指南
c语言·c++·面试
li星野1 小时前
C++面试真题分享20260320
java·c++·面试
2501_915909062 小时前
不用越狱就看不到 iOS App 内部文件?使用 Keymob 查看和导出应用数据目录
android·ios·小程序·https·uni-app·iphone·webview
独自破碎E3 小时前
【面试真题拆解】Spring事务机制
java·spring·面试
@大迁世界3 小时前
液态玻璃屏正在侵蚀你的电池
macos·ios·objective-c·cocoa
程序员爱钓鱼3 小时前
Go PDF处理利器: github.com/pdfcpu/pdfcpu 深度指南
后端·面试·go
江湖十年3 小时前
使用 testing/synctest 测试并发代码
后端·面试·go
独自破碎E4 小时前
【面试真题拆解】5秒内限10次HTTP接口访问,结合数据结构和算法说说你的思路
数据结构·http·面试