iOS性能优化:优化App启动速度

 2024-01-31 01:01:56  阅读 0

苹果是一家特别注重用户体验的公司。 这几年一直在优化App的启动时间。 尤其是在去年的 WWDC 2019 上[1],有人提到苹果开发团队在过去的一年里将启动时间提高了 200%。

虽然说增加了200%,但有些问题还是没有明确说明,比如:

那么今天我们结合-423-App[2]来聊聊启动相关的事情

词汇表

首先介绍一些与启动相关的名词。

马赫-O

Mach-O是iOS系统不同运行时期可执行的文件类型的统称。 主要分为以下三类:

Mach-O的基本结构如下图所示,分为三个部分:

大多数 Mach-O 文件包括以下三种类型:

图像

指的是Dylib或者一种。

有很多这样的名称,但在本文中,它指的是一个 dylib,它周围有一个特殊的目录结构来保存该 dylib 所需的文件。

虚拟内存( )

虚拟内存被构建为物理内存和进程之间的中间层。 它是一个连续的逻辑地址空间,逻辑地址不需要与实际的物理内存地址对应,也可以将多个逻辑地址映射到一个物理内存地址。

页面错误

当进程访问与物理地址不对应的逻辑地址时,就会发生页面错误。

懒惰的

如果要读取的页面不在内存中,则会触发页面错误。 系统通过调用mmap()函数来读取指定的页面。 这个过程称为惰性。

COW(写时复制)

当进程需要修改某个页的内容时,内核会先复制需要修改的部分,然后修改,并将逻辑地址重新映射到新的物理内存。 这个过程称为写时复制

脏页和干净页

加载图像后,内容被修改的页面称为脏页面,并且将包含特定于进程的信息。 相反的称为Clean Page,它可以从磁盘重新生成。

共享内存(共享RAM)

当多个Mach-O依赖同一个Dylib(例如UIKit)时,系统会让这些Mach-O的Dylib调用的逻辑地址指向同一个物理内存区域,从而实现内存共享。 脏页是进程独有的,不能共享。

地址空间布局随机化 (ASLR)

当Image加载到逻辑地址空间时,系统会利用ASLR技术,使Image的起始地址始终随机,以防止黑客通过起始地址+偏移量找到函数的地址。

当系统使用ASLR分配随机地址时,从0到该地址的整个范围将被标记为不可访问,即无法读取、写入或执行。 该区域就是段,其大小在32位系统上为4KB+,在64位系统上为4GB+

代码符号

代码签名可以让iOS系统保证要加载的Image的安全性。 当使用 Code Sign 设置签名时,将为每个内容页面生成一个单独的加密哈希值并存储在其中。 系统会在加载时验证每个页面。 页面内容确保未被篡改。

dyld()

dyld是iOS上的二进制加载器,用于加载图像。 很多人认为dyld只负责加载应用程序所依赖的所有动态链接库。 这种理解是错误的。 dyld工作的具体流程如下:

外部类调用内部类方法_外部调用符号是什么_ios外部调用类方法

参考:dyld启动流程[3]

加载

dyld 会在加载 Mach-O 之前解析并加载,然后它就会知道 Mach-O 依赖什么,依此类推,递归加载所有需要的 dylib。

一般来说,一个App大约依赖100-400个dylib,其中大部分是系统dylib。 因为有缓存和共享,所以读取速度比较高。

修复

由于ASLR和Code Sign,新加载的dylib处于相对独立的状态。 为了绑定它们,需要一个修复过程。 修复有两种主要类型:和绑定。

PIC(代码)

由于代码签名,dyld 无法直接修改指令。 然而,为了实现运行时的Fix-ups,在代码生成过程中,使用动态PIC(代码)技术来启用由于代码签名限制而无法修改的代码。 加载到间接地址。 当要调用一个方法时,首先在段中创建一个指针指向该方法,然后通过这个指针实现间接调用。

它是一个数据校正的过程,旨在解决“因为ASLR导致Mach-O在加载到内存时有一个随机的首地址”的问题。 内部指针地址将添加一个偏移量。 偏移量计算如下:

  Slide = actual_address - preferred_address

所有必需的指针信息已被编码。 那么就需要反复将这个偏移量添加到需要的指针上。 在此过程中可能会持续出现和COW,导致I/0性能损失。 但由于处理的是连续地址,内核会提前读取数据以减少I/O消耗。

它是绑定被调用的外部符号的过程。 例如,我们想使用符号$,但是这个符号在Mach-O中没有,需要从UIKit中获取。 因此,我们需要将这些对应关系绑定在一起。

在运行时,dyld需要找到符号名称对应的实现。 这需要大量的计算,包括查看符号表。 一旦找到,对应的值就会记录在指针中。 虽然计算量比 多,但实际需要的 I/O 操作很少,因为之前已经做过了。

dyld2 和 dyld3

外部类调用内部类方法_外部调用符号是什么_ios外部调用类方法

在iOS 13之前,所有第三方应用程序都使用dyld 2来启动应用程序。 主要流程如下:

以上所有过程都发生在App启动时,涉及大量的计算和I/O,所以为了加快启动速度,Apple开发团队在 - 413 - App Time: Past, 和 [4] 上正式提出了dyld3 。

dyld3 分为三个组件:

用于运行启动关闭的进程内引擎

一个启动关闭缓存服务

dyld 3提前处理了很多耗时的搜索、计算和I/O,大大提高了启动速度。

应用程序启动

上面介绍了很多名词之后,我们正式进入正题。

为什么应用程序启动如此重要?

所以我们需要优化启动时间。

启动类型

应用程序启动类型分为三类:

温暖是一个热的开始。 热启动需要满足以下条件:

指继续暂停的App的过程,需要满足以下条件:

应用程序启动阶段

App启动分为三个阶段

此时,苹果再次强调,建议“从用户点击App图标到能够再次交互的时间,也就是第二阶段结束”应该在400ms以内。 目前,大多数应用程序还没有实现这一目标。

接下来,我们将以上三个阶段分为以下六个部分,谈谈这些阶段做了什么以及可以优化什么。

外部类调用内部类方法_ios外部调用类方法_外部调用符号是什么

为了准备初始化App,系统主要做了两件事:Load和init。

2017年,苹果介绍了dyld3给系统应用带来了多少优化。 今年,dyld3正式开发出来供开发者使用,这意味着iOS系统将会缓存你的热启动运行时依赖。 达到减少启动时间的目的。 这也是涨幅达到200%的原因之一。

视频只说优化了热启动时间。 理论上dyld3应该也能针对iOS系统进行冷启动时间的优化,所以不知道是因为iPad增加了多任务功能还是因为所有功能都没有打开。 作者只提到了热启动。 原因尚不清楚。

另外,在Load阶段,开发者还可以进行以下优化:

有些人可能会困惑我们是否使用dyld3,所以我们不需要做Link。 其实还是有需要的。 有兴趣的话可以看一下 vs dyld3[5] 的文章,里面有详细的数据对比。

init部分主要加载一些优先级较低的系统组件。 这部分时间是固定成本,所以我们开发者不需要关心。

这个阶段主要是-C和Swift的初始化,所有的+load方法都会被调用,并且类信息会注册在里面。

现阶段,原则上不建议开发者做任何事情,所以为了避免一些启动时间的损失,可以做以下事情:

用户界面工具包

这个阶段主要做了两件事:

因此,这个阶段的优化比较简单。 你需要做两件事:

这个阶段主要是生命周期方法的回调,这也是开发者最熟悉的部分。

调用e的App生命周期方法:

  application:willFinishLaunchingWithOptions: 
  application:didFinishLaunchingWithOptions:

和 e 的 UI 生命周期方法:

  applicationDidBecomeActive:

同时,iOS 13 添加了新的回调:

  scene:willConnectToSession:options:
  sceneWillEnterForeground:
  sceneDidBecomeActive:

也会在这个阶段被调用。 如果您有兴趣,可以关注Most out of this。 暂时没有视频资源。 怀疑现场演示被推翻,因此视频资源尚未发布。

在这个阶段,开发者可以优化:

第一帧

该阶段主要做视图的创建、布局和绘制工作,并将准备好的第一帧提交给渲染层进行渲染。 经常调用以下函数:

 loadView
 viewDidLoad 
 layoutSubviews

在这个阶段,开发者可以优化:

更多详情请参见 - 220 - 高自动 [6]

大多数应用程序异步获取数据并最终将其呈现给用户。 我们称这部分为。

因为这部分每个App的表现都不同,苹果建议开发者先用测量的方式,然后慢慢分析优化。

测量应用程序启动时间

要发现启动过程中的问题,需要进行多次测量并比较前后情况。 但如果变量控制不好,就会产生错误。

因此,为了保证测量数据能够真实反映问题,我们需要减少不稳定因素,保证测量在可控、相似的环境中进行。 最后使用一致的结果进行分析。

条件一致性

为了保证环境的一致性,我们可以做以下事情:

帐户切换会影响性能,因此请勿切换帐户或禁用帐户。

测量注意事项

关于使用测试启动时间的信息,可以看-417-Life和[7],但是我测试了一下,好像还有一些API没有开放,还不能使用。

使用分析和优化应用程序开始流程优化

苹果给了我们三个优化方法的建议。 整体思路和前面提到的各个阶段的优化类似。

工作

想要更深入的了解GCD,可以看看-706-[8]

工作使用分析App启动流程

知道如何优化后,我们需要分析我们的启动流程。 Xcode 11 为此添加了新的 App 模板,使开发人员能够更好地分析其应用程序的启动速度。

外部类调用内部类方法_外部调用符号是什么_ios外部调用类方法

运行后可以看到每个阶段的具体时间,根据数据进行优化,看到耗时的函数调用。

外部调用符号是什么_外部类调用内部类方法_ios外部调用类方法

系统优化

去年,苹果做了很多优化。 下面突出显示的是与启动速度相关的优化。

外部类调用内部类方法_ios外部调用类方法_外部调用符号是什么

但不知道是不是时间原因,这部分的解释很少。 很难理解 200% 的作用。

但 Craig 在 WWDC 2019 现场脱口秀节目中与 Craig 和 Greg [9] 谈到了为什么要优化 200%:

这对我们来说是不是很疯狂。 不,事实证明,随着时间的推移,从应用程序的方式和公平竞争的方式等方面来看。 应用程序路径的一部分。 我的意思是“是”或“向上”以及它是的东西。 然后还有其他基于目标的内容。 所以它就被取消了,所以当我们说好的时。

我们可以想出,这将是在道路上,这将是所有这些预。 然后我们对 -c 和其他一系列工作做了一大堆其他工作,你把它们全部都做了。 是的,我的意思是感冒,我们从来没有在比赛中取得过这样的胜利。

从这段话来看,除了dyld3的贡献之外,减少代码签名的加密也是优化之一。

监控在线用户App启动情况

Xcode 11 在 Xcode 中添加了新的监控面板,您可以在其中多维度查看用户数据,包括平均启动时间。

外部类调用内部类方法_外部调用符号是什么_ios外部调用类方法

当你分析完你的启动流程并做了很多优化之后,你就可以使用Xcode来分析你这次优化的效果了。

当然,你可以通过去年发布的[10]获得一些定制数据。 详情请参阅 - 417 - 生活和[11]

总结

苹果今年提出的很多优化建议其实前几年就已经说过一次了,而且都是老生常谈。 但值得称赞的是新App和Xcode的性能监控。

启动优化是需要经常、反复进行的事情。 越早发现问题,就越容易解决。 如果您想给用户留下良好的第一印象,请迅速采取行动。

标签: 加载 原因 启动

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码