启动优化 二进制重排

一、启动优化

冷启动:杀死app后的第一次启动
热启动:app还在后台运行,这个时候点开app
启动优化:一般讲的是冷启动

启动阶段:main函数之前、main函数之后

  • main 阶段:
    1、懒加载
    2、发挥CPU的价值(多线程进行初始化)
    3、启动时避免使用Xib、stroyboard

  • 阶段一、main函数之前

打印启动时间

添加 DYLD_PRINT_STATISTICS

4907887-cbca20970b4ed7da.png
3572360-8847c39e60dc77c7.png

dylib loading :加载可执行文件(App 的.o 文件的集合), 加载动态链接库;(优化:建议不要大于6个)
rebase/binding :对动态链接库进行 rebase 指针调整和 bind 符号绑定; 修正内部偏移指针/外部符号绑定 (优化:减少OC类) 优化少
Objc setup :Objc 运行时的初始处理,包括 Objc 相关类的注册、category 注册、selector 唯一性检查等;(优化:减少OC类) 优化少
initializer:包括了执行 +load() 方法、attribute((constructor)) 修饰的函数的调用、创建 C++ 静态全局变量 (优化:使用懒加载)

a.减少加载动态库的数量
谈到优化,映射到我们头脑中的***个想法就是:少做事!减少进程启动过程中的动态库数量,就成了当务之急。这里介绍几个方法:
(1)将一些无用的动态库去掉。有些程序员为了自己编程方便,把一些动态库不管是否真的使用全都链接上,这导致进程启动过程中加载了一些无用的动态库,浪费了时间。这些无用的动态库应坚决去掉。
(2)重新组织动态库的结构,力争将进程加载动态库的数量减到最少。对于使用标准C编写的动态库,可以考虑将几个小动态库合并为一个大的动态库,减少进程加载动态库的数量。
对于使用C++编写的动态库,由于涉及全局对象初始化的问题,笔者建议将大的动态库拆分为若干个小的动态库,进程根据自己的需要灵活加载所需要动态库。对于那些经常一同出现的动态库,可以考虑将其进行合并。
关于这点,可以参考2.1.5节,那里有更加详细的论述。
(3)将一些动态库编译成静态库,与进程或其他动态库合并,从而减少加载动态库的数量。其优点是:
减少了加载动态库的数量。
在与其他动态库(或进程)合并之后,动态库内部之间的函数调用不必再进行符号查找、动态链接,从而提高速度。
缺点是:
该动态库如果被多个动态库或进程所依赖的话,那么该动态库将被复制多份合并到新的动态库中,导致整体的文件大小增加,占用更多的Flash。
失去了动态库原有的代码段内存共享,因此可能会导致代码段内存使用上的增加。
如果该动态库被多个守护进程所使用,那么其代码段很多代码已经被加载到物理内存,那么进程在运行该动态库的代码时产生的page fault就少;如果该动态库被编译成静态库与其他动态库合并,那么其代码段被其他多个守护进程运行到的机会就少,在进程启动过程中运行到新的动态库时所产生的page fault就多,从而有可能影响进程的加载速度。
基于此,在考虑将动态库改为静态库时,有以下原则:
对于那些只被很少进程加载的动态库,要将其编译成静态库,从而减少进程启动时加载动态库的数量;同时由于该动态库代码段很少被多个进程共享,所以不会增加内存方面的开销。
对于那些守护使用的动态库,其代码段大多已经被加载到内存,运行时产生的page fault要少,故其为动态库反而有可能要比静态库速度更快。
(4)使用dlopen动态加载动态库。进程所依赖的动态库,并不一定在进程启动时都要用到。不需要的动态库,要在进程启动时加载动态库的清单中去掉,从而加快进程的启动速度。在需要该动态库时,再使用dlopen来动态加载动态库。
dlopen的优点是:可以精确控制动态库的生存周期,一方面可以减少动态库数据段的内存使用,另一方面可以减少进程启动时加载动态库的时间。
其缺点是:程序员编写程序将变得很麻烦。

减少动态库的个数,如果太多就使用合并的方式控制,这样可以节约dylib loading及rebase/binding的时间
清理项目中未用到的类、类别、方法等,这样可以节约Objc setup的时间
对于可以不在+load中处理的逻辑可以放到其他的函数中去处理,比如:+initialize;控制 C++ 全局变量的数量;这样可以节约initializer的时间

阶段二、main函数之后

main 开始 到 第一个界面。

打点,使用BLStopwatch.h和BLStopwatch.m这个类

3572360-84869ead32a4bed6.png
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    [[BLStopwatch sharedStopwatch] start];
    int a = 0;
    for (int i = 0; i < 10000000; i++) {
        a++;
    }
    [[BLStopwatch sharedStopwatch] splitWithDescription:@"didFinishLaunchingWithOptions"];
    
    return YES;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    //刷新时间:
    [[BLStopwatch sharedStopwatch] refreshMedianTime];
    
    int a = 0;
    for (int i = 0; i < 10000000; i++) {
        a++;
    };
    [[BLStopwatch sharedStopwatch] splitWithDescription:@"viewDidLoad"];
    
}
-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    //刷新时间:
    [[BLStopwatch sharedStopwatch] refreshMedianTime];
       
    int a = 0;
    for (int i = 0; i < 10000000; i++) {
        a++;
    };
    [[BLStopwatch sharedStopwatch] splitWithDescription:@"viewDidAppear"];
    [[BLStopwatch sharedStopwatch] stopAndPresentResultsThenReset];
    
}
3572360-5d6d8c857d6e4a9e.png

二、二进制重排

二进制重排是在main函数之前

物理内存
虚拟内存 : 解决安全问题、解决内存使用率问题

3572360-c3faf8a8c08c0860.png
3572360-3c6bf5034b46ba91.png

解决安全问题:映射表(页表)(虚拟页表)
解决内存使用率问题:内存分页管理。缺页中断,然后加载到物理内存,加载之前会签名加载的页;如果启动的时候要加载的代码分别在不同的页,那么缺页中断时间就比较长,这时就出现了二进制重排(把启动要加载的代码放在前面几页)。使用内存分页后,就会导致代码的加载都是从0开始的,为了防止黑客,就出现了ASLR。
内存分页技术
MacOS 、linux (4K为一页)
iOS(16K为一页)


企业微信截图_d18f6c36-db41-49da-a857-0dc24d8f3435.png

PageFault(缺页中断)
进程如果能直接访问物理内存无疑是很不安全的,所以操作系统在物理内存的上又建立了一层虚拟内存。为了提高效率和方便管理,又对虚拟内存和物理内存又进行分页(Page)。当进程访问一个虚拟内存Page而对应的物理内存却不存在时,会触发一次缺页中断(Page Fault),分配物理内存,有需要的话会从磁盘mmap读人数据。

通过App Store渠道分发的App,Page Fault还会进行签名验证,所以一次Page Fault的耗时比想象的要多:


4907887-419c87b69f0bcdbe.png
  • 2.3 重排

编译器在生成二进制代码的时候,默认按照链接的Object File(.o)顺序写文件,按照Object File内部的函数顺序写函数。

  • 静态库文件.a就是一组.o文件的ar包,可以用ar -t查看.a包含的所有.o
4907887-371bbad00ce8f801.png

简化问题:假设我们只有两个page:page1/page2,其中绿色的method1和method3启动时候需要调用,为了执行对应的代码,系统必须进行两个Page Fault。

但如果我们把method1和method3排布到一起,那么只需要一个Page Fault即可,这就是二进制文件重排的核心原理。

4907887-61b2c876a225594b.png
  • 2.4 Xcode配置Order

那么我们需要将启动时候调用的函数进行重排,让它们尽可能的分配在同一个页;比如load方法我们就将其找出来,放到一起;LLVM支持我们通过设置order来达到这个效果


4907887-9e3e6a151bb25ab0.png
  • 2.4.1 首先打开Write Link Map File查看

Link Map File中文直译为链接映射文件,它是在Xcode生成可执行文件的同时生成的链接信息文件,用于描述可执行文件的构造部分,包括了代码段和数据段的分布情况
我们可以在Xcode的配置中将Write Link Map File设置为YES来生成Map File


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