APP启动优化——二进制重排,从入门到精通

一 理论介绍

1.1缺页中断

cpu加载数据到内存时,先根据数据对应的虚拟内存的地址,在页表找到其在物理内存中的地址。如果不存在相应的物理内存、该地址非法或没有权限,都会造成缺页中断。每个缺页中断耗时约0.6-0.8ms,虽然很短。但是消耗的时间 = 单次时间 * 次数。那什么时候,次数会非常大呢?冷启动的时候。尤其是对于大型APP,启动时调用的方法非常多。

举个例子,内存的布局如图,page1中有方法1-5,page2中有方法6-10,page3中有方法11-15。假如APP启动时调用方法1、6、11。则需要触发缺页中断3次。如果能将方法1、6、11收敛在一起,都放置在page1,则只会触发一次缺页中断。
(img)

11图1-1 方法分页布局.png

1.2 Linkmap

Linkmap是iOS编译过程的中间产物,记录了二进制文件的布局,里面记录了可执行文件的路径、CPU架构、目标文件、符号等信息。
linkmap主要包括三大部分,如下图

  • Object Files 生成二进制用到的link单元的路径和文件编号,如图从第3行开始;
  • Sections 记录Mach-O每个Segment/section的地址范围,如图从第15行开始;
  • Symbols 按顺序记录每个符号的地址范围,如图从第44行开始。
12图2-1 linkMap结构.png

其中Symbols中可以看到方法的地址、大小,其顺序是Build Phases中的Link Binary With Libraries中文件的顺序。我们的目标就是要修改这些方法的顺序。 下面分析一下详细步骤。

1.3 看二进制文件布局

xcode编译的工程,二进制文件内的数据、代码是如何分布的?Xcode提供了Write Link Map File选项。打开如图所示选项。

13-1writeLinkMap.png

接着在选择Products -> show in finder,看到项目名+LinkMap-normal-arm64.txt的文件,即为二进制文件的符号表。

13-2showBuildInFinder.png
13-3linkMap-normal-arm64txt.png
12图2-1 linkMap结构.png

从图中可以看到从第46行开始,即为符号地址、大小、方法名。第46行的0x100004218 + 0x00000054,相加结果即为第47行的0x10000426C。

我们的目标,就是重排这些顺序,使启动时调用的方法,收敛在一起,达到减少缺页中断个数的目的。

二 探索重排方案

如何找到启动时方法的调用顺序。其实比较容易想到的方法就是hook所有的方法。但是hook方案也非常多。

静态扫描+运行时trace。

有个团队也公开了自己的方法与效果。包括以下几种:

  • 扫描linkmap的__TEXT,__text,正则匹配拿到load方法,
  • 扫描linkmap的__DATA,__mod_init_func,C++静态初始化方法
  • 通过hook来获取oc方法、block符号。

但是initialize hook拿不到,部分block hook不到,C++通过寄存器的间接函数调用静态扫描不出来。
最终结论是覆盖率达到80%,启动速度提升了15%。

思维方式,自顶向下的思维方式

如果拿一个金字塔作比喻,那这种方法就是自底向下的方式,即最底层找所有的方法,有哪些种类,对其依次进行解决。这种思维方式也可以解决问题。但我可以转换一下思维:自顶向下。即从顶层向下层拆分,其需要满足MECE原则,即各部分之间满足两个原则

  • mutually exclusive,各部分之间相互独立 ,没有重叠、具有排他性。
  • collectively exhaustive,没有遗漏。

有一种测试覆盖率的方法就可以满足这些要求。

Clang SanitizerCoverage 的方案

想一想如果让我们测试代码覆盖率,我们可以怎么办?
Clang提供了sanitizer_cov_trace_pc_guard能力。其将代码分为函数、基本块、边界三类。 这样就可以覆盖所有的方法。
本来是用于测试代码覆盖率的,但其实也可以用在二进制重排中。
这种方式叫做静态插桩。将“桩”插入到了所有函数中。

三 Clang SanitizerCoverage操作步骤

1 打开选项

搜索Other C Flags,如图所示,添加-fsanitize-coverage=trace-pc-guard

31添加otherCFlag.png

添加完这个选项之后,即可在编译期,为每一个函数内部插入一行代码__sanitizer_cov_trace_pc_guard,以此来达到AOP的效果。

2 收集order file

接下来需要在APP首屏加载之后,调用方法AppOrderFiles,即可收集所有启动时调用的方法。
[https://github.com/yulingtianxia/AppOrderFiles]

因为用真机运行,在沙盒中可以拿到符号表文件,将其改名为app.order。准备写入order file文件。

3 写入order file文件

在链接阶段,可以修改即将生成的可执行文件的代码段进行重排。
Xcode使用的链接器是ld,ld有一个参数是Order File,通过配置路径$(SRCROOT)/Binary/app.order,并将文件放入工程相应的路径下即可,如图所示。

33-1appOrder路径.png
33-2appOrder路径2.png

四 效果验证

这一章节放在最后压轴,足以说明其重要性之高。验证效果应该是做性能优化的第一步,即通过制定一个目标,作为自己需要达到的标准与方向的指针,只有指针的指向正确,才能距离目标越来越近。
我们需要参考的指标有两个:缺页中断个数(毕竟直接优化的就是这个值)和启动时间。

指标1:缺页中断个数

打开 Instruments,选择 System Trace,运行之后。分析数据如图,选择“Main Thread”,底部的File Backed Page In即为缺页中断个数。


41-缺页中断数量.png

指标2:启动时间

虽然我们优化的是缺页中断的个数,但其最终目的还是启动时间。统计时间有几种:
1 打开Xcode的DYLD_PRINT_STATISTICS选项。
2 Instrument AppLaunch功能。

42-启动时间218s.png

如何分析数据

自动化平台

无疑这是最好的分析方式,只要有大量的用户数据,接着做一下可视化的分析,即可清晰看到效果。
但是有些团队可能没有很完善的平台,那是否可以使用手动的方式呢?

手动

冷启动与杀进程

为了避免缓存所造成的误差,需要杀进程,但杀进程 = 冷启动?显然并非如此,因为如果只是杀进程,因为内存还没有被其他进程使用,所以也没必要清空所有的缓存,苹果做了一些优化。即杀进程 != 冷启动。那如何保证尽可能得接近冷启动的效果呢?
杀进程之后,再多打开几个其他耗内存很高的APP。
并且删除Xcode的缓存~/Library/Developer/Xcode/DerivedData
这样虽然可以尽可能接近冷启动,但是每次完全编译,分析缺页中断的个数、启动时间。再优化前后对比2次。总共要完全重新编译4次。如果是比较大的项目,可能一个小时都不够。
并且,不同机型,也会有较大的差距。
所以,不建议使用手动的方式进行效果对比。

五 风险

order 文件里符号写错了或不存在会不会有问题

ld 会忽略这些符号,如果提供了 link 选项 -order_file_statistics,他们会以 warning 的形式把这些没找到的符号打印在日志里。

会不会影响上架

不会,order文件只是重新排列了所生成的 mach-O(可执行文件) 中函数表与符号表的顺序。

参考

[https://www.jianshu.com/p/52e0dee35830] iOS调优 | 深入理解Link Map File

[http://yulingtianxia.com/blog/2019/09/01/App-Order-Files/] App 二进制文件重排已经被玩坏了

[https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs] Clang SanitizerCoverage¶

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

推荐阅读更多精彩内容