Instruments Profiler
Overview
xctrace CLIを使ってiOS/macOSアプリをInstrumentsでプロファイリングし、パフォーマンス問題を特定・修正する。
Core Workflow
1) デバイス選択
-
デバイス一覧を取得
# シミュレータ一覧 xcrun simctl list devices available # 実機 + シミュレータ一覧(xctrace用UDID付き) xctrace list devices -
具体的なデバイスをユーザーに確認(AskUserQuestionを使用)
シミュレータと実機の両方を選択肢に含める:
questions: [{ question: "プロファイリングを実行するデバイスを選択してください(実機推奨)", header: "デバイス", options: [ { label: "<実機名1>", description: "実機 - UDID: xxx(推奨)" }, { label: "<実機名2>", description: "実機 - UDID: yyy(推奨)" }, { label: "iPhone 17 Pro (Simulator)", description: "シミュレータ" } ], multiSelect: false }]ポイント: 実機を先頭に表示し推奨する。実機の方がシミュレータより安定した動作が期待できる。
-
デバイスUDIDの取得
xctrace list devicesの出力からUDIDを取得する:例:
John's iPhone (26.0) (00008101-XXXXXXXXXXXX)→ UDIDは00008101-XXXXXXXXXXXX
2) Releaseビルド
重要: Debugビルドは最適化なしで計測不正確。必ずReleaseビルドを使用。
シミュレータの場合:
xcodebuild -scheme "<スキーム名>" \
-configuration Release \
-destination "id=<シミュレータUDID>" \
-derivedDataPath /tmp/DerivedData \
build
# appパスを取得
APP_PATH=$(find /tmp/DerivedData -name "*.app" -path "*/Release-iphonesimulator/*" | head -1)
# シミュレータにインストール
xcrun simctl install "<シミュレータUDID>" "$APP_PATH"
実機の場合:
xcodebuild -scheme "<スキーム名>" \
-configuration Release \
-destination "id=<実機UDID>" \
-derivedDataPath /tmp/DerivedData \
build
# appパスを取得
APP_PATH=$(find /tmp/DerivedData -name "*.app" -path "*/Release-iphoneos/*" | head -1)
# 実機にインストール
xcrun devicectl device install app --device "<実機UDID>" "$APP_PATH"
bundleIdの取得:
defaults read "$APP_PATH/Info.plist" CFBundleIdentifier
3) プロファイリングモード選択
ユーザーに計測モードを確認(AskUserQuestionを使用):
questions: [{
question: "計測モードを選択してください(Leaks, Allocations, Animation Hitches, Energy Logはその他から入力)",
header: "計測モード",
options: [
{ label: "SwiftUI", description: "Time Profiler + View Body + Hangs + Hitches" },
{ label: "App Launch", description: "起動時間 + フェーズ分析" },
{ label: "Time Profiler", description: "CPU時間プロファイリング(汎用)" }
],
multiSelect: false
}]
注意:
- 選択肢にないモード(Leaks, Allocations, Animation Hitches, Energy Log)はユーザーが「その他」から入力して指定可能
- Energy Logは実機でのみ正確なデータが取得可能。シミュレータでは意味のあるデータが得られない。
4) プロファイリング実行
トレースファイル名の生成(ファイル名衝突を防ぐためタイムスタンプを使用):
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
TRACE_FILE="/tmp/profile_${TIMESTAMP}.trace"
TRACE_SYM_FILE="/tmp/profile_${TIMESTAMP}_sym.trace"
EXPORT_DIR="/tmp/exported_${TIMESTAMP}"
App Launchテンプレートの場合(全自動)
App Launchは起動完了を自動検出して終了するため、確認なしで直接実行する。
# 同期実行(自動終了を待つ)
xctrace record \
--template "App Launch" \
--device "<デバイス>" \
--output $TRACE_FILE \
--launch <bundleId>
- バックグラウンド実行不要
- ユーザーによる停止確認不要
- 開始確認も不要
- 起動完了後、自動的にシンボル化・解析へ進む
SwiftUIテンプレートの場合(ユーザー操作が必要)
起動方法の選択(AskUserQuestionを使用):
questions: [{
question: "アプリの起動方法を選択してください",
header: "起動方法",
options: [
{ label: "起動中のアプリに接続", description: "既に起動中のアプリにアタッチ(--attach)" },
{ label: "アプリを起動して計測", description: "アプリを新規起動してプロファイリング(--launch)" }
],
multiSelect: false
}]
「起動中のアプリに接続」を選択した場合:
# PIDを検索
ps aux | grep <アプリ名>
# または xcrun devicectl で実機のプロセス一覧を取得
実行フロー:
run_in_background: trueでxctraceを実行(--time-limitなし)- AskUserQuestionで停止を待つ
- ユーザーが停止を選択したら、xctraceプロセスをkill
- 通常通りシンボル化・解析を続行
# バックグラウンドで実行(time-limitなし)
Bash(run_in_background: true):
# --attach の場合
xctrace record --template "SwiftUI" --device "<デバイス>" --output $TRACE_FILE --attach <PID>
# --launch の場合
xctrace record --template "SwiftUI" --device "<デバイス>" --output $TRACE_FILE --launch <bundleId>
# → shell_idを控える
# ユーザーに停止タイミングを確認
AskUserQuestion:
question: "アプリを操作してください。計測を終了する場合は停止を押してください"
options: [
{ label: "停止", description: "計測を終了して解析を開始" },
{ label: "停止", description: "計測を終了して解析を開始" }
]
# 「停止」が選択されたら
pkill -INT -f "xctrace record"
# TaskOutputで完了を確認
TaskOutput(task_id: "<shell_id>", block: false)
Time Profilerテンプレートの場合
汎用CPUプロファイリング。SwiftUIテンプレートと同様のフローを使用。
Leaks / Allocationsテンプレートの場合
メモリ関連テンプレートはユーザー操作が必要で、SwiftUIテンプレートと同様のフローを使用。
注意: Leaks/Allocationsは十分なメモリ操作(画面遷移、データ読み込み等)を行ってから停止すること。
Animation Hitchesテンプレートの場合
フレームドロップ検出。スクロールやアニメーション操作が必要。SwiftUIテンプレートと同様のフローを使用。
Energy Logテンプレートの場合
重要: Energy Logは実機のみで正確なデータが取得可能。シミュレータでは意味のあるデータが得られない。
デバイス選択時にシミュレータが選択された場合は警告を表示し、実機を選択するよう促す。
xctrace recordコマンド(内部参照用)
--attach(起動中のアプリに接続):
xctrace record --template "<テンプレート>" --device "<デバイス名 or UDID>" --output $TRACE_FILE --attach <PID>
--launch(アプリを起動して計測):
xctrace record --template "<テンプレート>" --device "<デバイス名 or UDID>" --output $TRACE_FILE --launch <bundleId>
注意:
--launchや--attach引数はコマンドの最後に配置- 実機・シミュレータ共通でbundleIdを使用可能
5) シンボル化(重要)
アプリコードのシンボルを解決するために、dSYMを使ってトレースをシンボル化:
xctrace symbolicate \
--input $TRACE_FILE \
--output $TRACE_SYM_FILE \
--dsym ~/Library/Developer/Xcode/DerivedData/<project>/Build/Products/Release-iphoneos/
シンボル化しないと: アプリのコードがunknownや<deduplicated_symbol>として表示される。
6) データエクスポート
<skill_dir>/scripts/export_trace.sh $TRACE_SYM_FILE $EXPORT_DIR
出力:
$EXPORT_DIR/toc.xml- テーブル一覧$EXPORT_DIR/time-profile.xml- Time Profilerデータ$EXPORT_DIR/report.md- 解析レポート
7) 解析・診断
# 基本的な解析
<skill_dir>/scripts/parse_trace.py $EXPORT_DIR
# アプリ固有コードをフィルタリング
<skill_dir>/scripts/parse_trace.py $EXPORT_DIR --app "MyApp"
# Flame Graph用データのみ出力
<skill_dir>/scripts/parse_trace.py $EXPORT_DIR --collapsed-only
出力内容:
- Summary - サンプル数、合計時間
- Hot Frames - Total Time - 関数の総実行時間(呼び出し先含む)
- Hot Frames - Self Time - 関数自身の実行時間(リーフフレーム)
- SwiftUI / AttributeGraph Frames - SwiftUI関連の処理
- App Code - アプリ固有のコード(--app指定時)
- SwiftUI View Body Updates - View更新統計(SwiftUIテンプレート使用時)
- Potential Hangs - ハング検出(SwiftUIテンプレート使用時)
- Animation Hitches - フレームドロップ(SwiftUIテンプレート使用時)
- Flame Graph Data - collapsed stack形式
解析観点:
| パターン | 意味 | 対処 |
|---|---|---|
| Self Time高 | その関数自体が重い | アルゴリズム改善、キャッシュ |
| Total Time高 / Self Time低 | 呼び出し先に問題 | 呼び出し先を調査 |
| SwiftUI関連が多い | View更新が頻繁 | 状態スコープ縮小 |
重要: 報告のフィルタリング
- パフォーマンスに実質的な影響がない軽微な問題は報告しない
- 例: 数ms以下のホットフレーム、発生頻度が極めて低いヒッチ、ユーザー体験に影響しないレベルのメモリ使用量
- ユーザーが実際にアクションを取るべき問題のみを報告する
8) 改善計画の確認
解析結果を表示した後、ユーザーに改善計画を立てるかどうかを確認する(AskUserQuestionを使用):
questions: [{
question: "解析が完了しました。改善計画を立てますか?",
header: "次のステップ",
options: [
{ label: "改善計画を立てる", description: "問題箇所を特定し、具体的な修正プランを作成" },
{ label: "結果の確認のみ", description: "今回は計測結果の確認だけで終了" }
],
multiSelect: false
}]
「改善計画を立てる」を選択した場合:
- 解析結果から問題の優先度を特定
- 各問題に対する具体的な修正方針を提案
- 影響範囲と修正のリスクを説明
- 必要に応じて
EnterPlanModeで詳細な実装計画を作成
「結果の確認のみ」を選択した場合:
- 解析結果のサマリーを表示して終了
- 後から
/instruments-profilerで再度計測可能であることを伝える
9) Flame Graph生成(オプション)
git clone https://github.com/brendangregg/FlameGraph
./FlameGraph/flamegraph.pl $EXPORT_DIR/collapsed.txt > flamegraph.svg
10) 修正提案
注意: パフォーマンスに実質的な影響がある問題のみ提案する。無視できるレベルの小さな問題には言及しない。
問題特定後、以下のパターンを適用:
| 問題 | 解決策 |
|---|---|
| ループ内のData.append | withUnsafeMutableBytesで直接書き込み |
| Array.removeFirst (O(n)) | リングバッファまたはインデックス管理 |
| View invalidation storms | 状態スコープの縮小 |
| Heavy work in body | 事前計算・キャッシュ |
| Large images | ダウンサンプリング |
Troubleshooting
| エラー | 原因 | 対処 |
|---|---|---|
| Permission denied | 開発者ツールアクセス未許可 | システム設定で許可 |
| Device not found | デバイス名/UDIDが不正 | xctrace list devicesで確認 |
| Template not found | テンプレート名が不正 | xctrace list templatesで確認 |
| Empty trace | 操作なし/すぐ停止した | 十分にアプリを操作してから停止 |
| シンボルがunknown | dSYMがない | xctrace symbolicateでシンボル化 |
| Cannot find process | プロセス検索失敗 | --launchオプションを使用 |
| Leaks/Allocationsが空 | track-based exportが必要 | export_trace.shが最新か確認 |
| Energy Logデータなし | シミュレータ非対応 | 実機で計測(シミュレータでは取得不可) |
| No en |