前言
APP启动优化大致分为Main函数之前
和Main函数
之后,Main函数之后的优化大多是使用懒加载
的方式或者优化业务加载顺序
,下面重点看一下Main函数之前
的优化。所谓冷启动优化就是应用第一次加载进内存的优化。
Pre-main时间
Main函数之前都干了什么?Xc缓存视频在手机哪里找ode
添加环境变量DYAPPLD_PRINT_STATISTICS
,日志看一下p多线程编程re-main
启动时间,此处demo以本人实际项目为例。
Xcode
运行项目,日志输出如下:
**Total pre-main time: 774.24 milliseconds (100.0%)**
**dylib loading time: 138.83 milliseconds (17.9%)**
**rebase/binding time: 5.88 milliseconds (0.7%)**
**ObjC setup time: 83.71 milliseconds (10.8%)**
**initializer time: 545.80 milliseconds (70.4%)**
**slowest intializers :**
**libSystem.B.dylib : 9.07 milliseconds (1.1%)**
**libMainThreadChecker.dylib : 50.17 milliseconds (6.4%)**
**libglInterpose.dylib : 185.33 milliseconds (23.9%)**
**推送助手 : 525.02 milliseconds (67.8%)**
可以看到pre-main
也就是main函数之前总耗时774.24
毫秒
-
dylib loading
:动态库的载入
。尽量减少自定义动态库,苹苹果12果建议大于6个
的话尽可能合并。 -
rebase/binding
:重定位/绑定。macho中的缓存视频在手机哪里找内存地址是偏移地址,需要加上首地址ASLR
变成真实的虚拟内存地址,这就是重定位
。区是于链接,链接是apple编译时,绑定是运行时,绑定是绑定的共享缓存的地址,外部符号越多绑定时间相对越多线程渲染长。 -
ObjC setup
:OC类的注册
。要尽可能的删除没有用到的类,虽然没有用到,但是只要存在,就会对类进行处理。 -
initializer time
:load()以及构造函数
。load()别做耗时
的事情,这是启动时间优化的重点,一般是优化业务逻辑
。缓存视频变成本地视频 -
slowest intializers
详细列出了ios下载initializer time
中具体耗时情况,我的项目中耗时最多的是主工程的加载,耗时525.02
毫秒
对于Main函数之前
的优化,先抛开主工程的加载,优化建议如下:
-
减少
自定义动态库,控制在6
个以内 -
删ios14.4.1更新了什么除
没有用到的类 -
load()别做耗时
的事情。appointment
虚拟内存
优化主工程的加载之前,先了解一下什么是虚拟内存
,简单总结如下。
- 一开始程序加载的内存是
物理内存
,但是软件发展速度太快了导致物理内存不够用,毕竟一缓存的视频怎么保存到本地个程序就占用一块物理缓存文件夹名称内存,而且直接使用物理内存存在安全隐患
,黑客很容易攻击这块内存。 - 为了让内存安全以及内存够用就产生了
虚拟内存
,现在我们所说的内存都是虚拟内存,CPU
上的一个模块会把虚拟内存
翻译成物理内存
,这样虚拟内存和物理内存就有一个映appointment射表
,为了高效的地址翻译,不可能一个字节一个字节的翻译,于是出现了内存分页管理
,即以页为单位翻译,iphone6s
之后一页是16
字节,iphon6多线程和多进程的区别s之前
一页是4
字节,所以iphone6s之后启动时间会比较缓存文件夹名称快,因缓存的视频怎么保存到本地为苹果官网内存翻译很快。虚拟内存地址很大有4G,这样就ios15解决了内存不够用的情况。 - 程序加载
不苹果7会把虚拟内存
根据映射表都加载进物理内存
中,而是用到哪一页或哪几页就把用到的加载进去缓存是什么意思
。比如当程序访问虚拟内存p1
页时,如果物理内存中没有,就缓存视频变成本地视频会发生缺页中断
,这时就要把这块虚拟内存加载进物缓存的视频怎么保存到本地理内存,ios程序的冷启动
是发生缺页中断
最多的地方,因为这时的物理内存中没有对应的虚拟内存,虽然加载进物理内存很快,但是如果有非常多缓存视频怎样转入本地视频的缺页中断,那么还是会影响效率
的。
总结:
过多的缺页中苹果8断
就会影响A多线程渲染PP启动速度,所以我们要尽可能的减少缺页中断
。比如启动Aapp小胖子PP时必须调用10个方法,但是这10个方法分布在10个不apple苹果官网同的页中,那application么就会发生10次缺页中断,如果把启动时必须调用的这10个方法放在同一个或者一缓存视频合并app下载两个页中,那么就会减少缺页中断的次数
。下面的重点就是如苹果13何重排二进制苹果xr
减少缺页中断的次数。
二进制重排前
Xcode
,command+control+i
启多线程和多进程的区别动inst缓存视频怎样转入相册ruments
选中System Trace
,点多线程的实现方式击启动,当APP启动后进入第一个页面,点击暂停,这时就会记录从启动到第一个页面的过程数据。搜索Main thread
查看虚拟内存中缺页中断情况
,如下图。
分析:缺页中断有384
次,耗时58.69
毫秒,启动耗时62.99
毫秒,缺页中断耗时apple苹果官网占了大部分。优化缺页中断次数的前提是有没有浪费,有浪费的缺页中断才需appreciate要优化。
启动时函数的加载顺序
我们上面说为了减少缺页中断,需要尽可能的把启动时函数的调用放在同缓存的视频怎么保存到本地一个页中,下面看下整个应用里函数加载进内存中的顺苹果x序。
Xcode->build setting->LAPPink Map->Write Lin多线程编程k Map File->YES
,编译后在bulid
中查看这个link map
文件如下:
link map
中这些函数调用是根据编译顺序排列
的,我们想要减少缺页中断,就要尽可能ios14.4.1更新了什么的把启动时的函数调用放在最前面
。可能你会觉得在Xcode中重新排列
文件的编译顺序就可以,但是我们是要把启动时调用的函数
放在application最前面,而不是启动时的文件放在最前面,毕竟文件中的函数有很多,但是只有个别函数在启动时会调用。
如何重排二进制
那么我多线程是并发还是并行们如何找出APP启动时调用了哪些函数呢?Clang
的插桩法可以很好的解决这个问题。根据Clang官方文档文档步骤如下:
1. Xcode->build setting->Other C Flags-多线程应用场景例子>-fsanitiz苹果xre-coverage=fuios越狱nc,trace-pc-guard
- 编写程序生成.order文件,比如gy.order,clang会根据此文件对link map文件中的函数重新排列,程序中需要实现的关键函数如下:
-
__sanitizer_cov_trace_pc_guard_init
:app中有多少个函数就会有多少个符号 -
__sanitizer_cov_trace_pc_guard
:hook所有回调函数,APP启动时
调用了多少个函数,就会调用多iOS少次该函数。clang在app小胖子编译时会把该函数插入到函数的开始位置。
具体demo如下:
#import <dlfcn.h>
#import <libkern/OSAtomic.h>
//定义原子队列
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
//定义符号结构体
typedef struct {
void * pc;
void * next;
} SYNode;
//里面反应了项目中符号的个数!!
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
uint32_t *stop) {
static uint64_t N;
if (start == stop || *start) return;
// printf("INIT: %p %p\n", start, stop);
for (uint32_t *x = start; x < stop; x++)
*x = ++N;
}
//HOOK一切的回调函数!!
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
void *PC = __builtin_return_address(0);
//创建结构体
SYNode * node = malloc(sizeof(SYNode));
*node = (SYNode){PC,NULL};
//结构体入栈
OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
}
//生成order文件!!
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//定义数组
NSMutableArray<NSString *> * symbleNames = [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);//转字符串
//给函数名称添加 _,c函数或者block函数需要加上_,oc函数不需要
BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
NSString * symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
[symbleNames addObject:symbolName];
}
//反向遍历数组,最后一个元素是启动时最先调用的
NSEnumerator * em = [symbleNames reverseObjectEnumerator];
NSMutableArray * funcs = [NSMutableArray arrayWithCapacity:symbleNames.count];
NSString * name;
//去除重复调用的函数
while (name = [em nextObject]) {
if (![funcs containsObject:name]) {//数组没有name
[funcs addObject:name];
}
}
//去掉touchbegin自己,touchbegin是测试生成排序文件的函数
[funcs removeObject:[NSString stringWithFormat:@"%s",__func__]];
//写入文件
//1.编程字符串
NSString * funcStr = [funcs componentsJoinedByString:@"\n"];
NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"gy.order"];
NSData * file = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil];
NSLog(@"%@",funcStr);
}
demo分析:
-
__sanitizer_coapplev_trace_pc_guard
,hook函数是多线程的,所以为了线程安全,需要定义一个原子队列
存放。 - 结构体入栈函数
OSAtomicEnqueue
中offsetof(SYNode, next)
目的是指定存放下一个函数的位置。
- 运行项目点击屏幕,拷贝沙盒中生成的
gy.order
文件放在项目根目录下,xcode->order File->./gy.ios系统order
,同时需要删除Xcode->bu苹果8plusild setting->Other C Flags->-fsanitize-coverage=func,trace-p苹果官网c-guard
配置。clang会根据gy.order重新排序函数编译的顺序,再来看一下link map是否重排成功了。
很明显link map
确实改变了,我们再Syappearstem Trace
看一下主线中缺页中断是否优化了。
对比一下没排序前,缺页中断有384
次,耗时58.69毫秒,启动耗时62.99毫秒。现在缺页中断271
,耗approach时40.03,启动耗时44.58,启动大概优化了不到20毫appear秒。目前测缓存视频在手机哪里找试的项目不是很大,如果项目很大的话,比如微信,那么这个启动优化还是很可观的。