启动阶段: Main 之前 ,Main之后
查看启动时间
增加环境变量 Edit schmene -> Arguments -> Environment Variables -> DYLD_PRINT_STATISTICS
dylib loading time: 348.01 milliseconds (26.0%) // 动态库加载时间
rebase/binding time: 222.60 milliseconds (16.6%) // rebase:修正偏移指针ASLR binding:fishHook 里面的符号绑定 减少OC类
ObjC setup time: 44.97 milliseconds (3.3%) // runtime类注册
initializer time: 721.15 milliseconds (53.9%) // Load方法加载 (优化 懒加载 可以放在initializer)
slowest intializers :
libSystem.B.dylib : 4.55 milliseconds (0.3%)
libglInterpose.dylib : 435.84 milliseconds (32.6%) //调试相关
marsbridgenetwork : 46.28 milliseconds (3.4%)
WeChat : 259.34 milliseconds (19.4%) //主程序
Main阶段 优化常规方式
- 懒加载
- 发挥CPU价值 (多线程进行初始化)
- 启动页少用XIB和Strotboard
Main 函数之后
main函数 到 第一个界面 使用工具类BLStopwatch 查看 在Application 打点开始 在第一个界面 打点结束
打点信息可以看到哪个方法比较耗时 就优化哪个函数
Main函数之前
需要只是点 虚拟内存 、 物理内存 fishHook 、 Clang Hook所有方法、二进制重排
二进制重排 原理
Page Fault:当进程访问一个虚拟内存Page而对应的物理内存却不存在时,会触发一次缺页中断(Page Fault),需要操作系统将其调入主存后再进行访问
Page Fault 为什么会出现:一个可执行文件可能很大,放在磁盘上,由局部性原理一次只将其中一部分读进内存
Instruments 调试工具 System Trace 查看Main函数之前耗时
File Backed Page In(Page Fault) 耗时
重排核心问题
- 重排效果怎么样 - 获取启动阶段的page fault次数
- 重排成功了没 - 拿到当前二进制的函数布局
- 如何重排 - 让链接器按照指定顺序生成Mach-O
- 重排的内容 - 获取启动时候用到的函数
1、重排效果怎么样 - 获取启动阶段的page fault次数
Instruments 调试工具 System Trace 查看Main函数之前耗时
File Backed Page In(Page Fault) 耗时
2、重排成功了没 - 获取当前二进制的函数布局
查看 函数执行顺序
Build Setting -> link Map File 设置为YES
查看 文件
/Intermediates.noindex/TraceDemo.build/Debug-iphonesimulator/TraceDemo.build/TraceDemo-LinkMap-normal-x86_64.txt
3、如何重排 - 让链接器按照指定顺序生成Mach-O
生成 .Order 文件 放根目录 里面放着函数执行顺序
Build Setting -> Order File 设置.Order 文件
4、重排的内容 - 获取启动时候用到的函数
使用 clang 生成 order文件
插庄 clang HOOK所有方法
http://clang.llvm.org/docs/SanitizerCoverage.html 官网
OC 配置
build Setting -> other c Flags -fsanitize-coverage=func,trace-pc-guard
Swift 配置
build Setting -> other swift flags 加入以下两个配置
-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; // 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 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.
void *PC = __builtin_return_address(0);
char PcDescr[1024];
// This function is a part of the sanitizer run-time.
// To use it, link with AddressSanitizer or other sanitizer.
__sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));
printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}
每个函数、方法 、load、initialize、block 都会先调用__sanitizer_cov_trace_pc_guard 函数
利用 clang 生成 .order文件
使用 clang 获取 执行过的函数 、 方法 之后生成.oder文件
#import
#import
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSMutableArray * 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 * emt = [symbolNames reverseObjectEnumerator];
//去重
NSMutableArray *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
NSString * name;
while (name = [emt nextObject]) {
if (![funcs containsObject:name]) {
[funcs addObject:name];
}
}
//干掉自己!
[funcs removeObject:[NSString stringWithFormat:@"%s",__FUNCTION__]];
//将数组变成字符串
NSString * funcStr = [funcs componentsJoinedByString:@"\n"];
NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"hank.order"];
NSData * fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
NSLog(@"%@",funcStr);
}
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.
}
//原子队列
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
//定义符号结构体
typedef struct {
void *pc;
void *next;
}SYNode;
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
// if (!*guard) return; // Duplicate the guard check.
/* 精确定位 哪里开始 到哪里结束! 在这里面做判断写条件!*/
void *PC = __builtin_return_address(0);
SYNode *node = malloc(sizeof(SYNode));
*node = (SYNode){PC,NULL};
//进入
OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
// Dl_info info;
// dladdr(node->pc, &info);
// printf("fname:%s \nfbase:%p \nsname:%s \nsaddr:%p\n",
// info.dli_fname,
// info.dli_fbase,
// info.dli_sname,
// info.dli_saddr);
//
}