mcpField
V
visualizing-recomposition-cascades

visualizing-recomposition-cascades

github.com/skydoves開発520122 回インストール19 週間前に更新
リポジトリ

visualizing-recomposition-cascades について

Compose Stability Analyzer IntelliJ プラグインを使って Jetpack Compose の再コンポーズを調べるスキルです。静的な呼び出しグラフと Android デバイス上の実行時カウントを組み合わせ、影響範囲やホットスポットを確認できます。

主な機能

  1. 01静的な再コンポーズ呼び出しツリー
  2. 02カスケードノードの安定性バッジ
  3. 03エディター内のリアルタイム回数表示
  4. 04コンポーザブル別イベント履歴
  5. 05累積カウントのクリア

活用シーン

  1. 01コンポーザブルのリファクタリング前に影響を評価
  2. 02スクロールやアニメーション中のホットスポットを特定
  3. 03静的解析を実行時の挙動で検証

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/skydoves/compose-performance-skills --skill visualizing-recomposition-cascades

インストール後、Claude Code で/visualizing-recomposition-cascadesを入力して読み込まれたか確認します。

詳細

再コンポーズのカスケードを可視化する — IDE 内で静的な影響範囲とライブヒートマップを表示

受動的なガターアイコンは、「このコンポーザブルはスキップ可能か」という問いに答えます(../using-stability-analyzer-ide-plugin/SKILL.mdで説明しています)。アクティブな調査ツールは、さらに難しい2つの問いに答えます。Cascade ビジュアライザーは、ルートの@Composableから静的な呼び出しグラフをたどり、ルートが再コンポーズされた場合に、ほかに何が再コンポーズされる可能性があるかを表示します。Live Heatmap はデバイスの logcat を購読し、計測対象の各コンポーザブルの上に、実際の再コンポーズ回数をブロックインレイとしてエディター内に描画します。これらを組み合わせることで、静的解析と実行時の証拠を結び付けられます。

どちらの機能も同じ IntelliJ プラグイン内にあり、右側に固定された Compose Stability Analyzer ツールウィンドウから利用できます。このウィンドウには、Explorer、Cascade、Heatmapの3つのタブがあります。

このスキルを使用する場合

  • 開発者が「このコンポーザブルを変更すると、何が巻き込まれるか」と尋ねた場合は、Cascade タブを使用します。
  • 開発者が「そのスクロール中に何回再コンポーズされたか」や「このアニメーション中にどのコンポーザブルが高負荷か」と尋ねた場合は、Heatmap タブを使用します。
  • ../../recomposition/debugging-recompositions/SKILL.mdで明らかになった再コンポーズの急増を調査し、呼び出し元のコンテキストを確認する必要がある場合。
  • リファクタリングを準備しており、共有コンポーザブルに手を加える前に影響範囲を把握したい場合。
  • ユーザーが Cascade タブ、Heatmap タブ、再コンポーズのインレイ、Analyze Recomposition Cascade、Toggle Recomposition Heatmap、またはClear Recomposition Dataに言及した場合。

このスキルを使用しない場合

  • ソースコードだけを使った診断。ガターアイコンと、../using-stability-analyzer-ide-plugin/SKILL.mdのUnstableComposableインスペクションで十分です。
  • 安定性のリグレッションに対する CI ゲート。../enforcing-stability-in-ci/SKILL.mdを使用してください。
  • FrameTimingMetric による、リリース品質のスクロール/起動計測。../../measurement/generating-baseline-profiles/SKILL.mdを使用してください。このような計測の前には、ヒートマップをオフにしてください。
  • コンパイラーが型をどのように分類するかについての概念的な質問。../understanding-stability-inference/SKILL.mdを使用してください。

前提条件

  • ../using-stability-analyzer-ide-plugin/SKILL.mdの IDEA プラグインがインストール済みで、右側のツールバーにCompose Stability Analyzerツールウィンドウが表示されていること。
  • Cascadeのワークフローでは、追加の準備は不要です。開いているプロジェクトの PSI に対する静的解析のみを行います。
  • Heatmapのワークフローでは、次の条件を満たしている必要があります。
    • @TraceRecompositionでアノテーションされたコンポーザブルが1つ以上あること(実行時の設定については../../measurement/tracing-recompositions-at-runtime/SKILL.mdを参照してください)。
    • ADB 経由で接続された Android デバイスまたはエミュレーターがあること。
    • アプリがインストールされ、実行中であること。
    • Application.onCreateでComposeStabilityAnalyzer.setEnabled(true)が呼び出されていること(BuildConfig.DEBUGまたは専用フラグで制御してください。../../measurement/tracing-recompositions-at-runtime/SKILL.mdを参照してください)。

ワークフロー A — Cascade ビジュアライザー

A1. @Composable内にキャレットを置く

Analyze Recomposition Cascadeアクション(id com.skydoves.compose.stability.idea.cascade.AnalyzeCascadeAction)は、キャレットが@Composable関数内にある場合にのみ有効になります。このアクションはエディターのポップアップに登録されています。コンポーザブルの本体内で右クリックすると表示されます。

A2. アクションを実行する

右クリック → 再コンポジションカスケードを分析を選択します。右側に固定されたCompose Stability Analyzerツールウィンドウが開き、Cascadeタブに、選択した関数をルートとするツリーが表示されます。

結果をトリアージする際に把握しておく内部動作:

  • 選択したKtCallExpressionから探索を開始し、resolveMainReference()で解決された呼び出し先をたどります。
  • 再帰の最大深度は10階層に制限されているため、非常に幅広いコンポーザブルのカスケードは、無制限に展開されず、深度10で停止します。
  • 循環の検出には完全修飾名の訪問済みセットを使用します。再帰的なコンポーザブル(ツリーのレンダラーなど)は一度だけ表示され、再展開されません。
  • 各ノードには、../using-stability-analyzer-ide-plugin/SKILL.mdのガターアイコンを表示するエンジンと同じエンジンによる安定性バッジが付きます。
A3. たどってトリアージする

深さ優先でたどります。各ノードについて:

  • 安定性バッジを確認します。ルートより下流に赤いノードがある場合、ルートの再コンポジション時に、そのサブツリーはスキップされずに再コンポジションされます。
  • ノードをダブルクリックすると、そのソースに移動します。
  • 分岐数が多い場合は、パラメーターを安定化する(../stabilizing-compose-types/SKILL.md)か、負荷の高いリーフ内で状態の読み取りを遅らせる(../../recomposition/deferring-state-reads/SKILL.md)ことで、枝を整理します。
A4. カスケードは静的な推定として扱い、実行時の保証とはみなさない

カスケードは静的な呼び出しグラフの探索です。下流の各ノードが実行時に実際に再コンポジションされるかどうかは、コンパイラーとランタイムが行う安定性とスキップの判断によって決まります。実際のコストについて結論を出す前に、ライブヒートマップ(ワークフローB)で確認してください。

ワークフローB — ライブヒートマップ

B1. 対象のコンポーザブルに@TraceRecompositionを付与する

ヒートマップには、@TraceRecompositionランタイムが出力するlogcatの行が使われます。計測対象のコンポーザブルがなければ、インレイは表示されません。../using-stability-analyzer-ide-plugin/SKILL.mdにある**@TraceRecompositionを追加**インテンションを使うか、アノテーションを手動で追加してください。ランタイムのセットアップ全般(ゲート、状態トレース、スロットリング)については、../../measurement/tracing-recompositions-at-runtime/SKILL.mdを参照してください。

B2. アプリを実行し、logcatの出力を確認する

接続済みのデバイスでデバッグビルドを実行します。Logcatツールウィンドウを開き、タグRecompositionでフィルターします。計測対象のコンポーザブルが再コンポジションされたときに、D/Recompositionの行が流れてくることを確認してください。プラグインのパーサーが想定する正確な形式:

[Recomposition #3] UserProfile (tag: user-screen) (2.30ms)
  ├─ [param] user: User changed (User@abc123 → User@def456)
  ├─ [param] count: Int stable (42)
  ├─ [state] counter: Int changed (5 → 6)
  └─ Unstable parameters: [user]

ログ行が表示されない場合は、ランタイムの設定に問題があります。続行する前に../../measurement/tracing-recompositions-at-runtime/SKILL.mdに戻ってください。

B3. ヒートマップをオンにする

エディターで右クリック → 再コンポジションヒートマップを切り替えを選択します(アクションID com.skydoves.compose.stability.idea.heatmap.ToggleHeatmapAction)。このアクションはCodeメニューにもあります。

プラグインのAdbLogcatServiceは、購読するために次のADBコマンドを実行します。

adb logcat -s Recomposition:D -T 1

-s Recomposition:Dはタグをデバッグレベルでフィルターし、-T 1は最新の行から取得を開始します。複数のデバイスが接続されている場合は、選択用のポップアップが表示されます。

B4. インレイを確認する

HeatmapInlayManagerは、少なくとも1件のイベントが流れてきた各@Composable宣言の上に、ブロックインレイを配置します。各インレイには次の情報が表示されます。

  • ヒートマップをオンにしてから(または最後に再コンポジションデータをクリアしてから)の再コンポジション回数。
  • 相対的なヒートを示す色分けされた背景。

インレイをクリックすると、ツールウィンドウがHeatmapタブに切り替わり、そのコンポーザブルのイベント履歴にフォーカスします。インレイにカーソルを合わせると、AdbLogcatService.getHeatmapData(name)から提供される最新データを、5秒間のツールチップで確認できます。

B5. 累積カウントをリセットする

Code → 再コンポジションデータをクリアを実行します(アクションID com.skydoves.compose.stability.idea.heatmap.ClearHeatmapDataAction)。アプリを再起動せずに連続して2つの操作(リストのスクロールと検索フィルターなど)を比較するときは、シナリオの間にこの操作を使ってカウンターをリセットしてください。

B6. 測定前にオフにする

ヒートマップアクションをもう一度切り替えて、リスナーを停止します。ヒートマップは再コンポジションのたびに logcat の I/O と PSI 処理を実行します。../../measurement/generating-baseline-profiles/SKILL.md に従って、Macrobenchmark または Baseline Profile の生成を実行する前に必ずオフにしてください。オフにしないと、オーバーヘッドによって計測結果が歪みます。

ツールウィンドウの構成

右側に固定される、ID が Compose Stability Analyzer のツールウィンドウ(アイコン /icons/stability.svg)には、StabilityToolWindowFactory によって登録された 3 つのタブがあります。

タブ実装クラス目的
ExplorerStabilityToolWindowモジュール → パッケージ → ファイル → コンポーザブルの階層ツリーを安定性バッジとともに表示します。ダブルクリックするとソースに移動します。Cascade または Heatmap で詳細を調べる前に、モジュール全体を概観するために使用します。
CascadeCascadePanelAnalyze Recomposition Cascade の実行時に表示されます。選択したルートからの静的コールグラフツリーを表示します。
HeatmapHeatmapPanelライブヒートマップをオンにすると表示されます。コンポーザブルごとのライブイベントログを表示します。

Explorer タブを起点として使用し、対象を Cascade で調べ、Heatmap を有効にして確認します。

パターン

パターン:@TraceRecomposition のないヒートマップではインレイが表示されない
// WRONG
1. Toggle Recomposition Heatmap on.
2. ADB connects, logcat reader starts.
3. Scroll the screen aggressively.
4. No inlays appear above any composable.
5. Developer concludes "the heatmap is broken".
// WRONG because: the heatmap is fed by logcat lines emitted by `@TraceRecomposition`.
// Without at least one instrumented composable there is nothing to render.
// Add `@TraceRecomposition` per `../../measurement/tracing-recompositions-at-runtime/SKILL.md`,
// rebuild, and toggle again.
// RIGHT — instrument first, then toggle the heatmap
@TraceRecomposition(traceStates = true)
@Composable
fun PriceTicker(price: Price) {
    Text(price.formatted)
}
パターン:Macrobenchmark の実行中はヒートマップを実行しない
// WRONG
1. Open Macrobenchmark scroll journey.
2. Toggle Recomposition Heatmap on (forgot from earlier session).
3. Run the benchmark.
4. FrameTimingMetric numbers regress vs. baseline; team panics.
// WRONG because: the heatmap drives logcat I/O and PSI updates on every recomposition.
// That overhead skews FrameTimingMetric and any release-grade measurement.
// Toggle the heatmap off before running `../../measurement/generating-baseline-profiles/SKILL.md`
// or any Macrobenchmark journey.
// RIGHT
1. Toggle Recomposition Heatmap off (verify no inlays in editor).
2. Run the Macrobenchmark journey.
3. Compare FrameTimingMetric vs. the committed baseline.
パターン:カスケードを実行時の保証として信頼しない
// WRONG
Cascade from PriceTicker shows 12 downstream composables.
Developer concludes "all 12 will recompose every tick" and refactors all 12.
// WRONG because: the cascade is a static call-graph walk. Whether each downstream node
// actually recomposes depends on stability and skipping at runtime. Several of the 12
// may already be skipping today; refactoring them is wasted work.
// Confirm with the live heatmap before drawing conclusions.
// RIGHT — the loop: cascade for blast radius, heatmap for confirmation, fix, re-measure
1. Analyze Recomposition Cascade from PriceTicker -> 12 nodes flagged.
2. Annotate the top 3 (by suspected hotness) with `@TraceRecomposition`.
3. Toggle Recomposition Heatmap on, run a representative scenario.
4. Inlays show 2 nodes with high counts; the other 10 stayed cold.
5. Fix the 2 hot nodes via `../stabilizing-compose-types/SKILL.md`.
6. Toggle the heatmap off, re-measure with `../../measurement/generating-baseline-profiles/SKILL.md`.
パターン:構造ではなく安定性に基づいて絞り込む
// WRONG
Cascade shows a wide subtree; developer wraps every leaf in `key { ... }` to force isolation.
// WRONG because: `key` does not make an unstable parameter stable. The leaves still
// receive unstable arguments; the parent still recomposes them. The fix is upstream:
// stabilize the type per `../stabilizing-compose-types/SKILL.md` so the existing
// skipping logic can do its job.
// RIGHT
Identify the unstable parameter via the cascade node's badge.
Stabilize the type at its declaration site.
Re-run the cascade to confirm the badge flipped to stable.

必須ルール

  • ヒートマップを切り替える前に、対象のコンポーザブルに @TraceRecomposition を必ず付与してください。付与しないと、logcat ストリームが空になり、インレイも表示されません。
  • ../../measurement/generating-baseline-profiles/SKILL.md に従って、Macrobenchmark または Baseline Profile の生成を実行する前に、ヒートマップを必ずオフにしてください。logcat のオーバーヘッドによって計測時間が歪みます。
  • 本番環境で ComposeStabilityAnalyzer.setEnabled(true) を決して使用しないでください。../../measurement/tracing-recompositions-at-runtime/SKILL.md に従い、BuildConfig.DEBUG または専用のフラグで制御してください。
  • カスケードを実行時の保証として決して解釈しないでください。これは影響範囲の静的な推定です。リファクタリングする前に、ヒートマップで確認してください。
  • 緑色のカスケードを目標として決して追い求めないでください。スキップ可能性は診断のためのものであり、KPI ではありません。下流のノードによっては、再コンポジションが必要なのが正当な場合もあります。
  • 推奨: 調査は毎回 Explorer タブから始めてモジュールを確認し、対象のルートを Cascade で調べてから、Heatmap を有効にして、カスケードの予測を実行時のデータで確認します。
  • 推奨: 2 つの操作を続けて比較する場合は、アプリを再起動する代わりに、シナリオ間で Clear Recomposition Data を実行します。
  • 推奨: ヒートマップのセッションでは、一度に 1 つの画面に絞ります。インレイはシナリオをまたいで蓄積し、シグナル対ノイズ比は急速に低下します。

検証

  • @Composable 内を右クリックすると Analyze Recomposition Cascade が表示され、コンポーザブルの外を右クリックしても表示されません。
  • Cascade タブに、選択した関数をルートとするツリーが表示され、深さ 10 または循環に達すると展開が停止します。
  • Cascade ノードをダブルクリックすると、そのコンポーザブルのソースに移動します。
  • Toggle Recomposition Heatmap は、接続されているデバイスが複数ある場合はデバイス選択を促し、1 台のみの場合は自動的に開始します。
  • ヒートマップを有効にして @TraceRecomposition を適用すると、Logcat ウィンドウに D/Recomposition の行が表示され、一致するコンポーザブル宣言の上にブロックインレイが表示されます。
  • インレイをクリックすると、ツールウィンドウが Heatmap タブに切り替わり、そのコンポーザブルにフォーカスします。
  • インレイにカーソルを合わせると、最新データを表示する 5 秒間のツールチップが表示されます。
  • Clear Recomposition Data を実行すると、累積カウントとインレイがリセットされます。
  • ヒートマップのアクションをもう一度切り替えると、ADB logcat リーダーが停止します(Logcat フィルターを閉じ、プラグインによる D/Recomposition の読み取りが停止したことを確認してください)。

参考資料

同じプラグインの受動的なエディター内機能(ガターアイコン、ホバー、インラインヒント、インスペクション)については、../using-stability-analyzer-ide-plugin/SKILL.md を参照してください。ヒートマップが使用する D/Recomposition の行を出力する実行時インストルメンテーションについては、../../measurement/tracing-recompositions-at-runtime/SKILL.md を参照してください。CI 側で安定性のリグレッションをゲートする方法については、../enforcing-stability-in-ci/SKILL.md を参照してください。カスケードの分岐を刈り込む上流の型修正については、../stabilizing-compose-types/SKILL.md を参照してください。ヒートマップで検出した高頻度実行されるリーフ内で状態読み取りを遅延させる方法については、../../recomposition/deferring-state-reads/SKILL.md を参照してください。ヒートマップをオフにして必ず実行する必要がある Macrobenchmark のユーザーフローについては、../../measurement/generating-baseline-profiles/SKILL.md を参照してください。

コメント