大多数浏览器和
Developer App 均支持流媒体播放。
-
分析并优化您的游戏内存
了解 Apple 平台如何为您的游戏计算及分配内存。我们将介绍如何利用 Instruments 和游戏内存模板来分析您的游戏,借助内存图以监控当前的内存使用情况,并使用 Xcode 内存调试器和命令行工具对其进行分析。我们还将探索 Metal 调试器中的 Metal 资源,提供小提示和技巧来进一步帮助您优化内存使用。
资源
相关视频
WWDC23
WWDC22
WWDC21
-
下载
♪ ♪
Jack 许: 欢迎来到 分析并优化您的游戏内存 我是 Jack 许 来自 Apple 的 GPU Software 团队 还有我的同事 Seth 陆 在过去的几年里 我们团队 一直和与大家一样的 游戏开发者们共同协作 致力于改善游戏内存 今天 我们将为大家分享成果 让您在调试游戏内存 创建精彩的游戏 设计最佳的游戏体验中 可抢占先机
我们从 CPU 和 GPU 对象方面 来分解您游戏内存使用情况 另外 分析您游戏的内存分配 物理内存的实际使用情况 以及对象间的引用 因为内存有许多部分 我们的开发者工具也从 不同的方面来揭开其面纱 在这场旅程中 我们能体验如何使用 Xcode Instruments 和 Terminal 中的命令行工具 今天的旅程将以理解游戏内存 拉开帷幕 并开始分析内存和内存增长 Seth 也会与我们分享 Instruments 的知识 了解 Instruments 的时序方案后 我们继续深入旅行 用 Xcode 和 Terminal 工具 来分析您游戏的内存图 这些工作流主要侧重于 当前内存使用状态 以及分解整体游戏内存 最后 Seth 将与大家分享 您如何使用 Metal Debugger 来优化 Metal 资源 虽然从某种程度上说 它在游戏内存中 相对独立 但仍然是核心领域 现在 我们先从理解游戏内存开始 您从 Xcode 启动游戏时 比如 Metal 的 示例代码 Modern Rendering 可以打开 Xcode 调试导航器中的 这份内存报告 初步展示了您游戏当前和近期 内存使用情况 及其在系统中的影响水平
测量仪中数字展示了 游戏当前内存使用情况 内存调试的第一步非常重要 即理解这些数字的含义
用一行文字来表示 游戏中的实际内存使用 与内存分配并不相同 实际内存使用是在 物理内存上的 而内存分配是游戏在 虚拟存储地址空间 所需要的内存 不同的内存分配是 自然分开计算的
当您的游戏分配内存时 那些新分配的内存并不会 立即或直接占用 物理内存的空间 相反 它们会在虚拟存储地址空间 预留一定空间 虚拟地址空间 是系统为每个进程提供的 当程序后续 实际使用该内存分配时 系统将在物理内存中准备空间
同类的内存分配会经过分类 并稀疏地占领虚拟地址空间 这些分类包括 程序的可执行二进制文件 所有库和框架 栈 为本地和临时变量 及一些函数参数提供存储 动态内存区域 也称为堆 包括类实例存储 程序手动分配的内存 从只读资源 如游戏资源文件中 映射的区域 当然 还有您游戏的 Metal 对象 如缓冲区 纹理和管线状态对象 这些分类是由区域组成的 在底层 存储操作处理 的粒度是内存页面 在现代 Apple 设备中有 16KB 这意味着每个区域会占据 一个或多个页面 最少有 16KB 的大小
随着游戏的持续运行 其内存状态不断改变 新对象被分配 旧项目被销毁 区域持续变化 但只有区域中的被使用的页面 才占用物理内存 系统会努力计入你的游戏 如其它 App 一样
您游戏的内存页面 可能是以下三者之一 dirty compressed 和 clean 我们来看看它们分别是什么 Dirty 内存页面包括 您游戏写入的内存 包括堆和框架分配的内存 只要你的游戏修改过 这些变量或者符号 在搭载 Apple 芯片的设备中 被访问的 Metal 资源 同样也属于该分类 这是因为 CPU 和 GPU 共享快速统一内存池
然而 如果有些 dirty 页面 长时间未使用 系统可能会减少它们在物理内存的占用 通过压缩这些页面的方式 或将其存储在闪存或者磁盘上 也被称为交换 (swap) 这让设备可以运行更多 App 和服务 随后 您的游戏如果再次请求这些页面 系统会解压或从磁盘换入 注意 计入您的游戏的 仍然是未压缩的大小 而在 clean 内存页面方面 它们包括从磁盘中映射的只读文件 如纹理或音频资产 以及加载到进程中的框架 该系统可于任何时间对它们 进行清空或重新加载 因此不会计入您游戏的内存足迹 然而 它们可能常驻于内存中 且过量使用会减缓系统 和游戏的速度 通常前面两个部分最值得关注 这两个部分合在一起称为内存足迹 系统用它来实施内存限制
在一些术语系统中 人们提到 ”dirty 内存” 指的是内存足迹 因为 dirty 是 clean 的反义词 但是不用担心 当变得模糊时 我们会明确所指的是哪一个 现在您知道内存的工作方式 以及系统如何在游戏中衡量它 除了这个 Xcode 内存测量仪 您还可以在系统的许多地方 看到内存足迹 包括 Mac 上的 Activity Monitor App 以及一些 Apple 平台用它来 作为 App 内存限制 您的游戏也可以使用这个指标 来指导内存使用 有一些有用的 API 可查询 当前足迹以及可用内存 我们来快速浏览下 要为 iOS iPadOS 或 tvOS 游戏 取得可用系统内存 调用在 os/proc.h 头文件中声明的 os_proc_available_memory
而任意 Apple 平台的内存足迹 均可通过 proc_pid_rusage 来获得 传入通过 getpid 获得进程 ID RUSAGE_INFO_CURRENT 当前为版本 6 以及数据存储结构 然后获取其物理足迹 或最大物理足迹属性
我们来回顾下 在第一部分中 我们浏览了关于内存的一些概念 游戏的内存分配在 虚拟存储地址空间进行 它们经由游戏访问后 作为 16KB 页面 占据物理内存空间 内存足迹在 Apple 平台中 作为主要和通用测量指标 确定游戏实际内存使用 内存足迹包括 dirty 压缩 和交换页面 在 Apple 芯片上 包括 CPU 和 GPU 对象 它可用于实施内存限制 您的游戏可以调用系统 API 来获取其足迹以及可用内存 现在您知道内存在底层 是如何工作了 我们来看看它在您游戏中 是怎样的 我们有请 Seth 来分享更多内容 Seth 陆: 谢谢 Jack 现在 我们开始捕获一个游戏的 内存增长 我会继续使用 Modern Renderer 示例项目 当您从 Xcode 中运行游戏时 Memory Gauge 能显示 不同时间下的内存足迹 然而 您可以通过在 Instruments 中分析游戏 获得更详细的内存使用情况 因为通常来说 游戏在启用期间 可以分配许多内存 您可在新游戏启动时就开始分析 而不是在当前运行期间来分析 在 Xcode 中 要快速开始 分析游戏 按下运行按钮不放 然后选择 “Profile” 这可以自动跳转到 Instruments Instruments App 包括一组分析工具 可在一条时间线内 从不同方面记录系统 并将记录数据可视化 今年全新推出了 Game Memory 模板 可以帮助您更好地了解 您的 Metal 游戏中的内存增长
该模板附带有 Allocations 和 Metal Resource Events 工具 来记录内存分配的历史 VM Tracker 可记录内存足迹 Virtual Memory Trace 可记录 虚拟内存活动 Metal Application 和 GPU 可记录与 Metal 相关的事件
在这个 demo 中 我将着重强调前面三个工具 Allocations Metal Resource Events 和 VM Tracker 但首先 我们先记录一份 游戏的 trace 您可以按下这里的记录按钮 开始记录 随后 停止记录 您可以按下同样的按钮 或退出游戏 趁着 Instruments 在记录 Modern Renderer 时 我给大家看下另一个 记录 trace 的方法 xctrace 指令让您可以 用编程方式运行记录 在自动化工作流中可能会有用
此外 您可以指定设备名称 选择 iPhone iPad 或 Apple TV 为目标
现在我已经抓取了 Instruments trace 首先看看 Allocations Allocations 为您提供了 内存分配的详细视图 包括它们的大小和对象引用计数 然而 不包括私有 Metal 资源 Statistics 视图展示了 所有堆的内存分配以及匿名 VM
All Heap Allocations 包括 可能包含对象的 malloc 分配的缓冲区 All Anonymous VM 包括 值得关注的可能为 dirty 的 VM 区域 我们稍后会看到这一分类下的 一些 Metal 资源
现在 我们来看看 All Heap Allocations 内部 通常 大内存空间分配 更值得关注优化 要找到单一最大的内存分配 您可以点击 Size 表列 来根据内存分配大小排序
在内存分配中 您可以点击这个箭头 来查看 Swift 和 Objective-C 对象的引用计数变化
在列表中选择这个大内存分配后 在 inspector 面板有 内存分配历史的栈踪迹 点击按钮可以隐藏系统库或框架 这里 根据栈踪迹 Modern Renderer 加载资产时 会进行内存分配
双击框可进入源代码 现在 我们返回看下 “All Anonymous VM” 分类内部
在 Metal 游戏中 您会看到 IOAccelerator 和 IOSurface 分类下 有很多内存分配 Allocations 中的 IOAccelerator 对应 Metal 资源
根据栈踪迹 您可以看到 加载资产时的内存分配
Allocations 中的 IOSurface 对应 drawable 这里 栈踪迹显示了 MetalKit 视图请求了 drawable
默认设置下 Allocations 工具 可将内存分配大小可视化 但是也会有其它的外观形式 您可点击 Allocations 轨中的 箭头按钮 自定显示模式 将 Allocation Density 可视化 这可更新图表 为您展示 不同时间下内存分配的数量 显示内存分配的峰值 这些峰值可能是 内存增长的来源 所以 Allocations 中显示的数据 是很底层的 要更好地理解分配的 Metal 资源 我们接着看 Metal Resource Events Metal Resource Events 工具 是围绕 Metal 资源来设计的 在 Resource Events 视图中 您可以找到 Metal 资源分配 和释放的历史记录 这里您可以通过标签 区分 Metal 资源 您可通过 Metal API 用编程方式进行指定标签 与 Allocations 工具类似 您可以在 inspector 中 为内存分配历史记录找到栈踪迹
该工具也会在 Metal 设备下 添加 Allocation 和 Deallocations 轨 帮助可视化事件密度 到目前为止 Allocations 和 Metal Resource Events 可以帮助理解内存分配 然而 内存分配并不一定 会转变为内存足迹 因此 我们接着到 VM Tracker 下 查看实际内存使用情况
VM Tracker 工具展示了 非压缩 dirty 压缩或交换内存 Dirty Size 代表了 非压缩 dirty 内存 Swapped Size 表示压缩 或交换内存 在这条记录中 Modern Renderer 内 没有压缩或交换内存使用 详细的 Summary 视图 显示了 VM 区域 在 “mapped file” 区域中 您可能会看到一些映射内存资源 比如您的游戏资产 Modern Renderer 将 bistro 资产 映射到内存 这是Instruments 中 Allocations Metal Resource Events 和 VM Tracker 的简要概述 我们快速回顾下您可以 如何分析内存增长 首先 选择 Game Memory 模板 然后 记录和分析 trace 有时 重现或验证 内存增长模式时 这个过程可能需要重复多次 我们希望新的 Game Memory 模板 可帮助您更好地理解 您游戏中的内存分配 或足迹增长 请查看其它视频来了解更多 关于 Instruments 的使用方法 现在 交回给 Jack
Jack: Game Memory 模板看起来 实在太赞了 肯定能大大帮助我们理解 不同时间下 内存使用情况的变化 另外 您可能会想抓取 指定时间内游戏的内存状态 这样您可以深入研究该内存状态 并从不同的角度进行审视 为了实现这一目的 我们有 内存图和一套工具
内存图是有效存储 您游戏内存状态的 完整快照文件 包括对象创建历史 引用 以及所有压缩或交换
您可以在任意时间生成快照 如问题发生时 或生成一对 分别在问题发生前和发生后 用来比较 我们来加点趣味 用菜谱类比法 来分析内存图的内存 包括配料和备料
配料方面 您需要的是您的游戏准备 被称为 Malloc Stack Logging 的配置 以及抓取一个内存图 配置 Malloc Stack Logging 和抓取内存图很便捷
Malloc Stack Logging 在游戏进程中 记录分配信息 您可以在 Scheme 设置中找到 选择 Run action 进入 Diagnostics 勾选 Malloc Stack Logging 复选框
如果您想知道这两个选项是什么 All Allocation and Free History 追踪所有的对象 即使对象已经释放 日志数据可能会耗费更多内存 但调试如分片的问题时会很有用 另一方面 Live Allocation Only 从它的历史记录中 丢弃已释放对象 所以更轻 在这种情况下 我只是研究 存在对象上的引用 所以我选择这个选项 实际上 大部分时间中 Live Allocation Only 都会是 您的推荐选项
或者 如果没有从 Xcode 中启动 您可以设置环境变量 查看 malloc 手册页面 以获得关于记录模式的信息 随后 准备内存图 在调试区域点击 调试内存图按钮 Xcode 会抓取内存快照 对其进行处理 进入内存调试器 Xcode Memory Debugger 为游戏 内存使用情况提供直观视角 我们花一分钟来看下其视图 左边 Debug Navigator 给您 对象实例的分级列表
右边 File Inspector 提供 如内存足迹 运行时间 和抓取日期的有用信息
位于中间区域的是 左边选定对象的内存图视图 以及引用是如何连接至对象的 我稍后会再详细解释这个图
File 菜单给您提供了 保存内存图的选项 从而进行进一步分析 或可以快捷地与您的团队共享
对于 Mac 游戏 您也可以 用进程 ID 或名称 通过 leaks 命令行程序抓取内存图 这意味着您可以 通过 ssh 远程操作 而让光标保留在游戏中 以使您游戏全屏运行并保持焦点 这就是您开始内存图分析所需的
现在来看下在 Terminal 中 用 Xcode Memory Debugger 和一些多功能命令行工具分析内存图 来查看内存分配 足迹甚至更多 第一步最好是按类别 分解内存使用情况 footprint 程序正是作为此用
footprint 使用内存图中的信息 来重建高度概括 通常 您首先会关注大一些的类别 对于如这个来自 Modern Rendering 示例代码的游戏内存图来说 IOAccelerator 通常是最大的 如 Seth 所说 它包括 Metal 资源 堆内存分配会分为 几个 MALLOC_ 开头的类别 系统为了提升性能 将内存分配按大小分到对应内存池 这些对象可能会来自许多地方 如第三方 plugins 或游戏处理音效 或物理模拟的库
这个内存图来自一个非常精彩的 Apple Arcade 游戏 名为 Manifold Garden 是 William “Cheer” Studio 制作的 我很高兴他们允许我向您展示 这款游戏的内存使用情况 如果您的游戏使用了游戏引擎 如同 Manifold Garden 使用了 Unity 或基于内存映射使用自定义分配器 内存像这样显示为 untagged VM_ALLOCATE 这里有一个专业意见 在 Apple 平台中 您的游戏可以使用 最多 16 个 App 专有标签 这样您在深入研究内存使用时 能更清晰 这和修改一行代码一样简单
首先 通过 16 个选项之一 创建标签 然后 当调用 mmap 时 用这个新标签作为文件描述符 来代替这个 -1 您可以查看 mmap 的手册页面 以学习如何定义标签和分类
如果您使用 mach_vm_allocate 在分配时在标记参数中使用 同样的标记
在 footprint 程序世界中 dirty 大小同样包括交换和压缩 所以要将其作为每个类别的总计量
这是当前内存使用的组成 以及它如何组成足迹的简要概念 有些较少使用的内存 会压缩或交换 它们可能是节省内存的来源 下一步是找到游戏使用了多少 压缩或交换内存 然后优化
那您需要对内存图运行 vmmap 它会给您 dirty 和交换的大小 而不是两者的结合 该 dirty 列包括当前未交换 或压缩的常规 dirty 内存 而交换列包括压缩或交换内存的 原始大小 系统将这两列加在一起 来确定足迹 但由于交换尺寸列中的内存 并非经常使用 这可很好地提示优化游戏内存时 需要查看的内容是什么 还有 这是有虚拟尺寸列的 内存分配大小 常驻大小包括 clean 页面 如可执行文件 和内存映射文件等
很方便的是 vmmap 会用一个 不同的表来显示内存分配 在其输出底部 vmmap 按区域对内存分组 这些区域反映了它们在您游戏中的 使用情况或生命周期 因为我打开了 MallocStackLogging 堆上的内存分配在工具区域 不然 它们按内存分配大小 会在两个默认区域 MallocHelperZone 和 DefaultMallocZone 通常 您会跳过小一点的 系统利用区域 如 QuartzCore 区域
而且 如果您想查看 高分片大小指示的分片 或者百分比 如数十或数百兆字节 WWDC 2021 讲座有更多 关于分片的问题
运行没有 --summary 的 vmmap 或在标准模式使用 vmmap 可以在这些分类中逐行显示 每个 vm 区域 就好像我们之前谈到的 虚拟地址空间一样 所以有 vmmap 您可以将经常使用的 dirty 内存中与较少使用的部分区分开 通常游戏中也有很多 不同大小的动态分配 或 malloc 分配的堆内存使用 它们尤其需要关注 heap 工具按类别分组 malloc 分配的资源 根据实例计数来排序 这些类在 C++ 中由 VTable 或通过 Objective-C 或 Swift 来确定的
我们使用的是 --quiet 参数 来跳过关于 一些元数据的头部信息 今年新推出的 heap 在 识别对象类型上更智能 它使用通过 Malloc Stack Logging 记录的信息 来展示调用者或相关库 因此巨大的 non-object 已经是过往 这个还是 Manifold Garden 的内存图 这个例子首次揭示了 如 FMOD Studio 的插件和 如 GameAssembly.dylib 的游戏组件 占用了多少堆使用空间 所以现在您更了解 内存是如何分布的 这也可以通过获得这些对象的 更多信息了解应该往那个方向调查 在这个例子中 开发者 打开了 FMOD Studio 来调整游戏中的音轨和音效 或者到 Unity 中 查看可优化的游戏代码等等
有时 按类总大小排序 而不是类实例计数 会更为有帮助 在 Modern Rendering 示例项目的 内存图中 顶级贡献者是一个使用了 超过 258MB 的类 要继续查看 Modern Rendering 示例中 较大的对象 使用 heap 加上 --sortBySize 按类总大小排序对象 加上 --showSize 列出所有对象 而不是每个类的概览 在 Bytes Storage 中 有一个 NSConcreteMutableData 对象 大小在 255MB 看起来是值得注意的 接下来 我想研究下这是什么 首先 我想知道它的地址 我添加了 --address 输入 NSConcreteMutableData 匹配符 然后是通配符 .* 还有括号里的大小过滤器 只列出 10MB 大小及以上的对象 这是对象地址 我会在后续的步骤中使用 来做进一步的分析 这是对实例做了对象识别改进的 heap 工具 到目前为止 已经看到了三种工具 来理解 游戏中什么对象在使用内存 它们都会提供不同的视图 我展示的只是一个工作流 根据您游戏中特定的内存模式 或使用的技术 您可以按需求进行使用
发现我们不确定 其存在的对象 下一步就是要找到其来源 及其分配调用堆栈
在 Modern Rendering 中的 200MB 对象中 我用了 --callTree 模式 将其地址传到 malloc_history 与额外的 invert 参数一起 我可以集中于最接近 内存分配的函数 然后就好了 这是内存分配的反向跟踪 同样的 Xcode Memory Debugger 也展示了 inspector 中一个对象的 内存分配历史 选择一个对象 点击 Memory Inspector 就好了 另一个例子 传入 VM_ALLOCATE 作为类模式 而不是地址 来检查您游戏或 plugin 中的 匿名 VM 使用 如调试自定义分配器 不管是使用 Xcode 或 malloc_history 您可以知道内存分配反向跟踪 确定是否想要进一步研究 包括首先在这行设置断点
最后 它也可以帮助 研究对象引用 内存图总是记录对象引用 甚至当 MallocStackLogging 由于多种原因未被启用时 我们在前面使用 leaks 来在 Xcode 外抓取内存图 leaks 能做的更多 它检查内存图中所有引用 所以它了解泄露和保留周期 leaks 通过使用跟踪树状参数 和堆中的对象地址 来获取对象的引用树 然而 这个例子中 这是一个很大的树状结构 从某种程度上说 有比在 Terminal 中 更好的方式来查看
通过 Xcode 14 我们重新设计了 内存图视图 展示选定对象的入边和出边
甚至有新的邻居选择弹窗 来选择您希望 Xcode 绘制的边缘 在复杂游戏状态中 尝试理解对象引用时 会极大的提升效率
在探索一圈后 我很确定是纹理管理工具 在访问这个对象 对您的游戏来说 考虑使用 leaks 工具 和内存图视图来找到 重要的对象引用关系 来研究这些对象是如何 在游戏中被访问的 这就是如何使用 leaks 或者 Xcode 展示和找到对象的重要引用 您可查看 leaks 的手册页面 以及 Xcode 的帮助以获取更多 关于这些工具的使用信息
在这个内存图的分析菜谱中 每步都使用了具体的工具 它们共同完成内存图的分析
总的来说 当您希望用 内存图来抓取和分析内存时 首先是 要启用 MallocStackLogging 然后通过 Xcode 为你的游戏抓取内存图 或给您的 Mac 游戏 使用 leaks 工具 接下来 找到尺寸大的惹麻烦的对象 footprint vmmap 和 heap 工具 提供从高层面到细节 的内存分解 使用 malloc_history 您可以查看对象是在哪里分配 leaks 可分析对象使用或引用 之前这些讲座包含了 这些工具使用情况的 深入指导和演示 到目前为止 我们迟迟未开始 Metal 资源的探索 现在是时候了 我们有请 Seth 为我们 介绍详细内容 Seth: 大家好 又见面了 在游戏中 Metal 资源可以使用 非常大的内存 但是也是有办法优化 它们的内存使用的
这里 我总结了一个 节省内存的清单 让您可以优化游戏中 Metal 资源的使用 我们看下 Metal Debugger 是如何帮助您审计资源 并学习更多进一步减少游戏内存的 高级技巧
Metal Debugger 可一站式 调试您的 Metal 游戏 进行 GPU 帧抓取后 您可以看到一个总结页面 为您提供关于抓取工作负荷的 概括数据
在页面的后半部分 有一个分为四个类别的 insights 列表 Memory 分类中的 Insights 为您游戏中的内存节约提出建议 针对这一 trace 没有太多的 内存 insights 访问这些 insights 后 我们只能节省数兆字节的内存
然而 您游戏可能有更多 可节省内存的具体部分 要获得 Metal 资源 所使用内存的完整图像 您可以点击 Show Memory 按钮 使用 Memory Viewer
Memory Viewer 为您提供 抓取自游戏的完整资源列表 上半部分展示了可过滤的不同分类 您可以用其来快速查看资源 比方说纹理 在下半部分中 表格只展示了纹理 我们先关掉过滤器 资源表格有许多列 来帮助您优化游戏 我着重介绍可以帮助您快速识别 一些有趣资源的几列
Insights 列和我们在总结页面 看到的类似 当用该列排序表格时 您可以快速 用 insights 查看所有资源 点击 insight 图标 可以展示弹窗 解释所发现的内容 提供一些可能的行动 旁边列是 Allocated Size 您可以按该列来排序 查看最大的资源是哪个 这对于审计有些资源 是否确实充分利用了它们的内存大小 可能会有用 例如 有些纹理可能可以重新调整为 小一点的分辨率 有些在缓冲区中加载的模型 可能可以使用较低的多边形计数 只要这样做并不会影响 游戏的视觉品质 有一些替代方法可以节省 纹理内存 我稍后会再提到 这还有另一个有趣的列 是 Time Since Last Bound 您可以通过该列来筛选资源 来查看有哪些是近期没有使用的 如果资源没有使用过 最好可以再次检查 看资产是否值得加载 有一段时间没有绑定的资源 且未来不会再使用 您可能要考虑将其释放 或者您可以将其可清除状态 设置为 volatile Metal 资源可能处于 以下三种可清除状态之一 non-volatile volatile 和 empty 在默认设置下 资源是 non-volatile 的 通过将可清除状态 设置为 volatile Metal 可从内存中清理该资源 应对系统内存压力过高的情况 一旦资源为空 系统不再需要将其 计入游戏内存足迹 当您的游戏再次需要该资源时 检查内容是否还在该位置 在需要的时候重新加载 对于频繁使用的资源 只考虑使用 volatile 这样可清除状态不会对您不利
所以这些是针对所有资源的 通用事项 现在 我们来详细看看纹理 Memory Viewer 并不是 默认显示所有列 右击表头可选择显示和隐藏列 如纹理的 Pixel Format 您可以通过优化纹理的像素格式 获得不同的节省量 游戏中的许多纹理 会使用 16 位半像素精度格式 来减少内存使用和带宽 在您需要 单一 alpha 通道的纹理时 您可以避免多个色彩通道 最后 一些只读纹理 可能会得益于块压缩 来降低内存占用 对于块压缩像素格式来说 有如 ASTC 和 BC 的选项 此外 自 A15 仿生开始 您可以对纹理和渲染目标 使用有损压缩 在尽可能保持画质的同时 节省内存 请查看之前的视频 以获取更多信息
以上是您使用 Memory Viewer 可快速发现 一些内存节省的方法 但您还可以使用一些其它技术 来进一步优化您的游戏 如果只有单一通道使用纹理 您可以将其存储模式 设置为 memoryless 从而节省内存和带宽 memoryless 纹理 非常适合临时渲染目标 如深度 stencil 或多样本纹理 另外 如果只有 GPU 使用纹理 您可以将其存储模式设置为私有 而不是共享或托管 提醒下 Apple 芯片 Mac 中 是不需要托管模式的 正如在 iPhone 和 iPad 中一样 以下是示例 游戏用的是 Depth32Float_Stencil8 纹理 深度纹理会跨通道使用 但 Stencil 纹理内容会被丢弃 后续在帧中也不会使用 所以 游戏可以使用两张纹理 将 Stencil 纹理设置为 memoryless 从而节省内存和带宽
最后 我要说下另一个充分利用 您游戏内存的技术 您可能会感兴趣 您可以使用堆中的别名资源 如果您游戏没有 同时使用它们的话 它们可以共享在同一内存分配 支持下的内存 但是在同步这些资源的访问时 要格外小心 您可以查看 “利用 Metal 3 实现无绑定” 的讲座 以了解更多关于使用 堆分配资源的信息 我们可以总结下节省内存的 检查清单 我希望这个清单能帮助您 审计游戏中的 Metal 资源
请查看其它 WWDC 讲座 以了解更多关于 使用 Metal Debugger 来优化您游戏内存的内容 再次有请 Jack
Jack: 谢谢 Seth 在今天的旅程中 我们探索了许多有趣的东西 让大家可以理解和改进 游戏的内存使用 首先 内存足迹是理解 游戏内存使用的首要指标 它包括 dirty 和 compressed 和交换内存 然后 我们体验了强大的 内存调试工具 Seth 给我们展示了 Instruments 如何用有用的 测量跟踪来助力内存分析 全新的 Game Memory 模板 是为这一工作量身定制的 随后 我展示了用内存图 来存储游戏内存状态的快照 有灵活 强大的命令行程序 来分析内存图中的对象 引用和分配历史 heap 工具的改进 和 Xcode Memory Debugger 的重新设计 将会强化游戏内存分析 最后 Seth 与我们共享了 Metal 资源的 内存节省检查清单 以及 Metal Debugger 可以如何 回答您游戏中关于 Metal 资源使用的问题 您可以查看其它 WWDC 讲座 文件和手册页面以了解更多内容
我们持续致力于为大家开发 最灵活的工具 所以何不尽情一试呢 它们可能正好就是您需要的
有任何反馈请随时 通过多种渠道与我们分享 如 Feedback Assistant 祝大家有一个愉快的内存 探索旅程 感谢大家的观看
-
-
6:53 - Available memory for the process
#import <os/proc.h> API_UNAVAILABLE(macos) API_AVAILABLE(ios(13.0), tvos(13.0), watchos(6.0)) size_t os_proc_available_memory(void);
-
7:07 - Current and peak footprint
#if __has_include(<libproc.h>) #include <libproc.h> // On macOS. #else #include <sys/resource.h> // On iOS, iPadOS and tvOS. int proc_pid_rusage(int pid, int flavor, rusage_info_t *buffer) __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); #endif rusage_info_current rusage_payload; int ret = proc_pid_rusage(getpid(), RUSAGE_INFO_CURRENT, // I.e., new RUSAGE_INFO_V6 this year. (rusage_info_t *)&rusage_payload); NSCAssert(ret == 0, @"Could not get rusage: %i.", errno); // Look up in `man errno`. uint64_t footprint = rusage_payload.ri_phys_footprint; uint64_t footprint_peak = rusage_payload.ri_lifetime_max_phys_footprint;
-
10:04 - Record an Instruments trace
xctrace record --template "Game Memory" \ --attach ModernRenderer \ --output ModernRenderer.trace \ --time-limit 30s
-
10:14 - Record an Instruments trace, on a selected device
xctrace record --device-name "Seth's iPhone" \ --template "Game Memory" \ --attach ModernRenderer \ --output ModernRenderer.trace \ --time-limit 30s
-
16:52 - MallocStackLogging
# See `man malloc`. MallocStackLogging=lite # Live allocations only. MallocStackLogging=1 # All allocation and free history.
-
18:07 - Capture a memory graph
leaks $PID --outputGraph foo.memgraph # or leaks GameName --outputGraph foo.memgraph
-
20:12 - Tag mapped anonymous memory
size_t length; int tag = VM_MAKE_TAG(VM_MEMORY_APPLICATION_SPECIFIC_1); // Check out `man mmap`. void * reservation = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, tag, // Instead of using default `-1`. 0); if (reservation == MAP_FAILED) { @throw [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; } return reservation;
-
20:30 - Tag anonymous memory
size_t page_count; mach_vm_size_t allocation_size = page_count * PAGE_SIZE; mach_vm_address_t vm_address; kern_return_t kr; kr = mach_vm_allocate(mach_task_self(), &vm_address, allocation_size, VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_APPLICATION_SPECIFIC_1)); if (kr != KERN_SUCCESS) { // Refer to mach/kern_return.h. @throw [[NSError alloc] initWithDomain:NSMachErrorDomain code:kr userInfo:nil]; } return vm_address;
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。