前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >启动优化

启动优化

原创
作者头像
ruochen
修改于 2021-11-25 04:57:06
修改于 2021-11-25 04:57:06
1.8K028
代码可运行
举报
运行总次数:28
代码可运行

启动优化

  1. 启动时间:从用户点击app图标开始到 AppDelegate 的didFinishLaunching
  2. 冷启动: 内存中不包含app相关数据的启动,一般我们可以通过重启手机来实现冷启动
    1. 热启动: 是指杀掉app进程后,数据仍然存在时的启动
  3. 启动优化 -> T1 + T2 需要启动优化的部分
    1. T1: pre-main阶段, 即main函数之前, 操作系统加载APP可执行文件到内存,执行一系列加载&链接等工作 -> dyld加载过程.
    2. main函数之后, 即从main函数开始, 到Appdelegate到didFinishLaunching方法执行完成为止, 主要是构建第一个界面,并完成渲染.
main函数之前的部分

Edit Scheme -> Run -> Arguments -> Environment Variables ->

添加'DYLD_PRINT_STATISTICS'设为1

image.png

pre-main字段说明

  • dylib loading time(动态库耗时)
  • rebase/binding time(偏移修正/符号绑定耗时)
    • rebase(偏移修正):任何一个app生成的二进制文件,在二进制文件内部所有的方法、函数调用,都有一个地址,这个地址是在当前二进制文件中的偏移地址。一旦在运行时刻(即运行到内存中),每次系统都会随机分配一个ASLR(Address Space Layout Randomization,地址空间布局随机化)地址值(是一个安全机制,会分配一个随机的数值,插入在二进制文件的开头),例如,二进制文件中有一个 test方法,偏移值是0x0001,而随机分配的ASLR是0x1f00,如果想访问test方法,其内存地址(即真实地址)变为 ASLR+偏移值 = 运行时确定的内存地址(即0x1f00+0x0001 = 0x1f01)
    • binding(绑定):,例如NSLog方法,在编译时期生成的mach-o文件中,会创建一个符号!NSLog(目前指向一个随机的地址),然后在运行时(从磁盘加载到内存中,是一个镜像文件),会将真正的地址给符号(即在内存中将地址与符号进行绑定,是dyld做的,也称为动态库符号绑定),一句话概括:绑定就是给符号赋值的过程
  • ObjC setup time (OC类注册的耗时):OC类越多,越耗时
  • initializer time(执行load和构造函数的耗时)

优化建议:

  1. 尽量少用外部动态库,苹果官方建议自定义的动态库最好不要超过6个,如果超过6个,需要合并动态库
  2. OC类越多越耗时
  3. 将不必须在+load方法中做的事情延迟到+initialize中,尽量不要用C++虚函数
  4. 如果是swift,尽量使用struct
main函数阶段的优化

didFinishLaunching方法中,主要是执行了各种业务,有很多并不是必须在这里立即执行的,这种业务我们可以采取延迟加载,防止影响启动时间。

didFinishLaunching中业务主要类型
  1. 【第一类】初始化第三方sdk
  2. 【第二类】app运行环境配置
  3. 【第三类】自己工具类的初始化等
main函数阶段的优化建议:
  • 减少启动初始化的流程,能懒加载的懒加载,能延迟的延迟,能放后台初始化的放后台,尽量不要占用主线程的启动时间
  • 优化代码逻辑,去除非必须的代码逻辑,减少每个流程的消耗时间
  • 启动阶段能使用多线程来初始化的,就使用多线程
  • 尽量使用纯代码来进行UI框架的搭建,尤其是主UI框架,例如UITabBarController。尽量避免使用Xib或者SB,相比纯代码而言,这种更耗时
  • 删除废弃类、方法

二进制重排原理

原理:

当进程访问一个虚拟内存page,而对应的物理内存不存在时,会触发缺页中断(Page

Fault),因此阻塞进程。此时就需要先加载数据到物理内存,然后再继续访问。这个对性能是有一定影响的。

基于Page Fault,我们思考,App在冷启动过程中,会有大量的类、分类、三方等需要加载和执行,此时的产生的Page Fault所带来的的耗时是很大的。

查看当前项目的缺页终端
  1. cmd + i 性能分析, 需要让子弹飞一会儿
  2. 选择System Trace
  3. 如下图配置查看缺页中断次数

image.png

从上面的Page Fault的次数以及加载顺序,可以发现其实导致Page Fault次数过多的根本原因是启动时刻需要调用的方法,处于不同的Page导致的。因此,我们的优化思路就是:将所有启动时刻需要调用的方法,排列在一起,即放在一个页中,这样就从多个Page Fault变成了一个Page Fault。这就是二进制重排的核心原理

查看文件执行顺序

  1. Build Setting -> Write Link Map File设置为YES, 如下图配置

image.png

  1. CMD+B编译demo,然后在对应的路径下查找 link map文件,如下所示,可以发现 类中函数的加载顺序是从上到下的,而文件的顺序是根据Build Phases -> Compile Sources中的顺序加载的
  2. Link Map是iOS编译过程的中间产物,记录了二进制文件的布局,需要在Xcode的Build Settings里开启Write Link Map File
代码语言:txt
AI代码解释
复制
1. Object Files 生成二进制用到的link单元的路径和文件编号
2. Sections 记录Mach-O每个Segment/section的地址范围
3. Symbols 按顺序记录每个符号的地址范围
ld

ld是Xcode使用的链接器,有一个参数order_file,我们可以通过在Build Settings -> Order

File配置一个后缀为order的文件路径。在这个order文件中,将所需要的符号按照顺序写在里面,在项目编译时,会按照这个文件的顺序进行加载,以此来达到我们的优化

-> 二进制重排的本质就是对启动加载的符号进行重新排列.

获取启动运行的函数呢
  1. hook objc_msgSend:我们知道,函数的本质是发送消息,在底层都会来到objc_msgSend,但是由于objc_msgSend的参数是可变的,需要通过汇编获取,对开发人员要求较高。而且也只能拿到OC 和 swift中@objc 后的方法
  2. 静态扫描:扫描 Mach-O 特定段和节里面所存储的符号以及函数数据
  3. Clang插桩:即批量hook,可以实现100%符号覆盖,即完全获取swift、OC、C、block函

Clang 插桩

llvm内置了一个简单的代码覆盖率检测(SanitizerCoverage)。它在函数级、基本块级和边缘级插入对用户定义函数的调用。我们这里的批量hook,就需要借助于SanitizerCoverage。

SanitizerCoverage官方文档(https://links.jianshu.com/go?to=https%3A%2F%2Fclang.llvm.org%2Fdocs%2FSanitizerCoverage.html%23tracing-

pcs)

AppOrderFiles

  1. 使用AppOrderFiles
  2. 在 Build Settings 里的 “Other C Flags” 中添加 -fsanitize-coverage=func,trace-pc-guard
    1. 如果是Swift项目,还需要额外在 “Other Swift Flags” 中加入-sanitize-coverage=func 和 -sanitize=undefined
代码语言:txt
AI代码解释
复制
//当然通过pod导入的, 可以在podfile配置也可以
代码语言:txt
AI代码解释
复制
post_install do |installer|
代码语言:txt
AI代码解释
复制
  installer.pods_project.targets.each do |target|
代码语言:txt
AI代码解释
复制
    target.build_configurations.each do |config|
代码语言:txt
AI代码解释
复制
      config.build_settings['OTHER_CFLAGS'] = '-fsanitize-coverage=func,trace-pc-guard'
代码语言:txt
AI代码解释
复制
      config.build_settings['OTHER_SWIFT_FLAGS'] = '-sanitize-coverage=func -sanitize=undefined'
代码语言:txt
AI代码解释
复制
    end
代码语言:txt
AI代码解释
复制
  end
代码语言:txt
AI代码解释
复制
end
源码解析
代码语言:txt
AI代码解释
复制
//原子队列,其目的是保证写入安全,线程安全
代码语言:txt
AI代码解释
复制
static  OSQueueHead queue = OS_ATOMIC_QUEUE_INIT;
代码语言:txt
AI代码解释
复制
//定义符号结构体,以链表的形式
代码语言:txt
AI代码解释
复制
typedef struct {
代码语言:txt
AI代码解释
复制
    void *pc;
代码语言:txt
AI代码解释
复制
    void *next;
代码语言:txt
AI代码解释
复制
}CJLNode;
代码语言:txt
AI代码解释
复制
/*
代码语言:txt
AI代码解释
复制
 - start:起始位置
 - stop:并不是最后一个符号的地址,而是整个符号表的最后一个地址,最后一个符号的地址=stop-4(因为是从高地址往低地址读取的,且stop是一个无符号int类型,占4个字节)。stop存储的值是符号的
 */
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;
    }
    
代码语言:txt
AI代码解释
复制
}
代码语言:txt
AI代码解释
复制
/*
代码语言:txt
AI代码解释
复制
 可以全面hook方法、函数、以及block调用,用于捕捉符号,是在多线程进行的,这个方法中只存储pc,以链表的形式
代码语言:txt
AI代码解释
复制
 - guard 是一个哨兵,告诉我们是第几个被调用的
 */
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
//    if (!*guard) return;//将load方法过滤掉了,所以需要注释掉
    
代码语言:txt
AI代码解释
复制
    //获取PC
代码语言:txt
AI代码解释
复制
    /*
代码语言:txt
AI代码解释
复制
     - PC 当前函数返回上一个调用的地址
     - 0 当前这个函数地址,即当前函数的返回地址
     - 1 当前函数调用者的地址,即上一个函数的返回地址
    */
    void *PC = __builtin_return_address(0);
    //创建node,并赋值
    CJLNode *node = malloc(sizeof(CJLNode));
    *node = (CJLNode){PC, NULL};
    
代码语言:txt
AI代码解释
复制
    //加入队列
代码语言:txt
AI代码解释
复制
    //符号的访问不是通过下标访问,是通过链表的next指针,所以需要借用offsetof(结构体类型,下一个的地址即next)
代码语言:txt
AI代码解释
复制
    OSAtomicEnqueue(&queue, node, offsetof(CJLNode, next));
代码语言:txt
AI代码解释
复制
}

获取所有符号并写入文件

注意:只要是汇编中的跳转都会被hook, 既有b,bl的指令都会被hook

代码语言:txt
AI代码解释
复制
extern void getOrderFile(void(^completion)(NSString *orderFilePath)){
代码语言:txt
AI代码解释
复制
    collectFinished = YES;
代码语言:txt
AI代码解释
复制
    __sync_synchronize();
代码语言:txt
AI代码解释
复制
    NSString *functionExclude = [NSString stringWithFormat:@"_%s", __FUNCTION__];
代码语言:txt
AI代码解释
复制
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
代码语言:txt
AI代码解释
复制
        //创建符号数组
代码语言:txt
AI代码解释
复制
        NSMutableArray<NSString *> *symbolNames = [NSMutableArray array];
代码语言:txt
AI代码解释
复制
        //while循环取符号
代码语言:txt
AI代码解释
复制
        while (YES) {
代码语言:txt
AI代码解释
复制
            //出队
代码语言:txt
AI代码解释
复制
            CJLNode *node = OSAtomicDequeue(&queue, offsetof(CJLNode, next));
代码语言:txt
AI代码解释
复制
            if (node == NULL) break;
代码语言:txt
AI代码解释
复制
            //取出PC,存入info
代码语言:txt
AI代码解释
复制
            Dl_info info;
代码语言:txt
AI代码解释
复制
            dladdr(node->pc, &info);
代码语言:txt
AI代码解释
复制
//            printf("%s \n", info.dli_sname);
代码语言:txt
AI代码解释
复制
            if (info.dli_sname) {
代码语言:txt
AI代码解释
复制
                //判断是不是OC方法,如果不是,需要加下划线存储,反之,则直接存储
代码语言:txt
AI代码解释
复制
                NSString *name = @(info.dli_sname);
代码语言:txt
AI代码解释
复制
                BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
代码语言:txt
AI代码解释
复制
                NSString *symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
代码语言:txt
AI代码解释
复制
                [symbolNames addObject:symbolName];
代码语言:txt
AI代码解释
复制
            }
代码语言:txt
AI代码解释
复制
        }
代码语言:txt
AI代码解释
复制
        if (symbolNames.count == 0) {
代码语言:txt
AI代码解释
复制
            if (completion) {
代码语言:txt
AI代码解释
复制
                completion(nil);
代码语言:txt
AI代码解释
复制
            }
代码语言:txt
AI代码解释
复制
            return;
代码语言:txt
AI代码解释
复制
        }
代码语言:txt
AI代码解释
复制
        //取反(队列的存储是反序的)
代码语言:txt
AI代码解释
复制
        NSEnumerator *emt = [symbolNames reverseObjectEnumerator];
代码语言:txt
AI代码解释
复制
        //去重
代码语言:txt
AI代码解释
复制
        NSMutableArray<NSString *> *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
代码语言:txt
AI代码解释
复制
        NSString *name;
代码语言:txt
AI代码解释
复制
        while (name = [emt nextObject]) {
代码语言:txt
AI代码解释
复制
            if (![funcs containsObject:name]) {
代码语言:txt
AI代码解释
复制
                [funcs addObject:name];
代码语言:txt
AI代码解释
复制
            }
代码语言:txt
AI代码解释
复制
        }
代码语言:txt
AI代码解释
复制
        //去掉自己
代码语言:txt
AI代码解释
复制
        [funcs removeObject:functionExclude];
代码语言:txt
AI代码解释
复制
        //将数组变成字符串
代码语言:txt
AI代码解释
复制
        NSString *funcStr = [funcs componentsJoinedByString:@"\n"];
代码语言:txt
AI代码解释
复制
        NSLog(@"Order:\n%@", funcStr);
代码语言:txt
AI代码解释
复制
        //字符串写入文件
代码语言:txt
AI代码解释
复制
        NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"cjl.order"];
代码语言:txt
AI代码解释
复制
        NSData *fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
代码语言:txt
AI代码解释
复制
        BOOL success = [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
代码语言:txt
AI代码解释
复制
        if (completion) {
代码语言:txt
AI代码解释
复制
            completion(success ? filePath : nil);
代码语言:txt
AI代码解释
复制
        }
代码语言:txt
AI代码解释
复制
    });
代码语言:txt
AI代码解释
复制
}
配置order文件

通过上面的方法生成order文件后, 配置 -> Build Setting -> Order File -> ./xxx.order

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
暂无评论
推荐阅读
FFmpeg常见的音视频处理方法
FFmpeg可使用众多参数,参数内容会根据ffmpeg版本而有差异 这里不再赘述,使用前建议先参考参数及编解码器的叙述。此外参数明细可用ffmpeg -h显示;编解码器名称等明细可用ffmpeg -formats显示。一些常用的参数也可以通过网上查找相关资料获取。
cohen
2020/12/25
3K0
FFmpeg常见的音视频处理方法
[1304]ffmpeg安装及使用
FFmpeg是一款功能强大的开源多媒体处理工具,它支持几乎所有的视频和音频格式,以及几乎所有的编解码器。
周小董
2024/11/24
1.8K0
[1304]ffmpeg安装及使用
【FFmpeg】ffmpeg 命令行参数 ⑤ ( 使用 ffmpeg 命令提取 音视频 数据 | 保留封装格式 | 保留编码格式 | 重新编码 )
使用 ffmpeg 命令 从 视频数据 中 提取 音频数据 / 视频数据 保留封装格式 , 封装格式 指的就是 封装 视频数据 的 容器 ;
韩曙亮
2024/03/12
1.2K0
【FFmpeg】ffmpeg 命令行参数 ⑤ ( 使用 ffmpeg 命令提取 音视频 数据 | 保留封装格式 | 保留编码格式 | 重新编码 )
FFmpeg从入门到精通(一)
FFmpeg既是一种音视频编解码工具,也是一组音视频解码开发套件。作为编解码开发套件,它为开发者提供了丰富的音视频处理的调用接口。
全栈程序员站长
2022/09/13
1.1K0
rodert教你学FFmpeg实战这一篇就够了
链接:https://pan.baidu.com/s/11kIaq5V6A_pFX3yVoTUvzA
JavaPub
2022/03/18
1.2K0
rodert教你学FFmpeg实战这一篇就够了
【FFmpeg】ffmpeg 命令行参数 ④ ( ffmpeg 视频参数解析 | 设置视频帧数 | 设置视频码率 | 设置视频帧率 | 设置视频宽高 | 设置视频宽高比 | 设置视频滤镜 )
在 ffmpeg 命令中 , -vframes 参数 的 作用是 指定要输出的视频帧数 , 通过该参数 可以 控制 视频处理的长度 , 即 : 在输出多少帧后 停止处理 视频流 ;
韩曙亮
2024/03/05
7.7K0
【FFmpeg】ffmpeg 命令行参数 ④ ( ffmpeg 视频参数解析 | 设置视频帧数 | 设置视频码率 | 设置视频帧率 | 设置视频宽高 | 设置视频宽高比 | 设置视频滤镜 )
ffmpeg常用命令
FFmpeg是一个强大的开源多媒体处理工具,它可以用于录制、转换以及流化音频和视频。它是一个跨平台的项目,可以在多种操作系统上运行,包括Windows、Mac OS和Linux。这个工具可以执行各种各样的音视频处理任务,包括但不限于:
Jensen_97
2024/04/18
4070
FFmpeg命令也就这几类了吧
FFmpeg作为多媒体处理领域的强大开源工具,提供了丰富的音视频处理命令,广泛用于视频转换、格式处理、流媒体传输等。FFmpeg操作基于命令行,灵活而高效,涵盖多种应用场景。本文将为您提供最全的FFmpeg命令讲解,从基础命令结构到各类详细应用示例,帮助您轻松掌握FFmpeg的实用技巧。
程序员的园
2024/11/14
1630
FFmpeg命令也就这几类了吧
ffmpeg针对音视频常规命令整理
FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。
HI hero
2021/12/22
2.2K0
【FFmpeg】ffmpeg 命令行参数 ① ( ffpeg 命令 -i 参数 指定 输入文件 / 输入流 | ffpeg 命令 -f 参数 指定 输出格式 | 指定 时间 相关参数 )
在 【FFmpeg】Windows 搭建 FFmpeg 命令行运行环境 博客中 , 搭建 FFmpeg 命令行 运行环境 , 就可以在 任意 目录位置 , 执行 ffmpeg 命令 ,
韩曙亮
2024/02/17
3.3K0
【FFmpeg】ffmpeg 命令行参数 ① ( ffpeg 命令 -i 参数 指定 输入文件 / 输入流 | ffpeg 命令 -f 参数 指定 输出格式 | 指定 时间 相关参数 )
FFmpeg 视频处理入门教程
它功能强大,用途广泛,大量用于视频网站和商业软件(比如 Youtube 和 iTunes),也是许多音频和视频格式的标准编码/解码实现。
ruanyf
2020/02/13
2.5K0
音视频八股文(2)--ffmpeg常见命令(1)
2.将下载得到的包中的执行文件ffmpeg.exe、ffplay.exe和ffprobe.exe拷贝到C:\Windows目录下。这样,在命令行窗口中便可以直接访问这些可执行文件。
福大大架构师每日一题
2023/06/08
1.1K0
音视频八股文(2)--ffmpeg常见命令(1)
第二弹:ffmpeg音视频处理流程、命令解析(1)
大家周末好,今天继续开始更新音视频系列文章;今天主要给大家分享ffmpeg和ffplay相关命令的使用!废话不多说,直接肝!
用户6280468
2022/03/21
6690
第二弹:ffmpeg音视频处理流程、命令解析(1)
C#进程调用FFmpeg操作音视频
因为公司需要对音视频做一些操作,比如说对系统用户的发音和背景视频进行合成,以及对多个音视频之间进行合成,还有就是在指定的源背景音频中按照对应的规则在视频的多少秒钟内插入一段客户发音等一些复杂的音视频操作。本篇文章主要讲解的是使用C#进程(Process)调用FFmpeg.exe进行视频合并,音频合并,音频与视频合并成视频这几个简单的音视频操作,还有些复杂的音视频操作后续有时间慢慢补上。
追逐时光者
2022/02/11
1.7K1
音视频基本概念和FFmpeg的简单入门
最近正好有音视频编辑的需求,虽然之前粗略的了解过FFmpeg不过肯定是不够用的,借此重新学习下;
乔达摩@嘿
2022/05/10
1.3K0
音视频基本概念和FFmpeg的简单入门
FFmpeg常用操作
ffmpeg是一个源于Linux的工具软件,是FLV视频转换器,可以轻易地实现FLV向其它格式avi、asf、 mpeg的转换或者将其它格式转换为flv。
码客说
2019/10/21
3.4K0
音视频平凡之路之FFmpeg全面介绍
FFmpeg中的FF全称是"Fast Forward",后面的mpeg全称是"Moving Picture Experts Group"(动态图像专家组),FFmpeg既是一款音视频编解码工具,也是一组音视频编解码开发套件,作为编解码开发套件,它为开发者提供了丰富的音视频处理的调用接口。
马上就说
2020/11/11
2.1K0
音视频平凡之路之FFmpeg全面介绍
探索FFmpeg
2015年,Michael Nidermayer移植了大量的代码到FFmpeg中,并辞职,希望两个项目可以共同发展,若能合并则更好
Noneplus
2020/01/22
9080
讲解FFMPEG H264/H265 编码延迟问题
在视频编码中,延迟是一个常见的问题。对于实时性要求较高的应用(如视频直播、视频会议等),延迟问题尤为重要。本文将重点讲解FFmpeg中H264和H265编码器的延迟问题,以及如何优化和降低编码延迟。
大盘鸡拌面
2023/12/10
2.5K0
C#进程调用FFmpeg操作音视频
在多媒体应用开发中,音视频处理是一个复杂且关键的领域。FFmpeg是一个强大的开源库,用于处理视频和音频数据。它提供了命令行工具,可以执行视频转换、编码、解码、流处理等多种任务。对于.NET开发者来说,C#提供了丰富的库和框架来处理各种编程任务,但直接操作音视频文件可能不是它的强项。幸运的是,我们可以利用C#调用FFmpeg的命令行工具来实现音视频处理。本文将详细介绍如何在C#中通过进程调用FFmpeg来操作音视频文件。
Michel_Rolle
2024/10/10
2.6K0
推荐阅读
相关推荐
FFmpeg常见的音视频处理方法
更多 >
LV.1
后端研发工程师
目录
  • 启动优化
    • main函数之前的部分
    • main函数阶段的优化
      • didFinishLaunching中业务主要类型
    • main函数阶段的优化建议:
  • 二进制重排原理
    • 查看当前项目的缺页终端
    • ld
      • 获取启动运行的函数呢
  • Clang 插桩
    • 源码解析
    • 配置order文件
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验