Files
2nd/10_Wiki/Topics/Coding/Android_Compose_Performance.md
T
2026-05-10 22:08:15 +09:00

7.3 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
android-compose-performance Compose Performance — recomposition / stability Coding draft B conceptual 2026-05-09 2026-05-09
android
compose
performance
vibe-coding
language applicable_to
Kotlin
Android
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

@Composable
fun MyComponent() {
    SideEffect {
        Log.d('Compose', 'recomposed')
    }
    // ...
}

→ Frequency 측정.

Stability annotation

@Stable
data class User(
    val id: String,
    val name: String,
)

@Immutable
data class Config(...)

→ Compose 가 skip 가능.

Unstable의 함정

// ❌ 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

// ❌ 매 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+)

// build.gradle.kts
kotlin {
    compilerOptions {
        freeCompilerArgs.add('-P plugin:androidx.compose.compiler.plugins.kotlin:experimentalStrongSkipping=true')
    }
}

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

derivedStateOf

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

→ Computed value memoize.

key (recomposition 정밀)

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

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

LazyColumn / LazyRow

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

// In code
import androidx.compose.runtime.tooling.*

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

→ Systrace 가 visible.

Baseline profile

// 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

./gradlew assembleRelease -PenableComposeCompilerReports=true

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

→ "왜 unstable?" 검증.

List 의 함정

// ❌
data class State(val items: List<String>)
// → List 가 unstable.

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

Function 의 함정

// ❌ Function type 가 unstable 가 default
data class State(val onClick: () -> Unit)

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

→ Strong skipping mode 가 자동 fix.

remember vs derivedStateOf

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

import kotlinx.collections.immutable.*

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

→ Compose 가 stable 인식.

Stop animation 시

val transition = rememberInfiniteTransition()
val rotation by transition.animateFloat(
    initialValue = 0f,
    targetValue = 360f,
    animationSpec = infiniteRepeatable(...)
)

// Visible 안 = 자동 stop.

→ Battery 친화.

Modifier order

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

→ Modifier 가 chain. 매 modifier 가 cost.

Profile

// Layout Inspector → Recomposition Count.
// 큰 number = bottleneck.

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

CompositionLocal 의 함정

val LocalUser = compositionLocalOf<User> { error('not provided') }

// 매 user 변경 = 매 consumer recompose.

→ 자주 변경 = unstable.

Jetpack Compose Compiler Metrics

# 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.

🔗 관련 문서