← 所有文章

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 在各個分支間打散的執行期負擔。1
  • Run 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

Watch on Apple Developer ↗

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.」1Top 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 系列

參考資料


  1. Apple, WWDC 2026 議程 268,Profile, fix, and verify: Improve app responsiveness with Instruments。本文關於以下內容的來源:診斷流程(先用 Time Profiler,由主執行緒 CPU 讀數指引調查方向)、release build 與 Swift Concurrency 範本的指引、os_signpostOSSignposter 的 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 屬性解決的 renderThumbnail Main Actor 爭用,還有針對一筆 1.7 GB 同步寫入的 System Trace 加 Inspector 診斷(file descriptor、緩衝區位址、寫入大小、on-core 與 off-core 時間、超過 500 毫秒且其中近 300 毫秒花在 off-core)。 

相關文章

Apple 效能團隊在 WWDC26 實驗室裡說了什麼

Apple 的 Power & Performance 團隊在 WWDC26 現場回答開發者的問題。關於 MetricKit、Power Profiler、熱管理與 AI 資源競用的實戰指引。

3 分鐘閱讀

MetricKit 全面重建:iOS 27 中具狀態感知的遙測

iOS 27 全面重建了 MetricKit:非同步的 metric 與 diagnostic 串流、Codable 報告,以及一套依 app 狀態拆分指標的 StateReporting 框架。

3 分鐘閱讀

從76到100:達成完美的Lighthouse分數

一個個人作品集網站如何從行動裝置Lighthouse效能分數76分、CLS 0.493,進步到全類別完美的100/100/100/100。

3 分鐘閱讀