之前可以通过设置DYLD_PRINT_STATISTICS = 1 变量在XCode上统计APP启动时间pre-main阶段,但是在XCode13.0 & iOS15之后,通过设置变量的方式在XCode上失效啦;
Instrument: App Launch & Time Profiler 取而代之
Xcode---> Product--->Profile进入到Instrument页面,选择App Launch(如下图:)
双击后,进入到App Launch界面:(如下图)
点击页面左上角红色按钮,注意选中真机,然后手机会自动启动APP,启动后停止运行
可以通过每个详细步骤的详情查看具体耗时情况(点击箭头可进入详情页)
可以根据分析数据针对项目情况进行相关步骤的优化
什么是dyld?
Dynamic linker, 动态链接器是每个应用程序的入口,负责让代码准备运行好。所以对dyld的任何改动都会影响应用程序的启动时间。在调用main、运行静态初始化方法或者设置Objc运行时之前,dyld会进行fixups工作(包括rebase、bind操作)这些操作修改了应用程序二进制文件中的指针,以保证在运行时所有地址的有效性。
iOS15中唯一的变化是数据现在由linkedit_data_command引用,这个结构中包含了第一个节点的偏移量
链式补丁格式(chain fixups)
在iOS15之前,rebase、bind和lazy bind各自存储在一个单独的表中。现在被合并成一个链,链的起点指针包含在这个新的加载命令中
应用程序的二进制文件文件被分成几个部分,每个部分都包含一连串的fixups,这些fixups可以是bind或者rebase(不再有lazy binds)。二进制文件的每个64位rebase[3]位置现在编码它所指向的偏移量,以及下一个fixups的偏移量,结构如下:
struct dyld_chained_ptr_64_rebase
{
uint64_t target : 36, /*用于指针target*/
high8 : 8,
reserved : 7, // 0s
next : 12, /*用于提供下一个fixup偏移量(stride=4)*/
bind : 1; // Always 0 for a rebase
};
这种非常紧凑的编码意味着遍历链的整个过程就可以覆盖整个二进制文件。在作者的测试中,超过50%的dyld都能够被新的格式系统所优化并最终减少二进制包的大小,只有少量的元数据被保留下来以引导每个page的第一次fixup
重点顺序问题
理解fixup格式背后的动机,必须了解应用程序最昂贵的操作之一:page fault。当应用程序启动过程中访问文件系统中的代码时,需要通过一个page fault将其从磁盘文件中带入内存。应用程序二进制文件中的每个16kb范围都被映射到内存中的一个页面。一旦页面被修改,只要应用程序不停止,它就需要留在RAM中(dirty page)。iOS通过压缩最近没有使用的页面来优化dirty page.
应用程序启动时的fixup需要改变应用程序二进制中的地址,因此整个page都不可避免被标记为dirty。
当使用表结构存储fixup数据时,首先要解决rebase,然后是bind。这意味着rebase需要很多page fault,并且大部分是IO绑定[4]。另一方面,bind所访问的page是rebase所使用的page的30%。
在iOS15中,链式fixup将每个内存page的所有变化合并在一起。Dyld现在可以更快地处理它,只需调整一次内存,就可以同时完成rebase和bind。这使得像内存压缩器这样的操作系统功能可以利用链式fixups中的信息,不需要在bind过程中回去解压旧page。这些变化,那么dyld中的rebase功能变成了一个无用的功能。
虽然这一变化只在以iOS15为目标是生效,但仍可以做很多事情来优化应用程序的启动时间。
Reduce number of dynamic frameworks 减少动态框架数量
Reduce app size so less memory pages are used 减少应用程序的大小,以减少内存也的使用
Move code out of +load and static initializers 移出+load和静态初始化中的代码
Use fewer classes 使用更少的类
Defer work to after drawing the first frame 延迟工作到第一帧绘制之后
以上部分是将上面链接中的部分进行了翻译,感兴趣看完整版,可直接看链接