Files
2nd/10_Wiki/Topics/Coding/Android_Baseline_Profile.md
T
2026-05-09 21:08:02 +09:00

7.7 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-baseline-profile Android Baseline Profile — Startup / Scroll 최적화 Coding draft B conceptual 2026-05-09 2026-05-09
android
performance
baseline-profile
vibe-coding
language applicable_to
Kotlin / Macrobenchmark
Android
Baseline Profile
AOT
Macrobenchmark
startup metric
frame timing

Android Baseline Profile

Startup / scroll 30% 빠르게. Macrobenchmark 가 critical user flow trace → AOT compiled. R8 + Compose 와 결합.

📖 핵심 개념

  • Baseline Profile: 자주 쓰는 코드 path 미리 AOT compile.
  • Macrobenchmark: 측정 + profile 생성.
  • Default profile: Compose / 일부 lib 자동.
  • Startup Profile: cold start 가속.

💻 코드 패턴

Setup

// build.gradle (app)
plugins {
    id 'androidx.baselineprofile'
}

dependencies {
    implementation 'androidx.profileinstaller:profileinstaller:1.3.1'
    baselineProfile project(':baselineprofile')
}

baselineProfile {
    saveInSrc = true        // src/main/baseline-prof.txt
    automaticGenerationDuringBuild = false  // CI 에서만
}

baselineprofile module

// :baselineprofile/build.gradle
plugins {
    id 'com.android.test'
    id 'androidx.baselineprofile'
}

android {
    targetProjectPath = ':app'
}

dependencies {
    implementation 'androidx.benchmark:benchmark-macro-junit4:1.2.0'
    implementation 'androidx.test.ext:junit:1.1.5'
    implementation 'androidx.test.espresso:espresso-core:3.5.1'
    implementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}

Generate profile (Macrobenchmark)

// :baselineprofile/src/main/java/.../BaselineProfileGenerator.kt
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
    @get:Rule val rule = BaselineProfileRule()
    
    @Test
    fun generate() = rule.collect(packageName = "com.acme.app") {
        // 1. App 시작 — cold start
        startActivityAndWait()
        
        // 2. 핵심 user flow
        device.findObject(By.res("login_button")).click()
        device.wait(Until.hasObject(By.res("home_screen")), 5000)
        
        // 3. Scroll
        val list = device.findObject(By.res("feed_list"))
        list.fling(Direction.DOWN)
        list.fling(Direction.DOWN)
        
        // 4. 다른 screen
        device.findObject(By.text("Profile")).click()
        device.wait(Until.hasObject(By.res("profile_screen")), 5000)
    }
}
./gradlew :baselineprofile:generateBaselineProfile
# 결과: app/src/main/baseline-prof.txt

Build 안 적용

- baseline-prof.txt 가 APK 안 포함.
- 첫 install 시 ProfileInstaller 가 dexopt.
- 사용자가 처음 launch 부터 빠름.

Macrobenchmark — 측정

@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
    @get:Rule val rule = MacrobenchmarkRule()
    
    @Test fun startupCold() = rule.measureRepeated(
        packageName = "com.acme.app",
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.COLD,
    ) {
        startActivityAndWait()
    }
    
    @Test fun scrollFeed() = rule.measureRepeated(
        packageName = "com.acme.app",
        metrics = listOf(FrameTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.WARM,
    ) {
        startActivityAndWait()
        val list = device.findObject(By.res("feed_list"))
        list.fling(Direction.DOWN)
        list.fling(Direction.DOWN)
    }
}
./gradlew :baselineprofile:connectedAndroidTest

→ Result: startup time, frame timing, jank %.

기대 효과

Startup: 20-30% 빠름
Scroll: jank ↓
Compose 첫 frame ↓

→ R8 + Baseline Profile + Compose 가 함께 사용.

Compose 최적화 (Baseline 와 별도 + 함께)

// 1. Stable / Immutable
@Stable data class UserState(...)

// 2. derivedStateOf
val isAtTop by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }

// 3. key + contentType
items(items, key = { it.id }, contentType = { it.kind }) { ... }

// 4. animateItem
items(items, key = { it.id }) { it ->
    Row(modifier = Modifier.animateItem()) { ... }
}

Compose Compiler Metrics

android {
    composeOptions {
        kotlinCompilerExtensionVersion = '1.5.10'
    }
}

tasks.withType(KotlinCompile).configureEach {
    kotlinOptions {
        freeCompilerArgs += [
            "-P", "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=$buildDir/compose_metrics",
            "-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=$buildDir/compose_metrics",
        ]
    }
}

→ Reports: 어떤 component 가 unstable / non-skippable.

Startup 측정 in production

// Firebase Performance / 자체
class App : Application() {
    override fun onCreate() {
        val startupTrace = Firebase.performance.newTrace("app_startup")
        startupTrace.start()
        super.onCreate()
        // ...
        startupTrace.stop()
    }
}

App Startup library

class MyInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        // 빠른 init
    }
    override fun dependencies() = emptyList<Class<out Initializer<*>>>()
}
<provider android:name="androidx.startup.InitializationProvider" ...>
    <meta-data android:name=".MyInitializer" android:value="androidx.startup" />
</provider>

→ ContentProvider 보다 빠른 init.

CI 안 generation

# .github/workflows/baseline-profile.yml
- name: Build + generate
  run: ./gradlew :baselineprofile:generateBaselineProfile
- name: Commit if changed
  uses: stefanzweifel/git-auto-commit-action@v5
  with:
    file_pattern: 'app/src/main/baseline-prof.txt'
    commit_message: 'chore: update baseline profile'

Profile vs ART AOT

ART (5.0+): 자동 PGO — 사용 후 점진 빠름.
Baseline Profile: 첫 사용부터 빠름.

→ 둘 다.

Common gotchas

- emulator 결과 ≠ 실기. 실기에서 측정.
- Cold start 만 측정 — process 재시작.
- Macrobenchmark 가 자체 process — system overhead.
- Profile 갱신 — 매 release 권장.
- Compose version 변경 시 다시 generate.

Frame timing

StartupTimingMetric:    Activity launch 시간
FrameTimingMetric:       p50/p95/p99 frame time
TraceSectionMetric:      자체 trace section
PowerMetric:             전력
NetworkUsageMetric:      bytes

Custom trace

// App code
Trace.beginSection("loadHomeFeed")
val items = repo.loadFeed()
Trace.endSection()

// Macrobenchmark
metrics = listOf(TraceSectionMetric("loadHomeFeed"))

Profile size

일반: 5-50 KB.
큰 app: 200 KB 까지.
APK 안 별 영향 X.

🤔 의사결정 기준

상황 적용
새 release Baseline Profile 항상
Compose UI 무거움 매우 효과
작은 utility app 큰 효과 X
Startup 핵심 우선순위
Scroll-heavy 매우 효과
Game Profile 보다 game-specific

안티패턴

  • Profile 한 번만 + 영원 사용: 매 release 갱신.
  • Emulator 만 측정: 실기와 다름.
  • 모든 path profile: 큰 file. critical 만.
  • R8 / minification 없이: 효과 적음.
  • App Startup library 안 씀 + 큰 init: cold start 느림.
  • Macrobenchmark 없는 측정: 추측.
  • Compose Compiler Metrics 무시: 어떤 게 unstable 모름.

🤖 LLM 활용 힌트

  • Macrobenchmark 가 측정 + profile 생성.
  • 매 release CI 갱신.
  • Compose stable / immutable + key + animateItem.
  • App Startup library 로 init 빠르게.

🔗 관련 문서