性能优化 - 启动优化 (冷启动)

启动阶段: 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阶段 优化常规方式

  1. 懒加载
  2. 发挥CPU价值 (多线程进行初始化)
  3. 启动页少用XIB和Strotboard

Main 函数之后

main函数 到 第一个界面 使用工具类BLStopwatch 查看 在Application 打点开始 在第一个界面 打点结束

打点信息可以看到哪个方法比较耗时 就优化哪个函数

Main函数之前

需要只是点 虚拟内存 、 物理内存 fishHook 、 Clang Hook所有方法、二进制重排

二进制重排 原理

15873095584658.jpg

Page Fault:当进程访问一个虚拟内存Page而对应的物理内存却不存在时,会触发一次缺页中断(Page Fault),需要操作系统将其调入主存后再进行访问

Page Fault 为什么会出现:一个可执行文件可能很大,放在磁盘上,由局部性原理一次只将其中一部分读进内存

Instruments 调试工具 System Trace 查看Main函数之前耗时

File Backed Page In(Page Fault) 耗时

重排核心问题

  1. 重排效果怎么样 - 获取启动阶段的page fault次数
  2. 重排成功了没 - 拿到当前二进制的函数布局
  3. 如何重排 - 让链接器按照指定顺序生成Mach-O
  4. 重排的内容 - 获取启动时候用到的函数

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);
//
}

注意 : 生成.order文件之后需要删除clang相关代码

参考: 字节跳动技术团队

你可能感兴趣的:(性能优化 - 启动优化 (冷启动))