iOS底层 - 启动优化(下)

1,074 阅读5分钟
写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。

目录如下:

  1. iOS 底层原理探索 之 alloc
  2. iOS 底层原理探索 之 结构体内存对齐
  3. iOS 底层原理探索 之 对象的本质 & isa的底层实现
  4. iOS 底层原理探索 之 isa - 类的底层原理结构(上)
  5. iOS 底层原理探索 之 isa - 类的底层原理结构(中)
  6. iOS 底层原理探索 之 isa - 类的底层原理结构(下)
  7. iOS 底层原理探索 之 Runtime运行时&方法的本质
  8. iOS 底层原理探索 之 objc_msgSend
  9. iOS 底层原理探索 之 Runtime运行时慢速查找流程
  10. iOS 底层原理探索 之 动态方法决议
  11. iOS 底层原理探索 之 消息转发流程
  12. iOS 底层原理探索 之 应用程序加载原理dyld (上)
  13. iOS 底层原理探索 之 应用程序加载原理dyld (下)
  14. iOS 底层原理探索 之 类的加载
  15. iOS 底层原理探索 之 分类的加载
  16. iOS 底层原理探索 之 关联对象
  17. iOS底层原理探索 之 魔法师KVC
  18. iOS底层原理探索 之 KVO原理|8月更文挑战
  19. iOS底层原理探索 之 重写KVO|8月更文挑战
  20. iOS底层原理探索 之 多线程原理|8月更文挑战
  21. iOS底层原理探索 之 GCD函数和队列
  22. iOS底层原理探索 之 GCD原理(上)
  23. iOS底层 - 关于死锁,你了解多少?
  24. iOS底层 - 单例 销毁 可否 ?
  25. iOS底层 - Dispatch Source
  26. iOS底层 - 一个栅栏函 拦住了 数
  27. iOS底层 - 不见不散 的 信号量
  28. iOS底层 GCD - 一进一出 便成 调度组
  29. iOS底层原理探索 - 锁的基本使用
  30. iOS底层 - @synchronized 流程分析
  31. iOS底层 - 锁的原理探索
  32. iOS底层 - 带你实现一个读写锁
  33. iOS底层 - 谈Objective-C block的实现(上)
  34. iOS底层 - 谈Objective-C block的实现(下)
  35. iOS底层 - Block, 全面解析!
  36. iOS底层 - 启动优化(上)

以上内容的总结专栏


细枝末节整理


前言

上一篇,我们从理论上分析了启动优化的知识点。总结了为什么二进制重排可以在启动时间进行优化(将启动时刻调用的函数放在一起,减少了缺页中断的次数);那么这里的难点是,我们如何知道启动时刻项目调用了哪些方法。好,整理下今天的思路,第一: 定位到,APP启动时刻调用的项目中的方法;第二:生成order文件;第三:配置二进制重排。

优化之前记录

image.png

image.png

image.png

整理


缺页中断是2578次 耗时 520.31ms;

一次缺页中断耗时 大概 0.2ms

冷启动时间 642.26ms


Clang插桩HOOK一切

这里是一份Clang13的文档 有一节 Tracing PCs (跟踪CPU执行到的代码)

通过Clang插桩我们可以跟踪到所有函数的执行,当然会包括APP启动时刻所调用的。

添加Clang插桩标记

image.png

实现如下函数:

image.png

start 和 stop 放的是符号的个数

前闭后开区间,所以stop-4

image.png

通过回调回来的数据信息,我们可以知道:

  • 我们的项目中有 3 * 16+5+6 * 16 * 16+5 * 16 * 16 * 16 = 22069 个符号;

__sanitizer_cov_trace_pc_guard 回调中我们就可以拿到符号,我们就可以在这里将启动时刻调用的方法做保存,然后取出来,再生成 order 文件就可以了,下一步,我们开始收集启动时刻调用的方法

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
  
    if (!*guard) return;
  
    // 上一个函数的地址
    void *PC = __builtin_return_address(0);
    
    Dl_info info;
    
    dladdr(PC, &info);
    
    printf("%s\n", info.dli_sname);
  
//    printf("fname-%s\nfbase-%p\nsname-%s\nsaddr%p\n\n\n\n",info.dli_fname, info.dli_fbase, info.dli_sname, info.dli_saddr);
}

收集调用方法

自定义存储结构体

定义符号结构体用来存储我们要收集的符号: image.png

__sanitizer_cov_trace_pc_guard 回调中存储节点数据:

image.png

在 启动完成的地方 取出所有的节点数据并打印出来


注意这里存在以下问题,需要我们修复:

  • 顺序是反向的
  • 顺序有重复的
  • 函数需要我们手动加下划线

image.png

生成order文件

处理完成上一步骤三个问题 , 就可以开始着手处理生成了我们需要的 order 文件了。

解决上一步骤存在的三个问题: image.png

配置二进制重排

这里就可以去掉 Clang 插桩标记了

image.png

配置order文件

将我们生成的 order 文件 放到项目的根路径下,然后配置 Order file

image.png

配置好之后打开link map build 一下项目 验证:

image.png

完美,和我们的 order 文件顺序一致。

优化效果

image.png

image.png

File Backed Page In 从 2578次 减少到了 688次 145.08ms(减少了近74% 的 缺页中断)

启动时间 从 642.26ms 降低到 519.45ms (冷启动提升了近 19% 的时间)

效果还是挺不错的哦!!!

二进制重排.001.jpeg

补充

配置只在方法内回调到Clang插桩回调函数:

-fsanitize-coverage=func,trace-pc-guard