Instruments 27 在 App 响应性方面的新功能
在 WWDC 2026 上,两位 Apple 工程师分析了一款笔记 App,它出现了三种各自独立的卡顿:保存时画笔无响应、滚动时画面不流畅,以及使用套索工具时的停顿。他们将 Instruments 27 中一项新工具对应到每个症状,借此修复了这三个问题,并通过基准版本与优化版本的比较,逐一证明每项修复确实有效。1这场演讲的核心论点是一套诊断流程:在卡顿发生时观察 CPU,CPU 会告诉你接下来该打开哪个工具。CPU 占用率高,代表你的代码太慢;CPU 空闲,代表你的代码被阻塞了。Instruments 27 同时提供了解决这两种情况的工具,并新增一个视图,用来确认修复是否真的奏效。
本文将逐一介绍支撑这套流程的三项工具:在 CPU 饱和时用 Top Functions 找出沉重的 self-weight;当工作争抢 Main Actor 时,用全新的 Swift executors 工具观察某个 task 究竟在哪个 executor 上执行;以及当线程空闲等待系统时,用全新的 Inspector 面板读取系统调用的参数。Run Comparisons 则衡量每项变更是否真的改善了 trace,将这些工具串连起来。以下内容皆直接取自这场演讲。
TL;DR
- Instruments 27 围绕一条诊断规则重新组织了响应性的工作流程:先打开 Time Profiler,在卡顿期间检查主线程的 CPU,再让这个读数带你找到合适的工具。1
Top Functions是一种新的分析模式,它舍弃调用层级,按 self-weight 将每个分散的节点合并起来,凸显出 flame graph 在各个分支间打散的运行时开销。1Run Comparisons是 Instruments 的“New in Instruments”功能,它精确计算基准 trace 与优化 trace 之间的性能差异,跨运行对应每个函数,将退化标为红色、改善标为绿色。1- 全新的
Swift executors工具会可视化呈现 Main Actor、全局 concurrent executor 以及任何自定义 executor,让你看清某个 task 在哪个 executor 上执行,并抓出 Main Actor 的争用情况。1 - 全新的 Inspector 面板会显示系统调用的确切参数(file descriptor、缓冲区地址、写入大小),并区分 on-core 与 off-core 时间,借此揭示诸如在主线程上写入 1.7 GB 这类同步阻塞行为。1
Instruments 27 所围绕的诊断流程
在介绍任何新工具之前,这场演讲先确立了统领它们的规则。当 App 掉帧或卡顿时,第一步是 Time Profiler,它提供高层次的概览,让你先建立方向感。1接着,一个问题就能指引一切:卡顿期间 CPU 在做什么?
如果 CPU 使用率高,表示线程正忙碌、工作耗时过久,这指向代码层面的性能瓶颈。你有两种修法:重构算法让它跑得更快,或者当沉重的工作无可避免时,把它移交给后台 task,让界面保持反应灵敏。1如果 App 在处理器空闲时卡顿,优化算法并无帮助,因为主线程正卡在等待某项资源释放:文件 I/O、同步锁,或是进程间通信。正如演讲所言:“Because Time Profiler only monitors active CPU cycles, it provides no visibility into these events.”1
工程师分析的是笔记 App 的 release build,因为“a debug build trades off runtime performance for debug ability, so profiling data from debug builds can be misleading.”1他们选用 Swift Concurrency 模板,这个模板仍会提供 Time Profiler 工具,并把三种卡顿全部录制进同一个基准 trace。他们还用 OSSignposter 类型将套索选择包进一个 os_signpost 区间,并把类别设为 points of interest,好让 Instruments 在 points of interest 轨道上呈现这个区间。这个 signpost 之后将成为过滤 trace 的锚点,稍后也成为无噪声运行比较的依据。1
Art 与 Harjas 在演示前先铺陈这套诊断流程,从 1:50 开始。
Instruments 27 的窗口本身就为工作流程定了框架。顶端的时间轴以水平轨道显示 task、actor 与 executor。下方的详细信息区会随所选轨道而变化。右侧则是“a brand new Inspector panel”,会按你的选择项目显示额外的细节与操作。1三项工具填满了这个框架,每一项都对应三种卡顿中的一种。
Top Functions:当 CPU 饱和时
套索卡顿表现为高 CPU。将 trace 过滤到套索的 os_signpost 区间后,hangs 工具确认那里出现了多次卡顿,把进程轨道展开到主线程后,CPU“staying around a 100% during this time period.”1高 CPU 代表代码正在执行,但耗时过久,因此 Time Profiler 是合适的工具。
演讲说明了为何在这里单靠 flame graph 并不够。Time Profiler 用硬件计时器以默认一毫秒的频率对调用栈采样,记录每个核心上当下的栈。每个被采样的函数都会得到一个 weight,而栈最底端的函数会得到 self-weight,也就是直接在它内部执行指令所花的时间。1flame graph 把这棵调用树绘制成空间块,调用者在上、被调用者向下延伸,条块宽度与总 CPU 时间成正比。1问题在于:“for code that is called from a lot of places, like Swift runtime functions and various helper utilities”,flame graph 会把总成本分散到每个调用它们的分支。执行时间被打散成许多小片段,这“makes it difficult to answer which specific functions burned the most overall cycles.”1
Top Functions 填补的正是这个空缺。正如演讲对这个新模式的描述:“This new mode discards the call hierarchy. Instead, it extracts every single scattered node and merges them together to form one block”,并以 self 指标来评估。1扫视套索的 flame graph,看不出单一的元凶,只是“different codepaths sum together to be costly enough to cause hangs.”1切换到按 self-weight 排序的 Top Functions,最顶端的项目是 swift_project_boxed_opaque_existential,这个运行时函数会将 existential 解封,好让代码能对它进行操作。1
修复之道藏在类型系统,而非时间轴里。工程师请 Xcode 的编码助手重写绘图代码,改用具体类型与泛型,而非 existential,因为 existential 的大小可能不一,访问时需要额外工作,对这个使用场景而言代价过高。1这项工具带来的启示是:Top Functions 之所以存在,正是为了抓出那些没有任何单一 flame-graph 分支会明显透露的分散软件开销。
Run Comparisons:证明修复确实有效
过去要确认修复是否有效,意味着要在不同窗口中打开两个 trace,并用肉眼比对并排的 Top Functions 数据。Instruments 27 以 Run Comparisons 取而代之,演讲中将其描述为“New in Instruments.”1它会“computes the exact performance delta by cross-referencing all of samples from the baseline trace and optimized trace”,评估栈中的每个节点。它将基准运行中函数的旧版本对应到优化运行中的新版本,计算差异,再按性能差距排序。红色块标示退化,绿色块标示改善。1
这套工作流程刻意着重于排除噪声。工程师先将两次运行都过滤到套索选择那段完全相同的 os_signpost 区间,接着选取主线程轨道,按下比较按钮,从下拉菜单挑选基准运行。1这会在侧边栏加入一个比较标签页,而且“you can create multiple comparisons, and they are saved to the document to make collaboration easier.”1
这次比较道出了真实的情况。文本形式的调用树显示整体套索执行时间下降了。flame graph 以绿色显示改善的路径、红色显示退化的路径,而那些退化是“new functions added by the coding assistant as it worked to eliminate the usage of existentials.”1在 Top Functions 视图中,退化默认排在最上方;翻转排序后便可看出 swift_project_boxed_opaque_existential“has been removed completely”,而且“overall, the improvements out[weigh] the regressions.”1这个差异正是验证的环节:Run Comparisons 之所以存在,是为了让修复成为可度量的结果,而非一厢情愿的期望。演讲一贯的建议是“leverage os_signpost to make sure your intervals for run comparisons are reliable.”1
Swift executors 工具:当 task 争抢 Main Actor 时
滚动卡顿没有 points of interest 记录可供依靠。演讲转而寻求另一种语境:“what tasks are on the Main Actor during these hangs.”1这正是全新 Swift executors 工具的任务,它会“visualizes the Main Actor, the global concurrent executor, and any custom executors in your process.”1
每次滚动卡顿时,Main Actor 轨道都显示出一个名为 renderThumbnail 的 Swift task。选取该轨道后,它汇总了 Main Actor 上的 task,并揭示“several render thumbnail tasks on the Main Actor taking a few hundred ms to run”,这与滚动不流畅的情况相符。1按照诊断流程,工程师过滤到单一卡顿,并用 Inspector 钉住主线程;Time Profiler 报告 CPU“around a 100%”,排除了等待系统资源的可能。这些 task 纯粹是在 Main Actor 上耗时过久。1
根本原因是一个上下文继承的细节,而这项工具让它无所遁形。Main Actor 负责所有 UI 更新与交互。App 以异步方式绘制缩略图,但因为这段代码是从 SwiftUI 调用而来,“it inherited the Main Actor context”,于是缩略图 task 便与关键的 UI 更新相互争用。1修复之道是把工作导向线程池。工程师在 task 的初始化器加上 @concurrent 属性,将绘制 task 从 Main Actor 移往全局 executor,而 Swift 编译器会检查这项变更不会引入竞态条件。1工具证实了这次迁移:在更新后的 trace 中,“the thumbnail rendering tasks have moved from the Main Actor track to the global executor track”,工作如今得以并行执行。1这项工具的重点在于:Swift executors 工具之所以存在,是为了通过明确呈现哪个 executor 执行了哪个 task,来识别 actor 的拥塞。
Inspector 面板:当线程空闲且被阻塞时
保存卡顿恰恰与前两者相反。过滤到 Write to File 的 os_signpost 区间并放大后,报告的微卡顿显示 CPU“hovering around 20%.”1低 CPU 具有迷惑性:“it doesn’t mean your code is executing slowly, it means the thread has stopped running.”1当界面在低 CPU 下冻结时,主线程正被阻塞、等待某项系统资源,此时优化算法毫无收获,“because there is no code running to optimize.”1
针对这个症状,演讲改用 System Trace 模板,它“built to visualize exactly when and why the operating system pauses your application.”1演讲逐步讲解线程的状态模型:一个执行中的线程一旦遇上不可用的资源,便进入阻塞状态,内核会把它从处理器上驱离,唯有当资源就绪时它才转为可执行,并等待调度器分配一个空闲的核心。这些为了协调请求下一阶段而短暂的唤醒,正是“what cause that twenty percent of CPU utilization.”1
在 System Trace 中,保存操作的活动轨道显示出“a large amount of blank space”,表示线程被阻塞,并以紫色区间标示正在执行的系统调用。1选取其中一个区间时,凸显的范围超出了所点击的那一段,可视化呈现出“one continuous write system call that spans both on and off-core time”:不透明的区段是 on-core 执行,半透明的区段是 off-core 阻塞。1
全新的 Inspector 面板把这一切化为定论。正如演讲所述:“The Inspector gives us the exact arguments passed to this system call. We can see the target file descriptor, the memory address of the buffer, and most importantly, the size.”1大小正是关键铁证:这个 App 正“trying to write over 1.7 gigabytes of data on the main thread.”1Inspector 也显示了代价:这单一操作“took over 500 milliseconds, and almost 300 of those milliseconds were spent off-core waiting for the disk.”1同步的 data.write 调用就是瓶颈所在。把编码与写入包进一个 Swift task,便将其推往 concurrent 线程池,一个验证 trace 确认 write 系统调用如今出现在后台线程上,而非主线程。1Inspector 面板之所以存在,是为了揭示诸如文件 I/O 这类纯 CPU profiler 看不见的同步阻塞行为。
重点摘要
给 iOS 与 macOS 工程师:
- 先打开 Time Profiler,在卡顿期间读取主线程的 CPU;高 CPU 把你带向
Top Functions,空闲 CPU 把你带向 System Trace 与 Inspector。1 - 当 flame graph 看不出单一元凶时,就用
Top Functions;它会按 self-weight 合并分散的运行时开销,让代价最高的函数浮现。1
给交付性能修复的团队:
- 用过滤到同一
os_signpost区间的Run Comparisons验证每项变更,看红绿差异,而非信赖并排的肉眼比对。1 - 比较会保存到文档中并可堆叠多次运行,于是修复的证据便能随 trace 一起传递,供他人审阅。1
给并发与响应性方面的工作:
- 当工作争抢 Main Actor 时,就动用
Swift executors工具;它会显示某个 task 究竟在 Main Actor、全局 concurrent executor,还是自定义 executor 上执行。1 - 一律分析 release build,因为 debug build 会产生误导性的数据。1
FAQ
Instruments 27 中的 Top Functions 模式是什么?
Top Functions 是 Time Profiler 中的一种新分析模式。它舍弃调用层级,把一个函数所有分散的实例合并成一个块,按 self-weight(直接在该函数内部执行指令所花的时间)排名。它回答了一个 flame graph 难以应付的问题:当某些函数的成本被打散到许多调用分支(例如 Swift 运行时函数与辅助工具)时,究竟哪些函数整体烧掉了最多周期。1
Instruments 中的 Run Comparisons 如何运作?
Run Comparisons 在演讲中被描述为 Instruments 的新功能,它精确计算基准 trace 与优化 trace 之间的性能差异。它跨两次运行对应每个函数,为栈中的每个节点计算差异,并按性能差距排序,将退化标为红色、改善标为绿色。为了取得干净的比较,你要把两次运行都过滤到同一个 os_signpost 区间、选取一条轨道,并从下拉菜单挑选基准;比较会保存到文档中。1
Swift executors 工具会显示什么?
Instruments 27 的 Swift executors 工具会可视化呈现你进程中的 Main Actor、全局 concurrent executor 以及任何自定义 executor。它让你看清某个特定的 Swift task 在哪个 executor 上执行,好让你抓出 Main Actor 的争用。在演讲中,它揭示了 renderThumbnail task 因 SwiftUI 所调用的代码继承了 Main Actor 上下文而卡在 Main Actor 上;把它们移往全局 executor 后便清除了卡顿。1
如何找出由文件 I/O 引起的卡顿?
当界面冻结但主线程 CPU 偏低(演讲中约 20%)时,线程是被阻塞、等待某项系统资源,而非执行缓慢的代码。切换到 System Trace 模板并选取系统调用区间;全新的 Inspector 面板会显示该系统调用的确切参数,包括 file descriptor、缓冲区地址与写入大小,以及 on-core 与 off-core 时间。在演示中,它揭示了主线程上一笔 1.7 GB 的同步写入。1
这场演讲所传授的诊断流程,与响应性的 SwiftUI 面向相呼应,请见 iOS 27 的 SwiftUI 性能与互操作,而其衡量思维则见 性能盲点。那项清除 Main Actor 卡顿的并发调整,落在 Swift 6.2 并发的实践应用 所涵盖的更宏观模型之中。整个系列的总览中心是 Apple Ecosystem 系列。
参考资料
-
Apple, WWDC 2026 演讲 268,Profile, fix, and verify: Improve app responsiveness with Instruments。本文关于以下内容的来源:诊断流程(先用 Time Profiler,由主线程 CPU 读数指引调查方向)、release build 与 Swift Concurrency 模板的指引、
os_signpost/OSSignposter的 points of interest 区间、全新的 Inspector 面板、call tree 与 flame graph 的采样模型(默认一毫秒频率、self-weight)、Top Functions分析模式与swift_project_boxed_opaque_existential的发现、Run Comparisons(“New in Instruments”)的红绿差异与保存于文档的比较标签页、Swift executors工具(Main Actor、全局 concurrent executor、自定义 executor)以及以@concurrent属性解决的renderThumbnailMain Actor 争用,还有针对一笔 1.7 GB 同步写入的 System Trace 加 Inspector 诊断(file descriptor、缓冲区地址、写入大小、on-core 与 off-core 时间、超过 500 毫秒且其中近 300 毫秒花在 off-core)。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩