美文网首页
App启动优化

App启动优化

作者: E术家 | 来源:发表于2020-04-27 10:08 被阅读0次

设置检查代码

对运行程序做设置
添加key值
对运行做设置
在Argument标签下添加DYLD_PRINT_STATISTICS作为key
运行后效果如下
  • Total pre-main time :

程序运行到main函数之前的时间

  • dylib loading time :

动态库的加载时间

  • rebase/binding time :

内部方法(自己定义的函数等)的偏移量修正+外部方法(系统方法,如NSLog)的绑定
当应用加载到内存的时候,系统会分配一个随机的内存地址,放在你文件的开头 —— ASLR
真实地址 = ASLR + 文件偏移地址

  • ObjC setup time :

OC每个类注册时间

  • initializer time :

load方法和initializer方法的执行时间

动态&静态 区别
  • 动态:方法 申明不实现 编译不报错
  • 函数:方法 申请不实现 编译报错 名称=指针
  • 总结:汇编时动态通过sender massage转汇编,静态的直接取函数地址使用

优化方案

main函数之前
  • 尽量减少或合并动态库,苹果官方建议动态库少于6个(dylib loading time)
  • 减少类的数量,冗余代码删除(rebase/binding time / ObjC setup time)
  • Swift语言是静态语言,所以在rebase/binding time耗时相对较少
main函数之后
  • 减少初始化流程 —— 懒加载/后台加载
  • 优化代码逻辑(主要的方案)
  • 启动阶段尽量使用多线程,尽量将CPU的性能发挥出来
  • 减少xlb/storyboard的使用

二进制重排 (重点)

iOS每个应用都有4G的连续虚拟内存空间

虚拟内存&物理内存
分页管理 iOS每一页16kb
虚拟内存映射表
优化了内存 但是存在部分安全问题
为了解决安全问题 使用ASLR技术
每次虚拟内存地址添加了一个随机的偏移值,防止通过固定值访问某些数据
缺页异常次数检查 command+i
使用工具

冷启动:内存中没有应用对应的数据或对应的数据被覆盖(并非杀死进程再启动就是冷启动)
热启动:内存中有应用对应的数据

程序运行的顺序

为了验证函数的执行顺序先分别在 AppDelegate.m 和 第一个加载的ViewController.m中添加Load方法



查看.m的存放顺序



系统会根据.m文件存放顺序加载
  • 获取当前应用的符号顺序



    在设置中将Write Link Map File调整为YES,编译后就可以到处对应的文件


    导出的文件

打开后能查看对应的文件加载顺序



找到对应的方法执行


这个顺序就是方法执行的顺序
我们可以手动的调整顺序
在工程目录下新建一个.order文件



在order文件中写入以下函数



在工程中指定order文件

重新编译下代码,并重新打开linkmap文件可以看到

运行的顺序已经根据我们设置的order文件来调整了

  • 备注 order 中如果写入了不存在的函数 不会被执行 系统默认会忽略

  • 因此,只要在order中调整启动时的函数执行顺序,就能对应用进行优化。

HOOK 所有方法

通过clang插桩

Clang相关文档
项目中设置 Other C Flags —— -fsanitize-coverage=trace-pc-guard
因为只查找方法 调整为 -fsanitize-coverage=func,trace-pc-guard


编译后报了错误

但是程序中没有用到这两个符号
通过文档得知,需要添加几个方法
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
                                                    uint32_t *stop) {
  static uint64_t N;  // Counter for the guards.
  if (start == stop || *start) return;  // Initialize only once.
  printf("INIT: %p %p\n", start, stop);
     printf("总计:%x",*(stop -1));
  for (uint32_t *x = start; x < stop; x++)
    *x = ++N;  // Guards should start from 1.
}

// This callback is inserted by the compiler on every edge in the
// control flow (some optimizations apply).
// Typically, the compiler will emit the code like this:
//    if(*guard)
//      __sanitizer_cov_trace_pc_guard(guard);
// But for large functions it will emit a simple call:
//    __sanitizer_cov_trace_pc_guard(guard);
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
  if (!*guard) return;  // Duplicate the guard check.
  // If you set *guard to 0 this code will not be called again for this edge.
  // Now you can get the PC and do whatever you want:
  //   store it somewhere or symbolize it and print right away.
  // The values of `*guard` are as you set them in
  // __sanitizer_cov_trace_pc_guard_init and so you can make them consecutive
  // and use them to dereference an array or a bit vector.
//  void *PC = __builtin_return_address(0);
  char PcDescr[1024];
  // This function is a part of the sanitizer run-time.
  // To use it, link with AddressSanitizer or other sanitizer.
//  __sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));
  printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}

头文件添加

#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>

编译后正常,然后控制台有以下输出



进入断点后打印结束指针的内容
内容 = 结束地址 - 0x4



得到一个 37
之后在代码中添加一个函数

再次执行



变成了 38
再试着添加一个block

再次执行

变成了 39
由此可知 Clang能抓取到所有的方法函数
直接修改汇编代码,在所有方法开头加入一段汇编代码,调用到一个函数里面去 —— 全局的AOP

深入研究下
建立原子队列和结构体

//原子队列
static  OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
//定义符号结构体
typedef struct {
    void *pc;
    void *next;
}SYNode;

在函数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));

在点击方法中导出列表

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSMutableArray <NSString *> * symbolNames = [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);
        //判断?
        BOOL  isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
        NSString * symbolName = isObjc ? name: [@"_" stringByAppendingString:name];
        [symbolNames addObject:symbolName];
    }
    //取反
    NSEnumerator * emt = [symbolNames reverseObjectEnumerator];
    //去重
    NSMutableArray<NSString *> *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
    NSString * name;
    while (name = [emt nextObject]) {
        if (![funcs containsObject:name]) {
            [funcs addObject:name];
        }
    }
    //干掉自己!
    [funcs removeObject:[NSString stringWithFormat:@"%s",__FUNCTION__]];
    //将数组变成字符串
    NSString * funcStr = [funcs  componentsJoinedByString:@"\n"];
    
    NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"snow.order"];
    NSData * fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
    NSLog(@"%@",funcStr);
}

运行程序,点击屏幕后就能得到对应的方法列表



之后在工程使用导出的order文件,就完成了二进制重排的操作。

相关代码:https://github.com/Snowxls/BinarySorting

相关文章

网友评论

      本文标题:App启动优化

      本文链接:https://www.haomeiwen.com/subject/epljwhtx.html