IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> iOS之启动速度优化二进制重排和clang插装 -> 正文阅读

[移动开发]iOS之启动速度优化二进制重排和clang插装

iOS启动流程


1、点击APP图标后,内核创建APP进程

2、将APP的Mach-O可执行文件mmap进虚拟内存,加载dyld程序,接下来调用_dyld_start函数开始程序的初始化

3、重启手机/更新APP会先创建启动闭包,然后根据启动闭包进行相关的初始化

4、将动态库mmap进虚拟内存,动态库数量太多则这里耗时会增加

5、对动态库和APP的Mach-O可执行文件做bind&rebase,主要耗时在 Page In,影响 Page In 数量的是 objc 的元数据

6、初始化 objc 的 runtime,如果有了闭包,由于闭包已经初始化了大部分,这里只会注册 sel 和装载 category

7、+load 和静态初始化被调用,除了方法本身耗时,这里还会引起大量 Page In

8、初始化 UIApplication,启动 Main Runloop

9、执行 will/didFinishLaunch,这里主要是业务代码耗时

10、Layout,viewDidLoad 和 Layoutsubviews 会在这里调用,Autolayout 太多会影响这部分时间

11、Display,drawRect 会调用

12、Prepare,图片解码发生在这一步

13、Commit,首帧渲染数据打包发给 RenderServer,启动结束。

启动速度优化思路:

1、控制APP的可执行文件大小

2、控制动态库数量

3、控制Page In 次数

4、控制首帧渲染前业务逻辑相关耗时

5、控制首帧视图渲染耗时,即上面流程中的步骤10-12

优化启动速度


1、无用资源的优化

1、无用的类

2、无用的方法

3、无用的图片资源

清理方式参见:iOS之安装包优化以及瘦身_风雨「83」的博客-CSDN博客

2、二进制重排

二进制重排为什么会加快启动速度?

当APP进程访问一页虚拟内存page,而对应的物理内存不存在时,先触发缺页中断(Page Fault)阻塞当前进程,然后加载数据到对应物理内存(Release版本还要对加载的数据进行签名),所以缺页中断还是比较耗时的。假设APP启动时调用100个函数,这100个函数如果分布在100个不同的内存页,那会产生100次缺页中断。如通过二进制重排将这100函数分布到50个或者更少的内存页中,缺页中断的次数减半,启动速度就提升了 。

获取启动阶段Page Fault的次数

打开Instruments,选择System Trace工具

重启手机(热启动情况下系统已经做了加载缓存,产生缺页中断大幅减少,所以最好重启手机),然后点击启动,待首屏出现后停止,如下图:?

解决方案

获取启动阶段调用的函数符号然后编写order_file编译顺序文件然后在Build Settings -> Order File中配置一个后缀为order的文件路径是实现二进制重排的核心思路。目前业界获取启动阶段调用的函数符号主要有三种:

1、抖音通过静态扫描和运行时 Trace 等方法确定 order_file。该方案实现难度大(需要汇编、反汇编等知识),且只能覆盖部分符号(无法覆盖纯Swift、initialize、部分block 和 C++ 通过寄存器的间接函数调用)

2、手淘通过修改 .o 目标文件实现静态插桩,需要对目标代码较为熟悉,通用性不高

3、clang编译器静态插桩,目前业界已有成熟的库和方案

静态插桩:在 build settings->"Other C Flags"中添加"-fsanitize-coverage=func,trace-pc-guard"。如过项目中有 Swift 代码,还需要在 "Other Swift Flags" 中加入"-sanitize-coverage=fun"和"-sanitize=undefined",如下:

设置完参数-fsanitize-coverage=func,trace-pc-guard 再次运行会报错。报错原因是未到找两个方法,这两个回调方法设置完参数就需要手动去添加。

首先需要引入头文件然后实现缺失的两个方法// 参考文档:SanitizerCoverage — Clang 15.0.0git documentation

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


 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);
  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);
}

?再次编译运行,继续报错 未定义的符号 __sanitizer_symbolize_pc

?

?删除以后再次运行编译通过。再次运行。

打印函数数量

打印应用启动所需要的符号表

获取启动需要的所有符号并排重

# pragma mark clang插桩
//https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards
 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);
  for (uint32_t *x = start; x < stop; x++)
    *x = ++N;
 
  NSLog(@"函数个数%llu",N);
     
 }

// 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);
//全局容器存放符号,原子队列,线程安全,先进后出
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
typedef struct{
    void *pc;
    void *next;
}SYNode;

 void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
  
//  NSLog(@"%s",__func__);
//  if (!*guard) return;  // Duplicate the guard check.
  //*PC指的上一个函数的地址
  void *PC = __builtin_return_address(0);
//  Dl_info info;
//  dladdr(PC, &info);
//     NSLog(@"-----%s",info.dli_fname);
//  NSLog(@"dli_sname-----%s",info.dli_sname);
     SYNode *node = malloc(sizeof(SYNode));
     *node = (SYNode){PC,NULL};
     //添加数据函数符号
     OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
     

//  char PcDescr[1024];
//  printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}
+(void)getAllStartSymbolList{
    NSMutableArray *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];
        
//        if ([name hasPrefix:@"+["] || [name hasPrefix:@"-["]) {
//            [symbolNames addObject:name];
//            continue;
//        }
//        [symbolNames addObject:[@"_"stringByAppendingFormat:name]];
//        NSLog(@"dli_sname-----%s",info.dli_sname);

    }
    //对数据进行取反操作
//    symbolNames = (NSMutableArray *)[[symbolNames reverseObjectEnumerator] allObjects];
    //正向遍历
    //    NSEnumerator *en = [symbolNames objectEnumerator];
    NSEnumerator *en = [symbolNames reverseObjectEnumerator];
    NSMutableArray *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
    NSString *name;
    while (name =[en nextObject]) {
        if (![funcs containsObject:name]) {
            [funcs addObject:name];
        }
    }
    NSLog(@"%lu",(unsigned long)symbolNames.count);
    NSLog(@"funcs %lu",(unsigned long)funcs.count);
    //生成order文件
    NSString *funcStrs = [funcs componentsJoinedByString:@"\n"];
    //写入文件
    NSString *filePath = [NSTemporaryDirectory() stringByAppendingString:@"cars.order"];
    NSData *file = [funcStrs dataUsingEncoding:NSUTF8StringEncoding];
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil];
    
    NSLog(@"%@",NSHomeDirectory());
}

将导出的文件拖到工程主目录下并在build Setting中设置order file文件,

?设置好orderfile之后再次运行程序查看优化前和优化后的时间差

启动时间缩短了0.45秒。

启动link map 顺序查看

build setting 搜索link map 找到Write link map file 设置为YES 然后编译,编译完成后找Product->show build in folder in folder->Intermediates.noindex->Debug-iphoneos找到xx-LinkMap-normal-arm64.txt

?编译顺序根据

设置cars.order编译后link map

设置完成cars.order后再次编译查看link map?

?验证发现link map编译顺序和order文件中的一致,由此可见编译顺序优先安装order编译并启动加载。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-06-20 23:05:27  更:2022-06-20 23:05:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 2:28:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码