[ WWDC2018 ] – 深入解析iOS内存 iOS Memory Deep Dive

作者 : 开心源码 本文共6827个字,预计阅读时间需要18分钟 发布时间: 2022-05-11 共52人阅读

Session 416 由三位苹果软件工程师 Kyle Howarth, James Snee, Kris Markel 为我们带来 iOS 内存相关的少量内容

  • 在 Memory Usage Performance Guidelines 不再升级之后,这个 Session 简单详情了一下 iOS 的虚拟内存机制的变化,如 Compressed memory 的用等,分析了开发者应该减少哪部分内存占使用。
  • Xcode 10 现在能捕获内存超限的 EXC_RESOURCE_EXCEPTION 事件,其底层记录内存信息的 memgraph 文件与命令行工具的结合用,使得内存相关的调试更加灵活高效。
  • 推荐开发者通过新的 API 让系统选择最佳的图片渲染格式来正当用内存;相比于UIImage的绘制,图片下采样时用ImageIO来减少损耗。
  • 减少应使用处在后端时较大的内存占使用,主要通过监听 App 生命周期的通知或者利使用VC的生命周期方法实现,使系统或者其余进程取得更多的可使用内存。

1. Why Reduce Memory?

开门见山,我们为什么要减少内存(占使用)?

为了更好的使用户体验

内存是有限且系统共享的资源,一个程序占使用更多,系统和其余程序所可以使用的就更少。程序启动前都需要先加载到内存中,并且在程序运行过程中的数据操作也需要占使用肯定的内存资源。减少内存占使用也可以同时减少其对 CPU 时间维度上的耗费,从而使不仅你所开发的 App,其余 App 以及整个系统也都可以体现的更好。

2. Memory Footprint

我们需要明确的是,这里的减少内存指减少 iOS App 的虚拟内存(Virtual Memory) 占使用。

iOS 以及 macOS 都采使用了虚拟内存技术来突破物理内存(RAM) 的大小限制,每个进程都拥有一段由多个大小相同的 page 所构成的逻辑地址空间。解决器和内存管理单元 MMU(Memory Management Unit) 维护着由逻辑地址空间到物理地址的 page 映射表,当程序访问逻辑内存地址时由 MMU 根据映射表将逻辑地址转换为真实的物理地址。在早期的苹果设施中,每个 page 的大小为 4KB;基于 A7 和 A8 解决器的系统为 64 位程序提供了 16KB 的虚拟内存分页和 4KB 的物理内存分页;而在A9之后,虚拟内存和物理内存的分页大小都达到了 16KB。

虚拟内存分页(Virtual Page, VP) 有两种类型:

1.Clean – Data that can be paged out of memory
指的是可以够被系统清除出内存且在需要时可以重新加载的数据,包括:

  • Memory mapped files
  • Frameworks 中的 __DATA_CONST 部分
  • 应使用的二进制可执行文件

2.Dirty – Any memory that has been written to by your app
指的是不可以被系统回收的内存占使用,包括

  • 所有堆上的对象
  • 图片解码缓冲数据(Decoded image buffers)
  • Frameworks 中的 __DATA 和 __DATA_DIRTY部分

Frameworks you link actually use clean memory and dirty memory

因为闪存容量和读写寿命的限制,iOS 上没有Disk swap机制,取而代之用 Compressed memory。

Disk swap 是指在 macOS 以及少量其余桌面操作系统中,当内存可使用资源紧张时,系统将内存中的内容写入磁盘中的backing store (Swapping out),并且在需要访问时从磁盘中再读入 RAM (Swapping in)。与大多数 UNIX 系统不同的是,macOS 没有预先分配磁盘中的一部分作为 backing store,而是利使用引导分区所有可使用的磁盘空间。

苹果最初只是公开了从 OS X Mavericks 开始用 Compressed memory 技术,但 iOS 系统也从 iOS 7 开始悄悄地用。从 OS X Mavericks Core Technology Overview 文档中能理解到该技术在内存紧张时可以够将最近用过的内存占使用压缩至原有大小的一半以下,并且可以够在需要时解压复使用。它在节省内存的同时提高了系统的响应速度,其特点能归结为:

  • Shrinks memory usage 减少了不活跃内存占使用
  • Improves power efficiency 改善电源效率,通过压缩减少磁盘IO带来的损耗
  • Minimizes CPU usage 压缩/解压十分迅速,可以够尽可可以减少 CPU 的时间开销
  • Is multicore aware 支持多核操作

本质上,Compressed memory 也是 Dirty memory

因而, memory footprint = dirty size + compressed size ,这也就是我们需要并且可以够尝试去减少的内存占使用。

1 (4).png

当 memory footprint 超过肯定值时(这里给出了不同机型的测试结果),就会收到内存警告(Memory Warnings)。对于Extension来说,限制值更小,因而用也需要更加谨慎。??少量情况下,假如内存用增长过快,App 有可可以在尚未响应内存警告的情况下就已经被系统杀掉进程了。

Kyle 在这一部分给出了几点关于内存警告的看法:

(1).你的 App 不肯定是真正的“凶手”

在少量 RAM 容量较低的机型上,App 用过程中接到一个电话,也有可可以触发内存警告。

(2).内存压缩技术的存在使得释放内存变得复杂

假设一个 App 的 Dirty memory 中有一个 NSDictionary 对象占使用了3个 page 的内存空间,当 App 处于非活跃状态时系统将其压缩至1个 page 的压缩大小,系统取得了2个 page 大小的可使用内存。

2.png3.png

但是,假如这时由于少量起因收到内存警告,我们可可以会决定将 NSDictionary 中的少量数据移除,这时我们重新访问了压缩后的page,它被解压 – 释放对象 – 而后内存占使用又回到了1个page大小。也就是说,我们努力释放了少量对象却没有添加可使用内存空间,甚至可可以会加剧内存紧张的态势,也添加了 CPU 的时间开销。

4.png

(3).缓存策略

缓存选择实际上是 CPU 和内存性可以开销的博弈,相比于用字典缓存,Kyle 更推荐用NSCache。NSCache 分配的内存实际上是 Purgeable Memory,能由系统自动释放。这点在 Effective Objective 2.0 一书中也有推荐,NSCache 与 NSPureableData 的结合用既可以让系统根据情况回收内存,也能在内存清除的同时移除相关对象。

3. Tools for profiling footprint

(1).为了更好寻觅可以够减少的内存占使用,Xcode 和 Instruments 一直以来提供了一系列工具帮助我们进行 Debug:

  • Xcode memory gauge
    在 Xcode 的 Debug navigator 中能通过 Xcode memory gauge 直接看到正在 debug 程序的内存占使用情况,以及其余程序占使用内存和系统总内存。为了查看更为详细的内存占使用变化,能用 Instruments 相关工具。

  • Allocations
    追踪程序的虚拟内存占使用和堆信息,提供对象的类名、大小以及调使用栈等信息。

  • Leaks
    使用于检测程序运行过程中的内存泄露,并记录对象的历史信息。

在检测内存泄露方面,三方库 MLeaksFinder 较为流行,可以够不入侵代码且不使用打开 Instruments,自动检测 UIViewController 和 UIView 对象的内存泄露,而且也能扩展以检测其它类型的对象。

  • VM Tracker
    可以够区分程序运行时前文所述的各种内存类型占使用情况,Instruments User Guide 中给出了各个参数的具体定义。

  • Virtual Memory Trace
    隐藏在 System Trace 中的 Virtual Memory Trace 工具可以够从 page 层面更深层次剖析应使用程序的虚拟内存操作。 Syetem Trace in Depth-WWDC 2016 中给出了详细的详情。

5.png

(2).在 Xcode 10 中,内存占使用触发限制时,会有 EXC_RESOURCE_EXCEPTION 事件被捕捉到,继而能利使用各种手段分析研究内存占使用情况,更有助于寻觅问题根源。此外,从 Xcode 8 开始引入的 Debug memory graph 也升级了更好的布局方式。

6.png

(3).Xcode 用 memgraph 的文件格式来储存应使用程序的占使用信息,因而导出 memgraph 文件能结合命令行工具进行分析。可以够尽管可视化工具已经可以够直观的体现我们想要理解的内存占使用信息,但是在终端中不仅能灵活地利使用各种命令和 flag 突出我们想要的内容,更能快速的实现信息查找和文本化交互。这一部分苹果工程师为我们详情了4个常使用命令:

18.png

  • vmmap
vmmap App.memgraphvmmap --summary App.memgraph

7.png

vmmap 可以够打印出进程信息,所有分配给该进程的 VMRegions 以及 VMRegion 的种类、内存占使用信息等内容。利使用 –summary 则可以够根据不同的 region type 打印出详细的内存占使用类型和信息。这里需要注意的是 SWAPPED SIZE 在 iOS 上指的是 Compressed memory size 且其值表示压缩前的占使用大小。

系统中将一系列连续的内存页关联到一个 VMObject 进行管理,VMRegion 即 VMObject 所管理IDE区域。 Finding iOS Memory 中对每种 VMRegion 作出了详细的解释。

此外,结合 grep 和 awk 命令,还能进行制定 VMRegion 的信息查找。例如,以下命令以 page 而非字节为单位打印 App 中所有动态库所占内存大小。

vmmap -pages App.memgraph | grep .dylib | awk '{ sum += $6} END { print "Total Dirty Pages:" sum}'
output:Total Dirty Pages:1387
  • leaks
 leaks App.memgraph leaks --traceTree [address] App.memgraph

8.png

leaks 追踪堆中的对象,打印出进程中内存泄露情况、调使用堆栈以及循环引使用信息。利使用 –traceTree 和指定对象的地址,leaks还可以以树形结构打印出对象的相关引使用。

9.png

  • heap
heap App.memgraphheap App.memgraph -sortBySizeheap App.memgraph -address all | <classes-pattern>

10.png

heap 会打印出所有在堆上的对象信息,默认按类数量排序,也能通过 -sortBySize 按大小排序,对于追踪堆中较大的对象十分有帮助。找到目标对象后,通过 -address 取得所有/指定类的地址,继而能利使用 malloc_history 寻觅其调使用堆栈信息。

11.png

  • malloc_history
malloc_history App.memgraph --fullStacks [address]

用上述命令可以够取得我们知道地址的对象的调使用堆栈信息,它可以够得到的比 memory inspector 中 Backtrace 更加详细。但是需要开启 Dignostics 中的 Malloc Stack 选项,才可以通过 malloc_history 取得 memgraph 记录的调使用堆栈信息。

12.png

  • To see object creation: malloc_history
  • To see what references an object in memory: leaks
  • To see how large a region or an instanceSize: heap & vmmap

上述命令都有着不同的适使用场景,与可视化工具的结合可以够更大的发挥它们的作使用。比方当进入 Debug memory graph 模式时,能直接通过点击下方导航栏的叹号查看内存泄露,可视化的内存泄露更为直观。发现泄露对象后,能再用命令行查看相对复杂的引使用关系和调使用堆栈。或者者当程序运行后,使用 vmmap/heap 查看详细的内存占使用情况,而后进一步查看具体占使用的 region type/class name 并得到对象地址,使用 malloc_history 获取调使用堆栈发现问题。

接下来两部分,苹果工程师针对内存的用给出了少量建议。

4. Images

图片在内存用上很容易产生较大的占使用,如下图所示,一个图片文件从硬盘到展现需要经历加载-解码-渲染三步。以一个590KB大小、2048 * 1536 像素的图片为例,在3x设施上解码后的内存占使用可以够达到10MB(2048 * 3 * 1536 * 3 * 4 Bytes/pixel)之多。更深层次的图像相关实践在 Image and Graphics Best Practices 中详情,这里我们需要知道:

Memory use of an image is related to the dimensions, not the file size in disk

13.png

因而,在解码图片时要注意所选择的图片分辨率大小,对于少量分辨率过大的图片,能先进行下采样降低分辨率再进行解码渲染等。在 iOS 设施上支持四种图片渲染格式,每种格式有着不同的 bitsPerComponent 和适使用场景:

  • SRGB format :每个像素占使用 4 字节,分别表示红、绿、蓝通道以及 alpha 通道
  • Wide format:iOS 硬件设施支持的更生动的色域的渲染格式,每个通道占使用 2 字节,每像素占使用 8 字节。iOS 7 以上的设施能拍摄这类照片,他们能栩栩如生地复原美好。但是由于其较大的内存开销需要谨慎用
  • Luminance and alpha 8 format:每像素占使用 2 字节,分别表示灰度和透明度,适使用于 Metal Apps 中的阴影等
  • Alpha 8 format:每像素只占使用 1 字节,单色,适使用于如阴影、无emoji文字等
    那么我们该如何选择合适的渲染格式呢?

Don’t pick the format, let the format pick you

相比于总是用默认的 SRGB 格式的 UIGraphicsBeginImageContextWithOptions 方法,Kyle 建议我们用在 iOS 10 引入的 UIGraphicsImageRenderer 类完成绘制任务,它在 iOS 12 中会根据场景自动选择最合适的渲染格式,更加正当地用内存。UIGraphicsImageRenderer 能创立 UIImage 对象或者者进行 JPEG/PNG 格式的编码。

此外,关于下采样(downsampling),尽管上述 API 可以够正当用渲染方案,但 UIImage 在修改图片尺寸时的性可以逊于 ImageIO。

  • UIImage 会首先把图片解码加载到内存,内部空间坐标转换也会带来巨大损耗

14.png

  • ImageIO 可以够在不产生 dirty memory 的情况下读取到图片尺寸和元信息,其内存损耗等于缩减后的图片尺寸产生的内存占使用

15.png

5. Optimizing when in background

最后Kyle给出了一点建议就是优化 App 的后端相关行为,即在 App 进入后端时释放内存占使用较大的资源,进入前端时重新加载。这里的实现有两种方式:

(1)App life cycle – 对于少量正在显示的view对象,能监听 UIApplicationDidEnterBackground 和 UIApplicationDidEnterForeground 系统通知

16.png

(2)UIViewController appearance cycle – 利使用 VC 的生命周期方法,更适使用于UITabBarController、UINavigationController 等有多个子vc的场景,由于你可可以会有多个同一层级的 vc,但同一时间内又只有一个页面在展现

17.png

两种方式都能在使用户没有感知的情况下减少后端行为下的内存占使用,让系统可以够取得更多可使用内存。除了苹果工程师为我们提供的建议外,内存占使用也还有更多的优化可可以。在对进行现有问题的追踪优化基础上,开发应使用的过程中,我们更要注意对对象和文件的用方式,避免引入显而易见的内存问题。

参考

  • iOS Memory Deep Dive
  • Memory Usage Performance Guidelines
  • OS X Mavericks Core Technology Overview
  • Understanding iOS Memory
  • Instruments User Guide
  • iOS App Performance: Memory
  • Handling low memory conditions in iOS and Mavericks
  • Finding iOS Memory

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » [ WWDC2018 ] – 深入解析iOS内存 iOS Memory Deep Dive

发表回复