mcpField
C
compose-performance-audit

compose-performance-audit

github.com/new-silvermoonテスト・品質992570 回インストール13 週間前に更新
リポジトリ

compose-performance-audit について

compose-performance-auditは、Android開発者向けのJetpack Compose性能監査スキルです。コードレビューからプロファイリングデータの分析、修正案まで扱い、コードだけで判断できない場合はLayout InspectorやPerfettoのデータ収集を案内します。

主な機能

  1. 01再コンポーズ、状態、パラメーターの安定性を確認
  2. 02遅延リストのキーとコンポジション中の処理を点検
  3. 03Layout InspectorとPerfettoのデータを分析
  4. 04対象を絞った修正案を提示
  5. 05ベンチマークとフレーム時間を比較

活用シーン

  1. 01Compose UIのカクつきを調査
  2. 02LazyColumnのキーによる再コンポーズを確認
  3. 03コンポジション中の高コスト処理を特定
  4. 04性能トレースで最適化結果を検証

インストール

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

$npx skills add https://github.com/new-silvermoon/awesome-android-agent-skills --skill compose-performance-audit

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

詳細

Compose のパフォーマンス監査

概要

計測とベースラインの設定から、根本原因の分析、具体的な改善手順まで、Jetpack Compose のビューのパフォーマンスをエンドツーエンドで監査します。

ワークフローの判断ツリー

  • ユーザーがコードを提示した場合は、「コード優先レビュー」から始めます。
  • ユーザーが症状のみを説明した場合は、必要最小限のコードやコンテキストを尋ねてから、「コード優先レビュー」を行います。
  • コードレビューで結論が出ない場合は、「プロファイリングの案内」に進み、Layout Inspector の出力または Perfetto のトレースを依頼します。

1. コード優先レビュー

収集する情報:

  • 対象の Composable コード。
  • データフロー:状態、remember、派生状態、ViewModel との接続。
  • 症状と再現手順。

注目する点:

  • 不安定なパラメーターや広範囲な状態変更による再コンポジションの嵐。
  • LazyColumn/LazyRow の不安定なキー(key の頻繁な変化、キーの欠落)。
  • コンポジション中の重い処理(書式設定、ソート、フィルタリング、オブジェクトの割り当て)。
  • 不要な再コンポジション(remember の不足、不安定なクラスやラムダ)。
  • 適切なサイズ指定や非同期読み込みがない大きな画像。
  • レイアウトの過剰な再計算(深いネスト、intrinsic measurement、SubcomposeLayout の誤用)。

提示する内容:

  • コードの該当箇所を示した、考えられる根本原因。
  • 推奨する修正とリファクタリング。
  • 必要に応じて、最小限の再現例または計測の提案。

2. プロファイリングの案内

データの収集方法を説明します:

  • Android Studio の Layout Inspector を使い、再コンポジションの回数を確認します。
  • Compose ツールで Recomposition Highlights を有効にします。
  • フレームタイミングの分析には Perfetto または System Trace を使います。
  • 起動やスクロールの指標について Macrobenchmark の結果を確認します。

依頼するもの:

  • 再コンポジションの回数が表示された Layout Inspector のスクリーンショット。
  • Perfetto のトレースまたは System Trace のエクスポートデータ。
  • デバイス/OS/ビルド構成(デバッグ版かリリース版か)。

重要:R8 を有効にしたリリースビルドでプロファイリングを行ってください。デバッグビルドには大きなオーバーヘッドがあります。

3. 分析と診断

Compose でよく見られる原因を優先して調べます:

  • 不安定なパラメーターや広範囲な状態変更による再コンポジションの嵐。
  • 遅延リストの不安定なキー(key の頻繁な変化、インデックスベースのキー)。
  • コンポジション中の重い処理(書式設定、ソート、オブジェクトの割り当て)。
  • 再コンポジションのたびに再生成される原因となる**remember の不足**。
  • Modifier.size() による制約がない大きな画像。
  • 不適切なコンポジションフェーズでの不要な状態読み取り。

トレース/Layout Inspector の証拠に基づいて、調査結果をまとめます。

4. 改善

対象を絞った修正を適用します:

  • パラメーターを安定化する:データクラスに @Stable または @Immutable アノテーションを使います。
  • キーを安定化する:LazyColumn/LazyRow のアイテムには、安定した一意の ID を使います。
  • 状態の読み取りを遅らせる:derivedStateOf、ラムダベースの Modifier、または Modifier.drawBehind を使います。
  • コストの高い計算を記憶する:remember { } または remember(key) { } でラップします。
  • 再コンポジションをスキップする:安定した Composable を抽出し、key() で識別性を制御します。
  • 画像を非同期で読み込む:適切なサイズ制約とともに Coil/Glide を使います。
  • レイアウトの複雑さを軽減する:階層を平坦化し、深いネストを避けます。

よくあるコードの問題(と修正方法)

不安定なラムダキャプチャ
// BAD: New lambda instance every recomposition
Button(onClick = { viewModel.doSomething(item) }) { ... }

// GOOD: Use remember or method reference
val onClick = remember(item) { { viewModel.doSomething(item) } }
Button(onClick = onClick) { ... }
コンポジション中のコストの高い処理
// BAD: Sorting on every recomposition
@Composable
fun ItemList(items: List<Item>) {
    val sorted = items.sortedBy { it.name } // Runs every recomposition
    LazyColumn { items(sorted) { ... } }
}

// GOOD: Use remember with key
@Composable
fun ItemList(items: List<Item>) {
    val sorted = remember(items) { items.sortedBy { it.name } }
    LazyColumn { items(sorted) { ... } }
}
LazyColumn のキーの欠落
// BAD: Index-based identity (causes recomposition on list changes)
LazyColumn {
    items(items) { item -> ItemRow(item) }
}

// GOOD: Stable key-based identity
LazyColumn {
    items(items, key = { it.id }) { item -> ItemRow(item) }
}
不安定なデータクラス
// BAD: Unstable (contains List, which is not stable)
data class UiState(
    val items: List<Item>,
    val isLoading: Boolean
)

// GOOD: Mark as Immutable if truly immutable
@Immutable
data class UiState(
    val items: ImmutableList<Item>, // kotlinx.collections.immutable
    val isLoading: Boolean
)
状態の早すぎる読み取り
// BAD: State read during composition (recomposes whole tree)
@Composable
fun AnimatedBox(scrollState: ScrollState) {
    val offset = scrollState.value // Recomposes on every scroll
    Box(modifier = Modifier.offset(y = offset.dp)) { ... }
}

// GOOD: Defer state read to layout/draw phase
@Composable
fun AnimatedBox(scrollState: ScrollState) {
    Box(modifier = Modifier.offset {
        IntOffset(0, scrollState.value) // Read in layout phase
    }) { ... }
}
コンポジション中のオブジェクト割り当て
// BAD: Creates new Modifier chain every recomposition
Box(modifier = Modifier.padding(16.dp).background(Color.Red))

// GOOD for dynamic modifiers: Remember the modifier
val modifier = remember { Modifier.padding(16.dp).background(Color.Red) }
Box(modifier = modifier)

安定性チェックリスト

型デフォルトで安定しているか?修正方法
プリミティブ(Int、String、Boolean)はい該当なし
安定したフィールドを持つ data classはい*すべてのフィールドが安定していることを確認する
List、Map、Setいいえkotlinx の ImmutableList を使う
var プロパティを持つクラスいいえ外部から見て安定している場合は @Stable を使う
ラムダいいえremember { } を使う

5. 検証

ユーザーに以下を依頼します。

  • Layout Inspector を再実行し、再コンポジション回数を比較します。
  • Macrobenchmark を実行し、フレームタイミングを比較します。
  • リリースビルドを実際のデバイスでテストします。

情報が提供されている場合は、差分(再コンポジション回数、フレーム落ち、ジャンク)をまとめます。

出力

以下を提示します。

  • 簡潔なメトリクス表(可能な場合は変更前/変更後)。
  • 影響の大きい順に並べた主な問題。
  • 推定作業量を添えた修正案。

参考資料

コメント