大多数浏览器和
Developer App 均支持流媒体播放。
-
探索适用于 M3 和 A17 Pro 的新 Metal 性能剖析工具
了解 Xcode 15 中的新剖析工具如何帮助你在 Apple 系列 9 GPU 上实现最佳 Metal 性能。探索如何使用 Shader Cost Graph、Performance Heat Map 和 Shader Execution History 工具来分析和优化你的 Metal 代码。掌握如何使用新的 GPU 计数器来优化 GPU 占用率和光线追踪性能。
资源
相关视频
Tech Talks
WWDC23
WWDC22
-
下载
大家好 我叫 Ruiwei 是一名软件工程师 从事 Metal 开发者工具方面的 工作 今天我将与同事 Irfan 一起向大家展示 最新的 Metal 剖析工具
Apple 系列 9 GPU M3 和 A17 Pro 采用全新的着色器核心架构
我们借此机会重新规划剖析工具 并构建出色的新工作流程
要了解有关这一新架构的更多信息 请查看《探索 M3 和 A17 Pro 中的 GPU 改进》
在本次讲座中 我将首先展示 Xcode 15 中令人惊叹的新工具
由于占用率管理对于实现 最佳性能非常重要 Irfan 将向大家展示如何使用一组 新的性能计数器来剖析占用率情况
最后大家将了解如何在这个新架构上 剖析光线追踪
让我们从新的剖析工具开始
我正在致力于使用由现代 GPU 驱动的管道渲染街道
渲染的图像看起来很漂亮 但我注意到一些性能问题
有两种主要方法可以处理 此类应用程序的性能瓶颈
首先是确定开销最高的着色器 并从那里开始了解 哪些是高开销的函数和路线
另一种方法是从查找 高开销的对象或像素开始 如果我们处理着色器 它们的行为可能会有所不同 具体取决于片元位置或线程 ID
得益于 M3 和 A17 Pro 中的 新分析架构 Xcode 15 包含多个新工具 来简化这些任务 今天 我将使用新工具向大家解释 如何发现工作负载的性能问题
首先我来介绍一下 Shader Cost Graph 这是一款新工具 可以帮助你查找 并分类高开销的着色器
这是工作负载的 GPU 截帧 出自我刚刚在 Xcode 15 中的剖析 导航到性能视图后 GPU 时间线会显示工作负载的 执行情况和性能计数器
要查看 Shader Cost Graph 我可以切换到新的 Shaders 标签页
左侧的性能导航器 显示了按开销排序的 通道和管道状态列表
我可以看到 GBuffer Pass 大约占总开销的 50% 这超出了我的预期
为了进行调查 我将首先查看 GBuffer Pass 中使用的 高开销管道状态
在导航器中选择管道状态 将显示 Shader Cost Graph
对于随机管道 将默认选择 片元着色器
Shader Cost Graph 分为两个主要部分 顶部是一个火焰图 可视化最高开销的着色器函数
下方是对应的着色器源代码
这是我们第一次在 Metal 着色器 采用火焰图 它看起来非常棒 通过使用火焰图 我可以轻松识别 片元着色器中开销最高的函数 在图表中选择一个函数会让 源代码编辑器直接跳转到 源代码所在的位置
源代码在左侧边栏中标有性能注释 显示每一行的开销有多大
这是一个全屏着色器 将光照 应用于图像的每个像素
通过左侧边栏中的性能注释 我可以快速识别 开销最高的着色器源代码行
将鼠标悬停在饼图上 会显示性能弹出窗口
弹出窗口提供了该行的具体细分 例如 GPU 执行了 多少条指令
以及不同指令类别的开销
由于这是一个全屏着色器 行为可能会有所不同 具体取决于片元位置 这可能会基于特定区域 或像素导致性能瓶颈 为了充分理解这个问题 我需要找到开销更高的像素 这是使用下一个 新工具 Performance Heat Maps 的好机会
Performance Heat Maps 是一种将像素或计算线程信息 和性能指标可视化的方法 它们使用片元位置或 GPU 线程的 计算线程 ID 构建而成
我们来看一下 GBuffer 通道 不同类型的 Performance Heat Maps
首先是 Shader Execution Cost Heat Map 开销是通过查看执行时间和 GPU 线程的延迟隐藏来计算的
我可以很容易地注意到图像 右半部分的像素显示为红色 这意味着它们开销更高
接下来是 Thread Divergence Heat Map 它以可视化方式显示 SIMD 组中 GPU 线程发散的量
发散随着线程之间控制流差异而增加 这可能发生在条件分支中 或由于几何形状而导致的非活动线程
Overdraw Heat Map 可视化由超过一个 GPU 线程 执行渲染的像素
这可能是由于几何重叠造成的 启用对一个或多个渲染命令进行混色 确保对 GPU 命令进行分组 做到首先渲染不透明物体 然后渲染透明的物体 获得在 Apple GPU 上实现最佳性能
Instruction Count Heat Map 显示 GPU 上对于每个像素或 SIMD 组执行了多少条指令
最后 Draw ID Heat Map 为不同的 GPU 命令进行颜色编码 在这种情况下 我可以看到大部分工作负载 均由单个命令呈现 只有透明窗口是分开的
大家已经了解什么是热图 以及它们的外观 接下来我们来看看如何 在 Xcode 15 中访问它们
要访问 Performance Heat Maps 我可以单击顶部栏上的 Heat Map 标签页
默认情况下会显示 Shader Execution Cost Heat Map 和第一个附件
请注意 对于场景的街道部分 执行开销要高得多 我们来添加更多热图以进一步研究
点击底部栏上的加号按钮 将显示热图弹出框
这让我能够快速启用或禁用 不同类型的热图
Instruction Count Heat Map 证实 GPU 对街道像素执行 更多指令 这可以解释高开销的现象
我可以将指针悬停在像素上 并查看详细信息 例如开销的百分位数 和确切的指令数量
热图已让我可以了解 为什么这些像素开销更高
我还可以进一步 通过使用下一个新工具 Shader Execution History 了解着色器如何渲染这些像素
点击性能热图中的像素 将选择下方的 SIMD 组
这将显示热图下方 SIMD 组的 着色器执行历史记录
Shader Execution History 分为两个主要部分 顶部的时间线 和下面的着色器源代码
时间线从左到右显示 所选 SIMD 组的进度 从上到下 每个执行点都会 显示完整的着色器调用堆栈
这是我第一次能够通过 这种强大的可视化功能 准确了解 SIMD 组是如何 由 Apple GPU 执行的
通过检查时间线 我可以立即识别 占用大部分执行时间的着色器函数 Metal 调试器还会自动检测循环 以帮助你更好地了解进度
在开销最高的着色器函数下 有一个包含 12 次迭代的循环 并占总 SIMD 组 执行时间的 79%
在每次迭代中 都会调用 apply spotlight 函数调用中还有更多循环 对纹理进行采样
这很奇怪 不应该有 12 个聚光灯 照亮街道的像素
检查过工作负载后 我注意到 存在重复聚光灯的错误配置 去掉多余的聚光灯后 GBuffer Pass 的性能要好得多
我们总结一下 Shader Execution History 以可视化方式显示了 GPU 如何执行 SIMD 组
这包括线程的状态 函数调用堆栈和循环
它提供了关于着色器执行的洞见 这在以前是无法实现的
这些是 Xcode 15 中可 用于 M3 和 A17 pro 的新剖析工具 非常期待大家可以充分加以利用
接下来 我的同事 Irfan 将向大家 介绍占用率剖析的情况
谢谢 Ruiwei 介绍了适用于 M3 和 A17 Pro 中的 GPU 的 全新工具和工作流程 大家好 我是 Irfan 接下来将首先介绍 占用率剖析如何 在新的 GPU 架构上运作 以及帮助你剖析 硬件光线追踪工作负载的 新计数器
在展示如何剖析占用率之前 建议大家观看《探索 M3 和 A17 Pro 中的 GPU 改进》 这将帮助大家更好地理解 我将要介绍的内容 我们首先回顾一下 与本部分最相关的一些重要概念
Apple 系列 9 GPU 包括 M3 和 A17 Pro
两款芯片中的 GPU 都有不同的组件 每个着色器核心都有多个执行管道 用于执行不同类型的指令 例如 FP32、FP16 此外还读取和写入 纹理和缓冲区资源
它还具有片上内存 用于存储 着色器程序可能使用的 不同类型的数据 例如用于存储变量值的寄存器 线程组和分块内存 用于存储 一个计算线程组内共享的数据 或一个图块内共享的色彩附件数据
这些片上内存共享 L1 缓存 之下是 GPU 末级缓存和设备内存
现在 我们来谈谈 GPU 性能 和占用率之间的关系
假设你的 Metal 着色器 在使用 ALU 执行管道执行 一些数学运算后 读取缓冲区 其结果将在之后立即使用
访问缓冲区可能需要一直 追溯到设备内存 这是一个延迟较长的操作 在此期间 SIMD 组 无法执行其他操作 这会导致 ALU 管道闲置
为了缓解这种情况 着色器核心可以执行 来自不同 SIMD 组的指令 它可能有一些自己的 ALU 指令
这减少了 ALU 未使用的时间 并允许 SIMD 组 并行运行 从而提高了性能
如果有其他 SIMD 组正在 着色器核心上运行 这可以完成多次 直到 ALU 和其他执行管道 永远不会缺乏要执行的指令
在着色器核心上 同时运行的 SIMD 组 的数量值称为其占用率 为了实现最佳性能 你应该增加 占用率 直到着色器核心上的 ALU 尽可能保持忙碌
接下来 我快速为 Apple 系列 9 GPU 占用率管理提供一些动机
寄存器、线程组、图块堆栈 和其他着色器核心内存类型 从 L1 缓存动态分配 然后由 GPU 末级缓存 和设备内存支持
每个 SIMD 组可以使用 大量不同的 片上着色器程序存储器 随着 SIMD 组数量的增加 工作负载使用的内存 可能会超过片上内存的可用量 这会导致溢出到下一个缓存级别
着色器核心平衡线程占用率 和缓存利用率 以防止内存缓存抖动
这会导致着色器数据保留在芯片上 并且这将使执行管道保持忙碌 从而获得更出色的着色器性能
Xcode 15 拥有一组新的性能计数器 可以帮助你轻松识别和解决 工作负载占用率低的原因 并实现出色的性能表现
接下来我将展示一个工作流程 它可通过增加占用率 来满足工作负载性能目标
你需要了解的第一点是 Metal 工作负载 在 GPU 上运行的情况 以及整个执行过程中的占用率情况 我来展示一下如何使用 Metal 调试器来做到这一点 当选择 Timeline 标签页时 你将在此处看到 GPU 上的 工作负载执行情况
它将显示每个着色器阶段 所有工作负载编码器 的持续时间 你还可以在每个着色器阶段的部分中 查看着色器管道的执行情况
编码器部分下方是一个计数器部分 你可以在其中查看顶级性能限制值 以及利用率和其他有用的 性能计数器 例如占用率
这些计数器会定期收集 同时其工作负载会在 GPU 上执行
我将在本部分中经常提到 性能利用率和限制值
所以让我简要介绍一下其含义
Work 是在硬件块中处理的项目数量 例如 ALU 中的算术指令 MMU 中的地址转换请求等等 Stall 是可用项目被下游块 卡停的次数 例如 内存指令请求被缓存卡停 等待请求从下一级缓存 或设备内存返回 以下是计算硬件块的利用率 和限制值的数学方程 利用率是硬件块在采样周期中 完成的工作占 硬件块峰值处理速率乘以采样周期 的百分比 Limiter 的计算方式类似 它包括采样周期内的工作和停滞两部分
接下来 我将展示 如何对低占用率进行分类
我来检查一下计数器轨迹
其总占用率看起来很低 由于占用率较低 我还将查看一下其他性能限制因素
虽然总占用率较低 你可以看到 ALU 子单元 FP16 的
性能限制约为 100% 这意味着 FP16 在整个间歇期 都很忙碌 在这种情景中 如果你尝试增加占用率 并且新添加的 SIMD 组 主要想要执行 FP16 的操作 你可能看不到任何性能改进
减少着色器中的 FP16 指令 很可能会提高整体着色器性能
这是一个不同的工作负载 你可以看到 其占用率和 所有 ALU 限制值都很低 这意味着占用率不够高 来避免 ALU 饥饿 现在我已可确定 占用率导致 ALU 单元 陷入饥饿 在实际上违背了 保持 ALU 忙碌的优化目标
接下来我将介绍 如何对低占用率的原因进行分类 并将其增加到足以使工作负载 受到 ALU 或内存带宽的限制 而不是受占用率限制
Shader Launch Limited Counter 包括 在着色器核心中 启动线程所完成的工作 以及由于背压而无法启动线程时的 停滞 此计数器的值较低表示 由于工作负载较小 导致没有足够多的线程被启动 高值则表示相反的情况
首先我将通过检查 计数器轨迹中的计数器值来检查 是否有足够的着色器线程 启动到着色器核心中 在这里 你可以看到 Compute Shader Launch 限制值仅为 0.07% 正如我之前提到的 较小的计数器值表示 着色器核心处于饥饿状态 因为该工作负载不够大 无法填满 GPU
现在我们来看看 我剖析的不同工作负载
在这里你可以看到 着色器启动限制值很高 这意味着 要么启动了足够的线程 或者线程启动由于背压而停滞 可能是由于这些线程需要的 内存资源耗尽
让我们了解下一步该做什么 以便继续进行调查
当 Shader Launch Limiter 计数器值为高时 占用率低可能有几个原因 首先 我将检查 在这段时间内是否有任何 正在执行的计算调度 会使用大量线程组内存 如果情况确实如此 则着色器核心将由于线程组 内存不可用而停止启动新线程 从而导致占用率较低
在这里 我剖析了一种 不同且更简单的工作负载 它仅包含一个计算过程 在 GPU 时间轴上 你可以看到 在任何给定时间执行的调度 在 GPU 时间线中 选择计算编码器后 你可以看到 为编码器中的每个调度 设置了多少线程组内存 由于调度的线程组内存使用量 仅为较低的 2 KB 因此我可以排除 线程组内存导致 着色器启动停滞的情况 着色器核心可以使用占用率管理器 设置最大占用率目标 以平衡线程利用率和缓存抖动
对于当前工作负载 我可以使用 Occupancy Manager Target Counter 来检查 GPU 是否限制占用率
这样做是为了将寄存器 线程组、区块和堆栈内存保留在片上 我可以在时间线计数器轨迹中查看 Occupancy Manager Target Counter 如你所见 Occupancy Manager Target Counter 低于 100% 这表明 GPU 使用占用率管理器来 将各种着色器数据内存类型 保留在芯片上 否则这些数据会溢出到 GPU 末级缓存 甚至更慢的设备内存
当 Occupancy Manager Target Counter 较低时 你可以使用该流程图 对低占用率进行分类 我将首先检查 L1 驱逐率计数器 这将衡量有多少寄存器 线程组、区块和堆栈存储器 能够保留在芯片上 而不是溢出到下一级缓存 在 L1 逐出率计数器轨迹中 我可以看到计数器显示高峰值 这表明 L1 缓存由于大量的 着色器核心内存访问 而出现抖动并被驱逐
现在我来向你展示如何找出 此类着色器核心内存中 哪些是导致逐出的原因
要找出哪个 L1 支持的 片上着色器核心内存 导致驱逐 我们需要查看哪种内存类型 最频繁地访问 L1 以及哪块内存分配了最大 百分比的缓存线
如果你在 GPU 时间线中检查 L1 负载和存储带宽计数器轨迹 可以看到各种片上 L1 支持内存的 L1 带宽 从这里可以看出图像块 L1 具有最高的 L1 内存存储带宽
同样 图像块 L1 具有最高的 L1 负载带宽 并且它导致大多数 L1 逐出
L1 驻留计数器轨道将显示 各种片上内存的 L1 缓存分配细分 并且你可以找出哪个着色器核心内存 在 L1 中拥有最大的分配
在这里你可以再次看到 图像块 L1 内存 具有最大的工作集大小 并且很可能是 导致 L1 驱逐率较高的原因
在这种情况下 你可以 使用最小像素格式来降低 L1 逐出率 如果你使用 MSAA 并且工作负载具有 高度复杂的几何形状 那么减少样本数量将有助于降低 L1 逐出率
在降低导致 L1 驱逐的访问频率 和片上内存的分配大小之后 我需要确保 相应更改具有期望的效果
优化内存并重新剖析后 我将确保 如果我的工作负载 不受 ALU 或内存带宽限制 我将首先检查其他限制值 如果情况确实如此 那么工作负载 不受占用率限制 并且我不需要对低占用率进行分类 如果工作负载不受 ALU 或内存带宽限制 我将再次检查占用值 和 Occupancy Manager Target Counter 并不断重复此过程 直到 L1 驱逐率较低
在这里 L1 驱逐率较低 因此在本例中 由于 GPU 末级缓存或 MMU 停滞 占用率管理器目标似乎正在实现 当设备执行内存访问、下方到末级 缓存或生成 TLB 未命中时 可能会发生这种情况 我将展示如何针对不同的工作负载 查看此类停滞 GPU 末级缓存利用率 衡量其服务、读取和写入请求的时间 占峰值末级缓存带宽的百分比 末级缓存限制值包括其利用时间 以及由于缓存抖动或来自主内存的 背压而停滞的时间 如果你发现 GPU 末级缓存限制值 远高于其利用率 则表明 它由于缓存抖动而严重停滞 你可以通过减小缓冲区大小 来减少这些停滞 以改善空间和时间局部性
同样 在 MMU 计数器轨道中 如果你看到 MMU 限制值 远高于 MMU 利用率 那么设备缓冲区访问 会导致 TLB 未命中 造成 MMU 抖动 减少对缓冲区的不连贯内存访问 可以减少这些停滞
优化了设备内存访问并 更新了工作量后 我会再次进行剖析
如果其他限制值较高 我会重点减少这些限制值 因为工作负载不再受占用率限制 如果其他限制很低 我会继续重复分类过程 正如我之前展示的那样 直到工作负载不因占用率低 而受到限制
利用这些新添加的性能计数器 你将能够让 指令执行管道保持忙碌 从而获得出色的着色器性能 现在 让我们转向光线追踪剖析 借助 Apple 系列 9 GPU 中内置的 全新光线追踪硬件加速器 你可以实时渲染 极其逼真的场景 我来展示一下 Xcode 中的 Metal 调试器 如何帮助你优化 光线追踪工作负载的性能
我一直在开发这款渲染卡车的 App 我使用光线追踪 来渲染一些非常漂亮的反射 借助新硬件 渲染速度已经非常让人惊叹 但我很好奇 是否可以让它变得更快
因此 为了帮助你实现 最佳光线追踪性能 除了你熟悉并喜爱的 Acceleration Structure Viewer Xcode 还有一组新的光线追踪计数器 你还可以使用 Ruiwei 之前展示的 Shader Cost Graph 来分析你的着色器 和自定义交集函数 让我们从计数器开始
我已经捕获了渲染器的一帧 并打开了性能时间线 Xcode 现在包含 一个新的光线追踪组 其中包含一组全面的轨迹 可帮助你了解工作负载 在新光线追踪硬件上的运行情况
让我们逐一检查一下
第一条轨迹显示光线占用率 硬件能够同时执行大量光线 并且光线占用率显示 活动的百分比 就像线程占用率一样 Apple 系列 9 GPU 着色器核心 也会自动优化光线占用率 以确保你的 App 以最佳性能运行
假设光线数量不会导致 工作负载不足 我将首先检查占用率管理器目标
遵循与之前相同的流程 但要特别注意 L1 驻留和带宽内的 光线追踪暂存类别
光线追踪单元使用 L1 的 很大一部分作为暂存缓冲区 优化负载大小即可减少该使用部分
重新剖析并重复我在上一部分中 展示的分类过程
下一组轨迹提供了活动射线 正在处理的内容的百分比细分 这可以帮助你更好地了解 可提升的地方 例如 此时 75% 的活动光线 正在执行实例变换 考虑到场景中应该只有 两个卡车实例 这似乎相当高
现在 如果你在自己的 工作负载中发现类似情况 可能值得调查一下场景 以确保实例重叠的最小化 稍后我将使用加速结构查看器 更深入地研究这一点 那么我们现在继续吧
最后 相交测试轨道 显示正在执行的 图元相交的百分比细目
对于我的渲染器 它显示硬件 仅运行不透明三角形测试 没有任何运动 为了实现最佳性能 尝试最大化不透明三角形测试 并且仅在需要它们的几何图形上 (例如需要 Alpha 的对象测试) 使用自定义交集函数
以上就是新的光线追踪计数器介绍 它们非常适合了解 硬件如何执行工作负载 并且是开始对性能 进行分类的好地方
在本例中我发现实例转换 非常高 这表明 场景存在潜在问题
要对潜在实例重叠等场景问题 进行分类 你可以使用 Acceleration Structure Viewer 我们来看一下 首先 我们来寻找一个 我使用加速结构的调度 我将点击时间线中的编码器
然后选择调度
最后 双击 实例、加速结构
这将打开 Acceleration Structure Viewer
它在左侧显示了 加速结构的细目 右侧显示了预览
我还可以突出显示 加速结构的不同方面
现在我想研究转换 让我打开实例遍历的 突出显示模式 热点以蓝色显示 从预览来看 似乎 比我预期的两个实例要多得多 我将把鼠标悬停在 这个深蓝色区域上 来检查光线到底 遍历了多少个实例 八个 这意味着我的光线 需要遍历八个实例 才能找到最近的相交点 这远远多于两个 这解释了 为什么活动光线大部分都在 进行实例转换 但为什么有这么多?
让我切换到实例突出显示模式 它赋予每个实例独特的颜色
啊 看来卡车的不同部分 是不同的实例 它们都在彼此之上 为了在这种情况下获得最佳性能 我应该将这些实例连接在一起 成为单个图元加速结构 但这可能不是代码更改 加速结构问题可能是 资源管道中出现问题的症状
因此 我做了一些调查 并获得了新的卡车资源
解决了该问题
看 实例遍历现在好多了
我们总结一下 你可以使用新的 光线追踪计数器 和 Acceleration Structure Viewer 来解锁 Apple 系列 9 GPU 出色的 光线追踪性能
你还应该继续遵循 光线追踪的最佳实践
通过其他讲座了解更多信息
现在 让我们回顾一下 今天涵盖的所有内容
Xcode 15 新添了出色的 GPU 剖析工具 可用于 Apple 系列 9 GPU 你可以使用 Shader Cost Graph 立即查找 并分类高开销的着色器
借助 Performance Heat Maps 你可以确定 哪个对象或像素 导致着色器开销更高
借助 Shader Execution History 工具 识别占用大部分执行时间的 着色器函数变得轻而易举
你可以使用 Xcode 15 中 新添的性能计数器 对低占用率的原因进行分类 并获得最佳性能
除了 Acceleration Structure Viewer 之外 使用新的光线追踪性能计数器 你可以获得最佳光线追踪性能
谢谢观看!
(无音频)
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。