axiom-concurrency-profiling について
InstrumentsでSwiftのasync/await性能問題を調べるためのガイドです。タスク実行、アクター競合、スレッドプール枯渇、優先度逆転の診断を扱います。
主な機能
- 01Swiftタスクとアクターの動作追跡
- 02メインスレッドのブロック診断
- 03アクター競合の特定
- 04スレッドプール枯渇の調査
- 05タスク優先度逆転の確認
活用シーン
- 01非同期処理中のUIの停止を調査
- 02実行されない待機タスクを特定
- 03予期しないアクターアクセスの直列化を追跡
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-concurrency-profilingインストール後、Claude Code で/axiom-concurrency-profilingを入力して読み込まれたか確認します。
詳細
並行処理のプロファイリング — Instruments のワークフロー
Instruments を使って、Swift の async/await コードをプロファイリングし、最適化します。
使用する場面
✅ 次の場合に使用します:
- 非同期処理中に UI がカクつく
- アクターの競合が疑われる
- タスクがキューに入っているものの、実行されない
- 非同期処理中にメインスレッドがブロックされる
- タスク実行の流れを可視化する必要がある
❌ 次の場合は使用しません:
- 問題が純粋な CPU パフォーマンスに関するもの(Time Profiler を使用してください)
- 並行処理と無関係なメモリの問題(Allocations を使用してください)
- 並行処理がボトルネックだと確認できていない
Swift Concurrency テンプレート
表示される内容
| トラック | 情報 |
|---|---|
| Swift Tasks | タスクのライフタイム、親子関係 |
| Swift Actors | アクターへのアクセス、競合の可視化 |
| Thread States | ブロック中か実行中か一時停止中か |
統計情報
- 実行中のタスク:現在実行中のタスク
- 生存中のタスク:ある時点で存在するタスク
- タスクの総数:作成された累計数
色分け
- 青:タスクを実行中
- 赤:タスクが待機中(競合)
- 灰色:タスクが一時停止中(await 待ち)
ワークフロー 1:メインスレッドのブロックを診断する
症状:UI がフリーズし、メインスレッドのタイムラインが埋まっている
- Swift Concurrency テンプレートでプロファイリングします
- メインスレッドの「Swift Tasks」レーンを確認します
- 長い青色のバー(メインスレッド上で実行中のタスク)を探します
- 処理を別の場所に移せるか確認します
解決パターン:
// ❌ Heavy work on MainActor
@MainActor
class ViewModel: ObservableObject {
func process() {
let result = heavyComputation() // Blocks UI
self.data = result
}
}
// ✅ Offload heavy work
@MainActor
class ViewModel: ObservableObject {
func process() async {
let result = await Task.detached {
heavyComputation()
}.value
self.data = result
}
}
ワークフロー 2:アクターの競合を見つける
症状:タスクが予期せず直列化され、並列処理が順番に実行される
- 「Swift Actors」インストゥルメントを有効にします
- アクセスが直列化されているパターンを探します
- 赤は待機中、青は実行中です
- 赤と青の比率が高い場合は、競合が発生しています
解決パターン:
// ❌ All work serialized through actor
actor DataProcessor {
func process(_ data: Data) -> Result {
heavyProcessing(data) // All callers wait
}
}
// ✅ Mark heavy work as nonisolated
actor DataProcessor {
nonisolated func process(_ data: Data) -> Result {
heavyProcessing(data) // Runs in parallel
}
func storeResult(_ result: Result) {
// Only actor state access serialized
}
}
その他の修正方法:
- アクターを複数に分割します(ドメインを分離します)
- ホットパスには Mutex を使用します(アクター間の切り替えより高速です)
- アクターのスコープを縮小します(隔離するプロパティを減らします)
ワークフロー 3:スレッドプールの枯渇
症状:タスクがキューに入っているものの、実行されず、タスク実行に空白が生じる
原因:ブロッキング呼び出しによって協調型プールが枯渇します
- すべてのスレッドでタスク実行に空白がないか確認します
- ブロッキングプリミティブを確認します
- 非同期の代替手段に置き換えます
よくある原因:
// ❌ Blocks cooperative thread
Task {
semaphore.wait() // NEVER do this
// ...
semaphore.signal()
}
// ❌ Synchronous file I/O in async context
Task {
let data = Data(contentsOf: fileURL) // Blocks
}
// ✅ Use async APIs
Task {
let (data, _) = try await URLSession.shared.data(from: fileURL)
}
デバッグフラグ:
SWIFT_CONCURRENCY_COOPERATIVE_THREAD_BOUNDS=1
非同期コンテキストでの安全でないブロッキングを検出します。
ワークフロー 4:優先度の逆転
症状:優先度の高いタスクが、優先度の低いタスクを待機する
- Instruments でタスクの優先度を調べます
- 待機チェーンをたどります
- 重要な処理経路に適切な優先度が設定されていることを確認します
// ✅ Explicit priority for critical work
Task(priority: .userInitiated) {
await criticalUIUpdate()
}
スレッドプールのモデル
Swift は CPU コア数に応じた協調型スレッドプールを使用します。
| 項目 | GCD | Swift Concurrency |
|---|---|---|
| スレッド数 | 制限なく増加 | コア数に固定 |
| ブロッキング | 新しいスレッドを作成 | 一時停止してスレッドを解放 |
| 依存関係 | 隠れている | ランタイムが追跡 |
| コンテキスト切り替え | カーネルによる完全な切り替え | 軽量な継続 |
ブロッキングが致命的な理由:
- ブロック中の各スレッドはメモリとカーネル構造体を保持します
- スレッド数に上限があるため、ブロックされると処理が進まなくなります
- プールの枯渇によってアプリがデッドロックします
クイックチェック(プロファイリングの前に)
まず、次の項目を確認します。
-
処理は本当に非同期ですか?
- 中断ポイント(
await)を探します - 非同期関数内の同期コードも、処理をブロックします
- 中断ポイント(
-
await 中にロックを保持していますか?
// ❌ Deadlock risk mutex.withLock { await something() // Never! } -
タスクをタイトなループで実行していますか?
// ❌ Overhead may exceed benefit for item in items { Task { process(item) } } // ✅ Structured concurrency await withTaskGroup(of: Void.self) { group in for item in items { group.addTask { process(item) } } } -
非同期コンテキストで DispatchSemaphore を使用していますか?
- 常に安全ではありません。代わりに
withCheckedContinuationを使用してください
- 常に安全ではありません。代わりに
よくある問題のまとめ
| 問題 | Instruments での症状 | 修正方法 |
|---|---|---|
| MainActor の過負荷 | メインスレッド上の長い青色のバー | Task.detached、nonisolated |
| アクターの競合 | 赤と青の比率が高い | アクターを分割し、nonisolated を使用 |
| スレッドの枯渇 | すべてのスレッドに空白が生じる | ブロッキング呼び出しを削除 |
| 優先度の逆転 | 優先度の高いタスクが低いタスクを待機する | タスクの優先度を確認 |
| タスクが多すぎる | タスク作成のオーバーヘッド | タスクグループを使用 |
安全なプリミティブと安全でないプリミティブ
協調型プールで安全:
await、アクター、タスクグループos_unfair_lock、NSLock(短いクリティカルセクション)Mutex(iOS 18以降)
安全でない(フォワードプログレスに違反):
DispatchSemaphore.wait()pthread_cond_wait- 同期的なファイル/ネットワーク I/O
- Task 内での
Thread.sleep()
リソース
WWDC:2022-110350、2021-10254
ドキュメント:/xcode/improving-app-responsiveness
スキル:axiom-swift-concurrency、axiom-performance-profiling、axiom-synchronization、axiom-lldb(スレッド状態の対話的な検査)