Edit Scheme -> Run -> Arguments -> Environment Variables ->
添加'DYLD_PRINT_STATISTICS'设为1
image.png
pre-main字段说明
优化建议:
didFinishLaunching方法中,主要是执行了各种业务,有很多并不是必须在这里立即执行的,这种业务我们可以采取延迟加载,防止影响启动时间。
减少启动初始化的流程
,能懒加载的懒加载,能延迟的延迟,能放后台初始化的放后台,尽量不要占用主线程的启动时间去除非必须的代码逻辑
,减少每个流程的消耗时间多线程
来初始化的,就使用多线程原理:
当进程访问一个虚拟内存page,而对应的物理内存不存在时,会触发缺页中断(Page
Fault),因此阻塞进程。此时就需要先加载数据到物理内存,然后再继续访问。这个对性能是有一定影响的。
基于Page Fault,我们思考,App在冷启动过程中,会有大量的类、分类、三方等需要加载和执行,此时的产生的Page Fault所带来的的耗时是很大的。
image.png
从上面的Page Fault的次数以及加载顺序,可以发现其实导致Page Fault次数过多的根本原因是启动时刻需要调用的方法,处于不同的Page导致的。因此,我们的优化思路就是:将所有启动时刻需要调用的方法,排列在一起,即放在一个页中,这样就从多个Page Fault变成了一个Page Fault。这就是二进制重排的核心原理
查看文件执行顺序
image.png
1. Object Files 生成二进制用到的link单元的路径和文件编号
2. Sections 记录Mach-O每个Segment/section的地址范围
3. Symbols 按顺序记录每个符号的地址范围
ld是Xcode使用的链接器,有一个参数order_file,我们可以通过在Build Settings -> Order
File配置一个后缀为order的文件路径。在这个order文件中,将所需要的符号按照顺序写在里面,在项目编译时,会按照这个文件的顺序进行加载,以此来达到我们的优化
-> 二进制重排的本质就是对启动加载的符号进行重新排列.
llvm内置了一个简单的代码覆盖率检测(SanitizerCoverage)。它在函数级、基本块级和边缘级插入对用户定义函数的调用。我们这里的批量hook,就需要借助于SanitizerCoverage。
SanitizerCoverage官方文档(https://links.jianshu.com/go?to=https%3A%2F%2Fclang.llvm.org%2Fdocs%2FSanitizerCoverage.html%23tracing-
pcs)
-fsanitize-coverage=func,trace-pc-guard
-sanitize-coverage=func 和 -sanitize=undefined
//当然通过pod导入的, 可以在podfile配置也可以
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['OTHER_CFLAGS'] = '-fsanitize-coverage=func,trace-pc-guard'
config.build_settings['OTHER_SWIFT_FLAGS'] = '-sanitize-coverage=func -sanitize=undefined'
end
end
end
//原子队列,其目的是保证写入安全,线程安全
static OSQueueHead queue = OS_ATOMIC_QUEUE_INIT;
//定义符号结构体,以链表的形式
typedef struct {
void *pc;
void *next;
}CJLNode;
/*
- start:起始位置
- stop:并不是最后一个符号的地址,而是整个符号表的最后一个地址,最后一个符号的地址=stop-4(因为是从高地址往低地址读取的,且stop是一个无符号int类型,占4个字节)。stop存储的值是符号的
*/
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
uint32_t *stop) {
static uint64_t N;
if (start == stop || *start) return;
printf("INIT: %p - %p\n", start, stop);
for (uint32_t *x = start; x < stop; x++) {
*x = ++N;
}
}
/*
可以全面hook方法、函数、以及block调用,用于捕捉符号,是在多线程进行的,这个方法中只存储pc,以链表的形式
- guard 是一个哨兵,告诉我们是第几个被调用的
*/
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
// if (!*guard) return;//将load方法过滤掉了,所以需要注释掉
//获取PC
/*
- PC 当前函数返回上一个调用的地址
- 0 当前这个函数地址,即当前函数的返回地址
- 1 当前函数调用者的地址,即上一个函数的返回地址
*/
void *PC = __builtin_return_address(0);
//创建node,并赋值
CJLNode *node = malloc(sizeof(CJLNode));
*node = (CJLNode){PC, NULL};
//加入队列
//符号的访问不是通过下标访问,是通过链表的next指针,所以需要借用offsetof(结构体类型,下一个的地址即next)
OSAtomicEnqueue(&queue, node, offsetof(CJLNode, next));
}
获取所有符号并写入文件
注意:只要是汇编中的跳转都会被hook, 既有b,bl的指令都会被hook
extern void getOrderFile(void(^completion)(NSString *orderFilePath)){
collectFinished = YES;
__sync_synchronize();
NSString *functionExclude = [NSString stringWithFormat:@"_%s", __FUNCTION__];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//创建符号数组
NSMutableArray<NSString *> *symbolNames = [NSMutableArray array];
//while循环取符号
while (YES) {
//出队
CJLNode *node = OSAtomicDequeue(&queue, offsetof(CJLNode, next));
if (node == NULL) break;
//取出PC,存入info
Dl_info info;
dladdr(node->pc, &info);
// printf("%s \n", info.dli_sname);
if (info.dli_sname) {
//判断是不是OC方法,如果不是,需要加下划线存储,反之,则直接存储
NSString *name = @(info.dli_sname);
BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
NSString *symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
[symbolNames addObject:symbolName];
}
}
if (symbolNames.count == 0) {
if (completion) {
completion(nil);
}
return;
}
//取反(队列的存储是反序的)
NSEnumerator *emt = [symbolNames reverseObjectEnumerator];
//去重
NSMutableArray<NSString *> *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
NSString *name;
while (name = [emt nextObject]) {
if (![funcs containsObject:name]) {
[funcs addObject:name];
}
}
//去掉自己
[funcs removeObject:functionExclude];
//将数组变成字符串
NSString *funcStr = [funcs componentsJoinedByString:@"\n"];
NSLog(@"Order:\n%@", funcStr);
//字符串写入文件
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"cjl.order"];
NSData *fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
BOOL success = [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
if (completion) {
completion(success ? filePath : nil);
}
});
}
通过上面的方法生成order文件后, 配置 -> Build Setting -> Order File -> ./xxx.order
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。