简介
iOS的启动优化一般分为main函数之前和之后,mian函数之前(pre-main)主要分为4个阶段如下
- 动态库加载
- rebase/binding
- ObjC设置
- initializer time(执行load、c++构造函数)
我们可以通过设置运行参数DYLD_PRINT_STATISTICS打印出pre-main阶段所消耗的时间。
运行结果如下:
dylib toading time: 25.65 miTLiseconds (5.9%)
rebase/binding time: 178.54 milliseconds (41.6%)
ObjC setup time: 194.22 milliseconds (45.2%)
initializer time: 30.73 milliseconds(7.1%)
针对pre-main的优化
1.dylib loding time
针对动态库加载优化,苹果建议自定义动态库少于6个,多于6个就做合并处理。配置好库的加载路径,减少动态库查找流程。
2.rebase/binding time
1.虚拟内存概念
虚拟内存是一种计算机系统中的技术,它通过将物理内存与硬盘空间相结合,为用户提供了一个比实际物理内存容量大得多的可寻址空间。 虚拟内存的实现主要依赖于内存管理单元(MMU)和页表的使用,通过这些技术,CPU生成的虚拟地址被转换为物理地址,从而实现内存的扩展。
1.虚拟地址与物理地址的转换:
- 在虚拟内存系统中,CPU生成的是虚拟地址,这些地址在访问物理内存之前需要被转换为物理地址。这种转换是通过内存管理单元(MMU)和页表来实现的。页表存储了虚拟页面到物理页面的映射信息。
- 虚拟地址由两部分组成:虚页号和页内地址。虚页号用于在页表中查找对应的物理页帧,而页内地址则指定了该页面内的具体位置。
2.页表与内存管理
- 每个进程都有其自己的页表,用于将虚拟地址映射到物理地址。页表由操作系统管理,并根据需要动态更新。
- 当CPU需要访问某个虚拟地址时,MMU会查询页表以找到对应的物理地址,然后进行数据交换。如果所需页面不在物理内存中,则通过页面置换算法将其调入。
3.虚拟内存的管理方式:
- 虚拟内存的管理方式主要有段式、页式和段页式三种。这些方式根据程序的逻辑结构或物理内存的划分来管理虚拟内存。
- 段式管理按照程序的逻辑结构划分地址空间,便于实现存储保护和动态装配。页式管理则将虚拟空间和物理空间都分成固定或可变大小的页面,便于管理和提高内存使用效率。段页式管理结合了段和页的优点,但增加了硬件和软件的复杂性。
4.虚拟内存的优缺点:
- 优点包括能够扩展可用内存容量、简化内存管理、保护每个进程的地址空间不被破坏等。
- 缺点包括占用硬盘空间、可能增加对硬盘的读写操作、设置不当可能影响系统稳定性等。
2.rebase原理
App 启动 rebase(在 iOS 开发语境下通常指的是对应用内存地址的重新设定基地址的操作)主要是为了解决地址空间布局随机化(ASLR)带来的问题以及优化应用在运行时的内存布局。
当系统加载应用程序时,会为应用分配一块内存区域,但每次分配的起始地址是随机的(这是 ASLR 的安全机制)。App 启动 rebase 过程中,会根据当前实际分配到的内存起始地址,调整应用程序中各种代码段、数据段等的内存地址引用,使其指向正确的实际内存位置,确保应用能够正常运行并且正确地访问所需的资源和代码
流程步骤如下:
- 系统启动应用程序时,首先分配一块内存空间给应用。
-
- 此时应用程序的原始内存布局是基于预期的一个基地址设计的,但实际分配的内存地址与预期不同。
- 应用程序开始启动 rebase 操作:
-
- 读取应用程序的可执行文件以及相关的数据文件等,解析其中的各种内存地址引用。
- 逐个修正内存地址引用:
-
- 根据实际分配的内存起始地址和原始的内存地址偏移量,计算出正确的新内存地址。
- 将应用程序中所有涉及到的代码、数据等的内存地址引用都更新为新的正确地址。
- 完成 rebase:
-
- 确保所有的内存地址引用都已正确调整后,应用程序就可以基于新的内存布局正常运行了。此时应用程序已经适应了系统分配的实际内存地址,能够正确地访问各种资源和执行代码
在启动的时候rebase过程中很容易产生内存pagefault,那么pagefault是什么呢。
内存 PageFault(页面错误)指的是当程序试图访问一个虚拟内存页面,而该页面在物理内存中不存在或者其访问权限不符合要求时,触发的一种异常情况。频繁的 PageFault 会严重影响程序的性能,因为每次处理 PageFault 都需要进行磁盘 I/O 操作,而磁盘访问速度远远慢于内存访问速度。所以我们在app启动的时候要减少pagefault。如何减少呢?即将启动时需要的符号表放在一起即可,xcode在编译的时候会根据.order文件里面符号表进行编译,所以我们需要将启动所需要的符号生成一个order文件就行。
3.clang插桩获取启动所需要的符号表,并且生成一个order文件。
1.获取oc方法、函数、block
- 在xcode clang编译配置Other C Flags:-fsanitize-coverage=func,trace-pc-guard
- 在Other C++ Flags:-fsanitize-coverage=func,trace-pc-guard
- Other Swift Flags:-sanitize-coverage=func -sanitize=undefined
- 编写一下代码
#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>
#import <dlfcn.h>
#import <libkern/OSAtomic.h>
#import "TraceDemo-Swift.h"
interface ViewController ()
@property(nonatomic, assign) int age;
@end
@implementation ViewController
+(void)load
{
[SwiftTest swiftTest];
block();
}
void(^block)(void) = ^(void){
NSLog(@"block函数执行!");
};
/*
+(void)load
{
}
+(void)initialize{
}
void test(){
NSLog(@"test函数执行!");
block();
}
-(void)testsleep{
sleep(3);
}
*/
- (void)viewDidLoad {
[super viewDidLoad];
self.age = 10;
// Do any additional setup after loading the view.
}
//定义原子队列
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);//转字符串
//给函数名称添加 _
// if ([name hasPrefix:@"+["] || [name hasPrefix:@"-["]) {//OC方法 直接存
// [symbleNames addObject:name];
// continue;
// }
// [symbleNames addObject:[@"_" stringByAppendingString:name]];
BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
NSString * symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
[symbleNames addObject:symbolName];
}
//反向遍历数组
// symbleNames = (NSMutableArray<NSString *> *)[[symbleNames reverseObjectEnumerator] allObjects];
// NSLog(@"%@",symbleNames);
NSEnumerator * em = [symbleNames reverseObjectEnumerator];
NSMutableArray * funcs = [NSMutableArray arrayWithCapacity:symbleNames.count];
NSString * name;
while (name = [em nextObject]) {
if (![funcs containsObject:name]) {//数组没有name
[funcs addObject:name];
}
}
//去掉自己!
[funcs removeObject:[NSString stringWithFormat:@"%s",__func__]];
//写入文件
//1.编程字符串
NSString * funcStr = [funcs componentsJoinedByString:@"\n"];
NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"hank.order"];
NSData * file = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil];
NSLog(@"%@",funcStr);
}
@end
5. 将生成的order文件拷贝到工程目录,并且在Build Settings->Linking->Order File 添加路径 6. 将Build Settings->Link->Write Link Map File改为YES
3.ObjC setup time
oc类注册。移除废弃类,减少类的注册
4.initializer time
load函数调用以及c++构造函数的调用,减少耗时操作,如果真有耗时操作,那就放入到子线程。