app冷启动三个阶段以及优化方案

app冷启动概括为3大阶段:

1、动态链接库, 启动app时,dyld会装载app的可执行文件,同时会递归加载所有依赖的动态库,进行 rebase 指针调整和 bind 符号绑定 装载完毕会通知Runtime

2、runtime  1)调用map—images进行可执行文件的解析和处理 2)调用load——methods,调用所有class和category的load方法  3)进行objc结果的初始化  4)c++静态初始化器和attribute修饰的函数

3、main() 函数执行后

dylib loading time:

加载动态链接库所耗的时间(每个库本身都有依赖关系,苹果公司建议使用更少的动态库,并且建议在使用动态库的数量较多时,尽量将多个动态库进行合并。数量上,苹果公司建议最多使用 6 个非系统动态库。)

rebase/binding time:

ASLR:全称为 Address Space Layout Randomization,地址空间布局随机化,它将进程的某些内存空间地址进行随机化来增大入侵者预测目的地址的难度,从而降低进程被成功入侵的风险。

mach-o文件正式采用了ASLR技术,每次启动时mach-o内存地址不一样。

rebase time:修正偏移的时间  因为ASLR技术,每个函数的实际地址是mach-o地址+方法的偏移地址

binding time:当引用外部函数时,比如NSLog,在编译时无法得到其内存地址,因为它不在当前进程中,它存储在iOS系统的共享缓存空间中(Foundation)。所以调用外部函数时,iOS系统在你的可执行文件中添加一个符号,等到运行时,由系统去绑定符号,找到真正的外部函数

objC setup time: 

这一步主要做了以下操作

注册Objc类 (class registration)

把category的定义插入方法列表 (category registration)

保证每一个selector唯一 (selctor uniquing)

优化方法:

减少class(类),selector(选择子)以及category(分类)这类元数据的数量(使用AppCode分析未使用的代码,可以看出有大量优化空间)

减少C++虚函数数量

使用swift stuct(其实本质上就是为了减少符号的数量)

 initializer time:

以上三步属于静态调整,都是在修改——DATA segment中的内容,而这里则开始动态调整,开始堆栈中写入内容,在这里的工作有以下几点:

用+ initialize方法代替+load方法

使用 dispatch_one() pthread_once() std::once() 代替 C/C++ __ atribute__((constructor))(__ attribute__((constructor))用法解析

减少静态构造函数

不要在初始化方法中调用 dlopen(),对性能有影响。因为 dyld 在 App 开始前运行,由于此时是单线程运行所以系统会取消加锁,但 dlopen() 开启了多线程,系统不得不加锁,这就严重影响了性能,还可能会造成死锁以及产生未知的后果。所以也不要在初始化器中创建线程。

总结一下

APP的启动由dyld主导,将可执行文件加载到内存,顺便加载所有依赖的动态库

并由runtime负责加载成objc定义的结构

所有初始化工作结束后,dyld就会调用main函数

接下来就是UIApplicationMain函数,AppDelegate的application:didFinishLaunchingWithOptions:方法

优化:

dyld阶段:

1)减少动态库,合并一些动态库(定期清理不必要的动态库)

2)减少Objc类、分类的数量、减少Selector数量(定期清理不必要的类、分类)

3)+load() 方法里的内容可以放到首屏渲染完成后再执行,或使用 +initialize() 方法替换掉。因为,在一个 +load() 方法里,进行运行时方法替换操作会带来 4 毫秒的消耗。不要小看这 4 毫秒,积少成多,执行 +load() 方法对启动速度的影响会越来越大。

4)控制 C++ 全局变量的数量。

main() 函数执行后

main() 函数执行后的阶段,指的是从 main() 函数执行开始,到 appDelegate 的 didFinishLaunchingWithOptions 方法里首屏渲染相关方法执行完成。

首页的业务代码都是要在这个阶段,也就是首屏渲染前执行的,主要包括了:首屏初始化所需配置文件的读写操作;首屏列表大数据的读取;首屏渲染的大量计算等。

功能级别的启动优化

优化的思路是: main() 函数开始执行后到首屏渲染完成前只处理首屏相关的业务,其他非首屏业务的初始化、监听注册、配置文件读取等都放到首屏渲染完成后去做。

1)在不影响用户体验的前提下,尽可能将一些操作延迟。不要都放在finishLaunching中

2)按需加载

方法级别的启动优化

检查首屏渲染完成前主线程上有哪些耗时方法,将没必要的耗时方法滞后或者异步执行。通常情况下,耗时较长的方法主要发生在计算大量数据的情况下,具体的表现就是加载、编辑、存储图片和文件等资源。

对 App 启动速度的监控,主要有两种手段:

第一种方法是,定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时。

Xcode 工具套件里自带的 Time Profiler ,采用的就是这种方式。这种方式的优点是,开发类似工具成本不高,能够快速开发后集成到你的 App 中,以便在真实环境中进行检查。说到定时抓取,就会涉及到定时间隔的长短问题。定时间隔设置得长了,会漏掉一些方法,从而导致检查出来的耗时不精确;而定时间隔设置得短了,抓取堆栈这个方法本身调用过多也会影响整体耗时,导致结果不准确。这个定时间隔如果小于所有方法执行的时间(比如 0.002 秒),那么基本就能监控到所有方法。但这样做的话,整体的耗时时间就不够准确。一般将这个定时间隔设置为 0.01 秒。这样设置,对整体耗时的影响小,不过很多方法耗时就不精确了。但因为整体耗时的数据更加重要些,单个方法耗时精度不高也是可以接受的,所以这个设置也是没问题的。总结来说,定时抓取主线程调用栈的方式虽然精准度不够高,但也是够用的。

第二种方法是,对 objc_msgSend 方法进行 hook 来掌握所有方法的执行耗时。hook objc_msgSend可以查看Facebook 开源了一个库,这个库叫 fishhookfishhook底层原理直通车。

只靠 fishhook 就能够搞定 objc_msgSend 的 hook 了吗?当然还不够。我前面也说了,objc_msgSend 是用汇编语言实现的,所以我们还需要从汇编层面多加点料。具体耗时检测的完整代码可查看链接,在需要检测耗时时间的地方调用 [SMCallTrace start],结束时调用 stop 和 save 就可以打印出方法的调用层级和耗时了。你还可以设置最大深度和最小耗时检测,来过滤不需要看到的信息。了这样一个检查方法耗时的工具,你就可以在每个版本开发结束后执行一次检查,统计总耗时以及启动阶段每个方法的耗时,有针对性地观察启动速度慢的问题。如果你在线上做个灰度开关,还可以监控线上启动慢的一些特殊情况。

参考:

https://time.geekbang.org/column/article/85331

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容