---
id: android-compose-performance
title: Compose Performance — recomposition / stability
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [android, compose, performance, vibe-coding]
tech_stack: { language: "Kotlin", applicable_to: ["Android"] }
applied_in: []
aliases: [Compose performance, recomposition, stability, immutable, baseline profile, strong skipping mode]
---

# Compose Performance

> Recomposition 폭발 = jank. **Stable / immutable, derivedStateOf, baseline profile, strong skipping mode**.

## 📖 핵심 개념
- Recomposition = render again.
- Stable = type 가 변경 시 비교 가능 → skip 가능.
- Strong skipping mode (Kotlin 2.0+) = 자동 skip.
- Baseline profile = startup 빠름.

## 💻 코드 패턴

### Recomposition trace
```kotlin
@Composable
fun MyComponent() {
    SideEffect {
        Log.d('Compose', 'recomposed')
    }
    // ...
}
```

→ Frequency 측정.

### Stability annotation
```kotlin
@Stable
data class User(
    val id: String,
    val name: String,
)

@Immutable
data class Config(...)
```

→ Compose 가 skip 가능.

### Unstable의 함정
```kotlin
// ❌ List 가 unstable
@Composable
fun ItemList(items: List<Item>) {
    // 매 recomposition.
}

// ✅ ImmutableList (kotlinx.collections.immutable)
@Composable
fun ItemList(items: ImmutableList<Item>) {
    // Skip 가능.
}
```

→ kotlinx.collections.immutable.

### Lambda stability
```kotlin
// ❌ 매 recomposition 가 새 lambda
@Composable
fun Screen(viewModel: ViewModel) {
    Button(onClick = { viewModel.action() }) { ... }
}

// ✅ Method reference (stable)
@Composable
fun Screen(viewModel: ViewModel) {
    Button(onClick = viewModel::action) { ... }
}

// ✅ remember
@Composable
fun Screen() {
    val onClick = remember { { /* ... */ } }
    Button(onClick = onClick) { ... }
}
```

### Strong Skipping Mode (Kotlin 2.0+)
```kotlin
// build.gradle.kts
kotlin {
    compilerOptions {
        freeCompilerArgs.add('-P plugin:androidx.compose.compiler.plugins.kotlin:experimentalStrongSkipping=true')
    }
}
```

→ 자동 skip (lambda 도). Magic 식.

### derivedStateOf
```kotlin
@Composable
fun Screen(items: List<Item>) {
    val hasMore by remember(items) {
        derivedStateOf { items.size > 100 }
    }
    
    // hasMore 가 변경 시 만 update.
}
```

→ Computed value memoize.

### key (recomposition 정밀)
```kotlin
@Composable
fun ItemList(items: List<Item>) {
    LazyColumn {
        items(items, key = { it.id }) { item ->
            ItemRow(item)
        }
    }
}
```

→ Item 의 identity. Reorder 시 잘못 recompose 안.

### LazyColumn / LazyRow
```kotlin
LazyColumn {
    items(1000) { index ->
        Card { Text('item $index') }
    }
}
```

→ Visible 만 render. RecyclerView 의 modern.

### Layout inspector
```
Android Studio:
- Layout Inspector.
- Compose tree.
- Recomposition count per node.
```

→ "이 node 가 가짜 recompose?".

### Tracing
```kotlin
// In code
import androidx.compose.runtime.tooling.*

Trace.beginSection('MyComponent')
// ...
Trace.endSection()
```

→ Systrace 가 visible.

### Baseline profile
```kotlin
// macrobenchmark module
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
    @get:Rule val rule = BaselineProfileRule()
    
    @Test
    fun generate() = rule.collect(packageName = 'com.example.app') {
        startActivityAndWait()
        // critical user flow
    }
}
```

→ AOT compile of hot code. Cold start 30-50% 빠름.

### Stability inspect
```bash
./gradlew assembleRelease -PenableComposeCompilerReports=true

# Generated:
# build/compose_metrics/-classes.txt
# - Stable / unstable 매 class.
```

→ "왜 unstable?" 검증.

### List 의 함정
```kotlin
// ❌
data class State(val items: List<String>)
// → List 가 unstable.

// ✅
data class State(val items: ImmutableList<String>)
```

### Function 의 함정
```kotlin
// ❌ Function type 가 unstable 가 default
data class State(val onClick: () -> Unit)

// ✅
@Stable data class State(val onClick: () -> Unit)
```

→ Strong skipping mode 가 자동 fix.

### remember vs derivedStateOf
```kotlin
val sorted = remember(items) { items.sortedBy { it.name } }
// → items 가 변경 시 recalculate.

val hasMore by remember { derivedStateOf { items.size > 100 } }
// → items 가 변경 + hasMore 가 변경 시 만 update.
```

→ derivedStateOf 가 fine-grained.

### Immutable collection
```kotlin
import kotlinx.collections.immutable.*

val list: ImmutableList<Item> = persistentListOf(...)
val map: ImmutableMap<String, Int> = persistentMapOf(...)
```

→ Compose 가 stable 인식.

### Stop animation 시
```kotlin
val transition = rememberInfiniteTransition()
val rotation by transition.animateFloat(
    initialValue = 0f,
    targetValue = 360f,
    animationSpec = infiniteRepeatable(...)
)

// Visible 안 = 자동 stop.
```

→ Battery 친화.

### Modifier order
```kotlin
// Order 가 important
Box(modifier = Modifier
    .padding(8.dp)
    .background(Color.Red)
    .size(100.dp)
)
```

→ Modifier 가 chain. 매 modifier 가 cost.

### Profile
```kotlin
// Layout Inspector → Recomposition Count.
// 큰 number = bottleneck.

// Profile mode (release):
./gradlew assembleRelease
adb install ...
adb shell setprop debug.layout true
```

### CompositionLocal 의 함정
```kotlin
val LocalUser = compositionLocalOf<User> { error('not provided') }

// 매 user 변경 = 매 consumer recompose.
```

→ 자주 변경 = unstable.

### Jetpack Compose Compiler Metrics
```bash
# build/compose_metrics/
- *-classes.txt (stability)
- *-composables.txt (skippable, restartable)
```

→ Skippable: input 같음 = skip OK.
Restartable: trigger restart of recomposition.

### Performance tier
```
1. Layout Inspector (initial check).
2. Compose Compiler Reports (stability).
3. Tracing (Trace.beginSection).
4. Baseline profile (startup).
5. Macrobenchmark (real device).
```

### Real-world tips
```
- 큰 list = LazyColumn + key.
- ImmutableList default.
- Strong skipping mode (Kotlin 2.0+).
- Method reference > lambda.
- @Stable / @Immutable annotation.
- Profile 매 release.
```

### vs RecyclerView (옛)
```
RecyclerView: imperative, 빠름.
LazyColumn: declarative, 비슷 빠름.

→ 현재 = LazyColumn.
```

### Common 함정
```
- List 가 unstable.
- Lambda 가 매번 새.
- ViewModel state 가 큰 object.
- 매 recomposition 가 expensive work.
- LazyColumn 가 key 없음.
- Modifier 가 매번 새.
```

## 🤔 의사결정 기준
| 작업 | 추천 |
|---|---|
| Custom data class | @Stable / @Immutable |
| List | ImmutableList |
| Lambda | Method reference / remember |
| Compute | derivedStateOf |
| Big list | LazyColumn + key |
| Cold start | Baseline profile |
| Profile | Layout Inspector + tracing |

## ❌ 안티패턴
- **List 가 unstable**: skip 안 됨.
- **Lambda 가 매번**: recompose 폭발.
- **No key in LazyColumn**: identity 깨짐.
- **CompositionLocal 자주 변경**: 폭발.
- **Big object state**: 매 변경 = 큰 recompose.
- **Profile 안 함**: blind.

## 🤖 LLM 활용 힌트
- Stable / immutable 가 핵심.
- Strong skipping mode (Kotlin 2.0+).
- Baseline profile 가 cold start.
- Layout Inspector 가 visible.

## 🔗 관련 문서
- [[Android_Compose_Recomposition_Pitfalls]]
- [[Android_Compose_State_Hoisting]]
- [[Android_Baseline_Profile]]
