ios启动优化
关于ios启动优化的文章已经有很多了,这里作者只是做个笔记记录一下整个优化的过程中的关键技术点,方便后续查看~
打印启动耗时
Edit Scheme
-> Run
-> Arguments
-> Environment Variables
增加DYLD_PRINT_STATISTICS
,值为YES即可
Pre-main名词解释
Total pre-main time: 3.5 seconds (100.0%)
dylib loading time: 77.45 milliseconds (2.1%)
rebase/binding time: 131.04 milliseconds (3.6%)
ObjC setup time: 595.42 milliseconds (16.5%)
initializer time: 2.7 seconds (77.6%)
slowest intializers :
libSystem.B.dylib : 13.10 milliseconds (0.3%)
libglInterpose.dylib : 784.40 milliseconds (21.8%)
PackageTest : 3.5 seconds (99.4%)
dylib loading
:动态库加载时长,只能通过减少动态库数量来优化,可以考虑合并动态库。
rebase/binding
:在dyld加载阶段,由于ASLR技术所以对于动态库以及主程序的内存地址需要重新排列才可以访问,这个过程就是rebase。另外,对于外部函数的调用,例如:NSLog,dyld在加载主程序后需要把其内部的外部函数符号表中对应的方法真实实现地址进行绑定,这个过程就是binding。这个过程开发者暂无法干涉。
ObjC setup
:oc类的加载,只有通过减少oc类个数来优化,平时注意删除不用的oc类即可。
initializer
:load方法执行总时长。
slowest intializers
:load方法耗时中比较大的前几个。具体为啥后面的PackageTest还比总的大就不清楚了。
统计load方法耗时
通过环境变量OBJC_PRINT_LOAD_METHODS
可以查看项目中所有实现过load方法类。另外pod上有一个第三方库A4LoadMeasure
可以统计所有load方法
的耗时情况。
//debug环境下集成
pod 'A4LoadMeasure', configuration: ['Debug']
//集成后打印信息如下
Total load time: 1671.940207 milliseconds
JCORENetworkReachabilityManager load time: 1031.488061 milliseconds
_AFURLSessionTaskSwizzling load time: 262.246013 milliseconds
MOBFDevice load time: 164.516091 milliseconds
GDTAdManager load time: 57.882071 milliseconds
_SM_AFURLSessionTaskSwizzling load time: 24.365067 milliseconds
然后就可以根据情况看是否干掉对应的load方法,或者延时处理。另外说一下A4LoadMeasure
的统计原理比较有意思,有兴趣的同学也可以研究一下。
查看Page Fault次数
用Instrument
工具App Launch
跑一下然后输入
Main Thread
,选中你自己项目的主线程那一栏,然后选择Summary:Virtual Memory
,看Backed Page In
次数和耗时即可。
二进制重排
二进制重排主要是为了减少Page Fault的次数,那为什么会有Page Fault呢?主要是因为操作系统的内存管理方式决定的,操作系统会为每个进程分配虚拟内存,再加一张内存页表用于映射到物理内存,应用程序访问虚拟内存数据时操作系统会到对应的物理内存中读取真实数据,但当该块区域的真实物理数据还未加载时就会触发Page Fault。
核心思路是利用系统提供的hook方法,监听所有函数的调用,然后利用函数调用栈相关方法__builtin_return_address
获取原始函数的地址,打印其信息。最后在合适的时机(例如首页被加载出来了)结束整个函数方法的统计工作,生成order file
文件。
//首先在Build Settings中增加Clang编译器的执行参数。
-fsanitize-coverage=func,trace-pc-guard
//对swift编译器
-sanitize-coverage=func
-sanitize=undefined
//然后实现函数数量监听
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;
}
//函数被调用的监听回调
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
// if (!*guard) return;
//当前函数返回到上一个调用的地址!!
void *PC = __builtin_return_address(0);
//创建结构体!
SYNode * node = malloc(sizeof(SYNode));
*node = (SYNode){PC,NULL};
//加入结构!
OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
}
//根据内存地址读取函数名称
Dl_info info = {0};
dladdr(node->pc, &info);
NSString * name = @(info.dli_sname);
free(node);
相关资料:
iOS深思篇 | 启动时间的度量和优化
clang tracing-pcs
iOS Memory Deep Dive
iOS性能优化:优化App启动速度