기준 프로필을 사용하여 앱 성능 개선

1. 시작하기 전에

이 Codelab에서는 기준 프로필을 생성하여 애플리케이션 성능을 최적화하는 방법과 기준 프로필 사용의 성능 이점을 확인하는 방법을 알아봅니다.

필요한 항목

실행할 작업

  • 기준 프로필 생성기를 사용하도록 프로젝트 설정
  • 기준 프로필을 생성하여 앱 시작 및 스크롤 성능 최적화
  • Jetpack Macrobenchmark 라이브러리를 사용하여 성능 향상 확인

학습할 내용

  • 기준 프로필 및 기준 프로필로 앱의 성능을 개선할 수 있는 방법
  • 기준 프로필 생성 방법
  • 기준 프로필의 성능 향상

2. 설정

시작하려면 다음 명령어를 사용하여 명령줄에서 GitHub 저장소를 클론합니다.

$ git clone https://github.com/android/codelab-android-performance.git

또는 ZIP 파일 두 개를 다운로드해도 됩니다.

Android 스튜디오에서 프로젝트 열기

  1. 'Welcome to Android Studio' 창에서 61d0a4432ef6d396.png Open an Existing Project를 선택합니다.
  2. [Download Location]/codelab-android-performance/baseline-profiles 폴더를 선택합니다. baseline-profiles 디렉터리를 선택해야 합니다.
  3. Android 스튜디오에서 프로젝트를 가져오면 app 모듈을 실행하여 나중에 작업할 샘플 애플리케이션을 빌드할 수 있는지 확인합니다.

샘플 앱

이 Codelab에서는 JetSnack 샘플 애플리케이션을 사용합니다. Jetpack Compose를 사용하는 가상 스낵 주문 앱입니다.

애플리케이션의 성능을 측정하려면 벤치마크에서 UI 요소에 액세스할 수 있도록 UI의 구조와 앱의 동작 방식을 이해해야 합니다. 앱을 실행하고 스낵을 주문하면서 기본 화면에 익숙해지세요. 앱이 설계된 방식을 자세하게 알 필요는 없습니다.

23633b02ac7ce1bc.png

3. 기준 프로필이란 무엇인가요?

기준 프로필을 사용하면 포함된 코드 경로의 해석과 JIT(just-in-time) 컴파일 단계를 피하여 최초 실행 후 코드 실행 속도가 약 30% 향상됩니다. 앱 또는 라이브러리에 기준 프로필을 제공하면 Android 런타임(ART)이 AOT(Ahead-Of-Time) 컴파일을 통해 포함된 코드 경로를 최적화하여 모든 신규 사용자와 앱 업데이트에 향상된 성능을 제공합니다. 이 프로필 기반 최적화(PGO)는 앱이 시작을 최적화하고, 상호작용으로 인한 버벅거림을 줄이고, 첫 실행부터 최종 사용자가 경험하는 전반적인 런타임 성능을 개선할 수 있도록 해 줍니다.

기준 프로필을 사용하면 앱 시작, 화면 간 이동, 콘텐츠 스크롤과 같은 모든 사용자 상호작용이 처음 실행될 때부터 더 원활해집니다. 앱의 속도와 반응성을 높이면 일일 활성 사용자를 늘리고 평균 재방문율을 높일 수 있습니다.

기준 프로필은 첫 실행부터 앱 런타임을 개선하는 일반적인 사용자 상호작용을 제공하여 앱 시작뿐만 아니라 그 이후로도 최적화를 적용하는 데 도움을 줍니다. 안내식 AOT 컴파일은 사용자 기기에 의존하지 않으며 휴대기기가 아닌 개발 머신에서 출시당 한 번씩 실행할 수 있습니다. 기준 프로필이 포함된 버전을 제공하면 클라우드 프로필에만 의존하는 것보다 훨씬 빨리 앱을 최적화할 수 있습니다.

기준 프로필을 사용하지 않는 경우 모든 앱 코드는 해석된 후에 메모리에 또는 기기가 유휴 상태일 때 백그라운드의 odex 파일에 JIT로 컴파일됩니다. 그러면 새 경로가 최적화되기 전에 처음 앱을 설치하거나 업데이트한 후 앱을 실행할 때 사용자 환경이 저하될 수 있습니다.

4. 기준 프로필 생성기 모듈 설정

새 Gradle 모듈을 프로젝트에 추가해야 하는 계측 테스트 클래스로 기준 프로필을 생성할 수 있습니다. 프로젝트에 추가하는 가장 쉬운 방법은 Android 스튜디오 Hedgehog 이상에서 제공되는 Android 스튜디오 모듈 마법사를 사용하는 것입니다.

Project 패널에서 프로젝트나 모듈을 마우스 오른쪽 버튼으로 클릭하고 New > Module을 선택하여 새 모듈 마법사 창을 엽니다.

232b04efef485e9c.png

열린 창의 Templates 창에서 Baseline Profile Generator를 선택합니다.

b191fe07969e8c26.png

모듈 이름이나 패키지 이름, 언어, 빌드 구성 언어와 같은 일반적인 매개변수 외에도 새 모듈에는 Target applicationUse Gradle Managed Device라는 일반적이지 않은 두 가지 입력이 더 있습니다.

Target application은 기준 프로필을 생성하는 데 사용되는 앱 모듈입니다. 프로젝트에 앱 모듈이 두 개 이상 있는 경우 생성기를 실행할 모듈을 선택합니다.

Use Gradle Managed Device 체크박스는 자동으로 관리되는 Android Emulator에서 기준 프로필 생성기를 실행하도록 모듈을 설정합니다. Gradle 관리 기기에 관한 자세한 내용은 Gradle 관리 기기로 테스트 확장을 참고하세요. 이를 선택 해제하면 생성기는 연결된 기기를 사용합니다.

새 모듈에 관한 모든 세부정보를 정의한 후 Finish를 클릭하여 모듈 생성을 진행합니다.

모듈 마법사를 사용한 변경

모듈 마법사는 프로젝트에 여러 변경사항을 적용합니다.

baselineprofile(또는 개발자가 마법사에서 선택한 이름)이라는 Gradle 모듈을 추가합니다.

이 모듈은 com.android.test 플러그인을 사용합니다. 이 플러그인은 Gradle에 애플리케이션에 포함하지 않도록 지시하므로 테스트 코드 또는 벤치마크만 포함할 수 있습니다. 기준 프로필 생성을 자동화할 수 있는 androidx.baselineprofile 플러그인도 적용합니다.

마법사는 개발자가 선택한 타겟 애플리케이션 모듈도 변경합니다. 특히 androidx.baselineprofile 플러그인을 적용하고, androidx.profileinstaller 종속 항목을 추가하며, 새로 만든 build.gradle(.kts) 모듈에 baselineProfile 종속 항목을 추가합니다.

plugins {
  id("androidx.baselineprofile")
}

dependencies {
  // ...
  implementation("androidx.profileinstaller:profileinstaller:1.3.0")
  "baselineProfile"(project(mapOf("path" to ":baselineprofile")))
}

androidx.profileinstaller 종속 항목을 추가하면 다음 작업을 할 수 있습니다.

  • 생성된 기준 프로필의 성능 향상을 로컬에서 확인합니다
  • 클라우드 프로필을 지원하지 않는 Android 7(API 수준 24) 및 Android 8(API 수준 26)에서 기준 프로필을 사용합니다.
  • Google Play 서비스가 없는 기기에서 기준 프로필을 사용합니다.

baselineProfile(project(":baselineprofile")) 종속 항목을 사용하면 Gradle이 생성된 기준 프로필을 어떤 모듈에서 가져와야 하는지 알 수 있습니다.

이제 프로젝트가 설정되었으니 기준 프로필 생성기 클래스를 작성해 보겠습니다.

5. 기준 프로필 생성기 작성

대개 앱의 일반적인 사용자 경험에 관한 기준 프로필을 생성합니다.

모듈 마법사는 앱 시작을 위한 기준 프로필을 생성할 수 있는 기본 BaselineProfileGenerator 테스트 클래스를 만듭니다(아래 참고).

@RunWith(AndroidJUnit4::class)
@LargeTest
class BaselineProfileGenerator {

    @get:Rule
    val rule = BaselineProfileRule()

    @Test
    fun generate() {
        rule.collect("com.example.baselineprofiles_codelab") {
            // This block defines the app's critical user journey. This is where you
            // optimize for app startup. You can also navigate and scroll
            // through your most important UI.

            // Start default activity for your app.
            pressHome()
            startActivityAndWait()

            // TODO Write more interactions to optimize advanced journeys of your app.
            // For example:
            // 1. Wait until the content is asynchronously loaded.
            // 2. Scroll the feed content.
            // 3. Navigate to detail screen.

            // Check UiAutomator documentation for more information about how to interact with the app.
            // https://d.android.com/training/testing/other-components/ui-automator
        }
    }
}

이 클래스는 BaselineProfileRule 테스트 규칙을 사용하며 프로필 생성을 위한 하나의 테스트 메서드를 포함합니다. 프로필을 생성하는 진입점은 collect() 함수입니다. 다음 두 가지 매개변수만 있으면 됩니다.

  • packageName: 앱의 패키지입니다.
  • profileBlock: 마지막 람다 매개변수입니다.

profileBlock 람다에서 앱의 일반적인 사용자 경험을 다루는 상호작용을 지정합니다. 라이브러리는 profileBlock을 여러 번 실행하고, 호출된 클래스와 함수를 수집하고, 최적화할 코드로 기기에서 기준 프로필을 생성합니다.

기본적으로, 만들어진 생성기 클래스는 기본 Activity를 시작하는 상호작용을 포함하고 startActivityAndWait() 메서드를 사용하여 앱의 첫 번째 프레임이 렌더링될 때까지 기다립니다.

맞춤 경험을 사용하여 생성기 확장

생성된 클래스에는 앱의 고급 경험을 최적화하기 위해 더 많은 상호작용을 작성하는 TODO도 포함되어 있는 것을 확인할 수 있습니다. 이는 앱이 시작된 이후에도 성능을 최적화할 수 있도록 권장됩니다.

샘플 앱에서 다음을 실행하여 이러한 경험을 식별할 수 있습니다.

  1. 애플리케이션을 시작합니다. 이는 생성된 클래스에서 이미 부분적으로 다뤘습니다.
  2. 콘텐츠가 비동기식으로 로드될 때까지 기다립니다.
  3. 스낵 목록을 스크롤합니다.
  4. 스낵 세부정보로 이동합니다.

다음 스니펫에서 일반적인 경험을 다루는 설명된 함수를 포함하도록 생성기를 변경합니다.

// ...
rule.collect("com.example.baselineprofiles_codelab") {
    // This block defines the app's critical user journey. This is where you
    // optimize for app startup. You can also navigate and scroll
    // through your most important UI.

    // Start default activity for your app.
    pressHome()
    startActivityAndWait()

    // TODO Write more interactions to optimize advanced journeys of your app.
    // For example:
    // 1. Wait until the content is asynchronously loaded.
    waitForAsyncContent()
    // 2. Scroll the feed content.
    scrollSnackListJourney()
    // 3. Navigate to detail screen.
    goToSnackDetailJourney()

    // Check UiAutomator documentation for more information about how to interact with the app.
    // https://d.android.com/training/testing/other-components/ui-automator
}
// ...

이제 언급된 각 경험에 관한 상호작용을 작성합니다. MacrobenchmarkScope의 확장 함수로 작성하므로 제공된 매개변수와 함수에 액세스할 수 있습니다. 이렇게 작성하면 벤치마크와의 상호작용을 재사용하여 성능 향상을 확인할 수 있습니다.

비동기 콘텐츠 대기

많은 앱에는 콘텐츠가 로드되고 렌더링될 때를 시스템에 알리고 사용자가 콘텐츠와 상호작용할 수 있는 앱 시작(완전히 표시된 상태라고도 함) 시 일종의 비동기 로드가 있습니다. 다음 상호작용을 사용하여 생성기(waitForAsyncContent)에서 상태를 기다립니다.

  1. 피드 스낵 목록을 찾습니다.
  2. 목록 내의 일부 항목이 화면에 표시될 때까지 기다립니다.
fun MacrobenchmarkScope.waitForAsyncContent() {
   device.wait(Until.hasObject(By.res("snack_list")), 5_000)
   val contentList = device.findObject(By.res("snack_list"))
   // Wait until a snack collection item within the list is rendered.
   contentList.wait(Until.hasObject(By.res("snack_collection")), 5_000)
}

스크롤 목록 경험

스크롤 스낵 목록 경험(scrollSnackListJourney)의 경우 다음 상호작용을 따를 수 있습니다.

  1. 스낵 목록 UI 요소를 찾습니다.
  2. 시스템 탐색을 트리거하지 않도록 동작 여백을 설정합니다.
  3. 목록을 스크롤하고 UI가 안정될 때까지 기다립니다.
fun MacrobenchmarkScope.scrollSnackListJourney() {
   val snackList = device.findObject(By.res("snack_list"))
   // Set gesture margin to avoid triggering gesture navigation.
   snackList.setGestureMargin(device.displayWidth / 5)
   snackList.fling(Direction.DOWN)
   device.waitForIdle()
}

세부정보 경험으로 이동

마지막 경험(goToSnackDetailJourney)은 다음 상호작용을 구현합니다.

  1. 작업할 수 있는 스낵 목록과 모든 스낵 항목을 찾습니다.
  2. 목록에서 항목을 선택합니다.
  3. 항목을 클릭하고 세부정보 화면이 로드될 때까지 기다립니다. 스낵 목록이 더 이상 화면에 표시되지 않는다는 사실을 활용할 수 있습니다.
fun MacrobenchmarkScope.goToSnackDetailJourney() {
    val snackList = device.findObject(By.res("snack_list"))
    val snacks = snackList.findObjects(By.res("snack_item"))
    // Select snack from the list based on running iteration.
    val index = (iteration ?: 0) % snacks.size
    snacks[index].click()
    // Wait until the screen is gone = the detail is shown.
    device.wait(Until.gone(By.res("snack_list")), 5_000)
}

기준 프로필 생성기를 실행하는 데 필요한 모든 상호작용을 정의한 후에는 생성기를 실행할 기기를 정의해야 합니다.

6. 생성기를 실행할 기기 준비

기준 프로필을 생성하려면 Gradle 관리 기기와 같은 에뮬레이터를 사용하거나 Android 13(API 33) 이상을 실행하는 기기를 사용하는 것이 좋습니다.

프로세스를 재현 가능하게 하고 기준 프로필 생성을 자동화하려면 Gradle 관리 기기를 사용하면 됩니다. Gradle 관리 기기를 사용하면 수동으로 실행하고 해체하지 않고도 Android Emulator에서 테스트를 실행할 수 있습니다. Gradle 관리 기기에 관한 자세한 내용은 Gradle 관리 기기로 테스트 확장을 참고하세요.

Gradle 관리 기기를 정의하려면 다음 스니펫과 같이 :baselineprofile 모듈 build.gradle.kts 파일에 해당 정의를 추가하세요.

android {
  // ...

  testOptions.managedDevices.devices {
    create<ManagedVirtualDevice>("pixel6Api31") {
        device = "Pixel 6"
        apiLevel = 31
        systemImageSource = "aosp"
    }
  }
}

여기서는 Android 11(API 수준 31)을 사용하며 aosp 시스템 이미지는 루팅된 액세스를 지원합니다.

이제 정의된 Gradle 관리 기기를 사용하도록 기준 프로필 Gradle 플러그인을 구성합니다. 이를 위해 다음 스니펫과 같이 기기 이름을 managedDevices 속성에 추가하고 useConnectedDevices를 사용 중지합니다.

android {
  // ...
}

baselineProfile {
   managedDevices += "pixel6Api31"
   useConnectedDevices = false
}

dependencies {
  // ...
}

이제 기준 프로필을 생성합니다.

7. 기준 프로필 생성

기기가 준비되면 기준 프로필을 만들 수 있습니다. 기준 프로필 Gradle 플러그인은 Gradle 작업을 만들어 생성기 테스트 클래스를 실행하고 생성된 기준 프로필을 앱에 적용하는 전체 프로세스를 자동화합니다.

새 모듈 마법사는 터미널과 Android 스튜디오 간에 전환하지 않고도 필요한 모든 매개변수를 사용하여 Gradle 작업이 빠르게 실행될 수 있도록 실행 구성을 만들었습니다.

실행하려면 Generate Baseline Profile 실행 구성을 찾아 Run 버튼 599be5a3531f863b.png을 클릭합니다.

6911ecf1307a213f.png

이 작업은 앞서 정의된 에뮬레이터 이미지를 시작합니다. BaselineProfileGenerator 테스트 클래스에서 상호작용을 여러 번 실행한 후 에뮬레이터를 해체하고 Android 스튜디오에 출력을 제공합니다.

생성기가 성공적으로 완료되면 Gradle 플러그인은 생성된 baseline-prof.txtsrc/release/generated/baselineProfile/ 폴더의 타겟 애플리케이션(:app 모듈)에 자동으로 넣습니다.

fa0f52de5d2ce5e8.png

(선택사항) 명령줄에서 생성기 실행

또는 명령줄에서 생성기를 실행할 수 있습니다. Gradle 관리 기기에서 만든 작업(:app:generateBaselineProfile)을 활용할 수 있습니다. 이 명령어는 baselineProfile(project(:baselineProfile)) 종속 항목으로 정의된 프로젝트의 모든 테스트를 실행합니다. 모듈에는 나중에 성능 향상 확인을 위한 벤치마크도 포함되어 있으므로 이러한 테스트는 에뮬레이터에서 벤치마크 실행에 대한 경고와 함께 실패합니다.

android
   .testInstrumentationRunnerArguments
   .androidx.benchmark.enabledRules=BaselineProfile

이 경우 다음 계측 실행기 인수를 사용하여 모든 기준 프로필 생성기를 필터링할 수 있으며 모든 벤치마크는 건너뜁니다.

전체 명령어는 다음과 같습니다.

./gradlew :app:generateBaselineProfile -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile

기준 프로필을 사용하여 앱 배포

기준 프로필이 생성되고 앱의 소스 코드에 복사되면 평소처럼 앱의 프로덕션 버전을 빌드하세요. 사용자에게 기준 프로필을 배포하기 위해 추가 작업을 할 필요가 없습니다. 빌드 중에 Android Gradle 플러그인에서 선택하며 AAB 또는 APK에 포함됩니다. 그런 다음 빌드를 Google Play에 업로드합니다.

사용자가 앱을 설치하거나 이전 버전에서 앱을 업데이트하면 기준 프로필도 설치되므로 앱을 처음 실행할 때부터 성능이 향상됩니다.

다음 단계에서는 기준 프로필로 앱 성능이 얼마나 향상되었는지 확인하는 방법을 보여줍니다.

8. (선택사항) 기준 프로필 생성 맞춤설정

기준 프로필 Gradle 플러그인에는 특정 요구사항을 충족하도록 프로필을 생성하는 방식을 맞춤설정하는 옵션이 있습니다. 빌드 스크립트에서 baselineProfile { } 구성 블록을 사용하여 동작을 변경할 수 있습니다.

:baselineprofile 모듈 내의 구성 블록은 managedDevices를 추가하고 useConnectedDevices 또는 Gradle 관리 기기를 사용할지 결정할 수 있어 생성기 실행 방식에 영향을 미칩니다.

:app 타겟 모듈 내의 구성 블록은 프로필 저장 위치나 생성 방식을 결정합니다. 다음 매개변수를 변경할 수 있습니다.

  • automaticGenerationDuringBuild: 사용 설정된 경우 프로덕션 출시 빌드를 빌드할 때 기준 프로필을 생성할 수 있습니다. 이는 앱을 출시하기 전에 CI에서 빌드할 때 유용합니다.
  • saveInSrc: 생성된 기준 프로필을 src/ 폴더에 저장할지 지정합니다. 또는 :baselineprofile 빌드 폴더에서 파일에 액세스할 수 있습니다.
  • baselineProfileOutputDir: 생성된 기준 프로필을 저장할 위치를 정의합니다.
  • mergeIntoMain: 기본적으로 기준 프로필이 빌드 변형(제품 버전 및 빌드 유형)마다 생성됩니다. 모든 프로필을 src/main으로 병합하려면 이 플래그를 사용 설정하면 됩니다.
  • filter: 생성된 기준 프로필에서 포함하거나 제외할 클래스 또는 메서드를 필터링할 수 있습니다. 이는 포함된 라이브러리의 코드만 원하는 라이브러리 개발자에게 유용할 수 있습니다.

9. 시작 성능 개선 확인

기준 프로필을 생성하고 앱에 추가한 후 앱의 성능에 원하는 효과를 발휘하는지 확인합니다.

새 모듈 마법사는 StartupBenchmarks라는 벤치마크 클래스를 만듭니다. 이 클래스는 앱 시작 시간을 측정하는 벤치마크를 포함하고 앱이 기준 프로필을 사용할 때와 비교합니다.

이 클래스는 다음과 같습니다.

@RunWith(AndroidJUnit4::class)
@LargeTest
class StartupBenchmarks {

    @get:Rule
    val rule = MacrobenchmarkRule()

    @Test
    fun startupCompilationNone() =
        benchmark(CompilationMode.None())

    @Test
    fun startupCompilationBaselineProfiles() =
        benchmark(CompilationMode.Partial(BaselineProfileMode.Require))

    private fun benchmark(compilationMode: CompilationMode) {
        rule.measureRepeated(
            packageName = "com.example.baselineprofiles_codelab",
            metrics = listOf(StartupTimingMetric()),
            compilationMode = compilationMode,
            startupMode = StartupMode.COLD,
            iterations = 10,
            setupBlock = {
                pressHome()
            },
            measureBlock = {
                startActivityAndWait()

                // TODO Add interactions to wait for when your app is fully drawn.
                // The app is fully drawn when Activity.reportFullyDrawn is called.
                // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
                // from the AndroidX Activity library.

                // Check the UiAutomator documentation for more information on how to
                // interact with the app.
                // https://d.android.com/training/testing/other-components/ui-automator
            }
        )
    }
}

앱의 벤치마크를 실행하고 성능 측정항목을 수집할 수 있는 MacrobenchmarkRule을 사용합니다. 벤치마크 작성의 진입점은 규칙의 measureRepeated 함수입니다.

다음과 같은 여러 매개변수가 필요합니다.

  • packageName: 측정할 애플리케이션입니다.
  • metrics: 벤치마크 중에 측정하려는 정보 유형입니다.
  • iterations: 벤치마크가 반복되는 횟수입니다.
  • startupMode: 벤치마크 시작 시 원하는 애플리케이션 시작 방식입니다.
  • setupBlock: 측정 전에 발생해야 하는 앱과의 상호작용입니다.
  • measureBlock: 벤치마크 중에 측정하려는 앱과의 상호작용입니다.

테스트 클래스에는 두 가지 테스트도 포함되어 있습니다. startupCompilationeNone()과 다른 compilationModebenchmark() 함수를 호출하는 startupCompilationBaselineProfiles()입니다.

CompilationMode

CompilationMode 매개변수는 애플리케이션이 기계어 코드로 사전 컴파일되는 방식을 정의합니다. 다음과 같은 옵션이 있습니다.

  • DEFAULT: 사용 가능한 경우 기준 프로필을 사용하여 앱을 부분적으로 사전 컴파일합니다. compilationMode 매개변수가 적용되지 않은 경우 사용됩니다.
  • None(): 앱 컴파일 상태를 재설정하고 앱을 사전 컴파일하지 않습니다. JIT(Just-In-Time) 컴파일은 앱 실행 중에 계속 사용 설정됩니다.
  • Partial(): 기준 프로필이나 준비 실행 또는 둘 다를 통해 앱을 사전 컴파일합니다.
  • Full(): 전체 애플리케이션 코드를 사전 컴파일합니다. 이는 Android 6(API 23) 이하에서 유일하게 지원됩니다.

애플리케이션 성능을 최적화하려는 경우 DEFAULT 컴파일 모드를 선택하면 됩니다. 성능이 Google Play에서 앱을 설치할 때와 비슷하기 때문입니다. 기준 프로필을 통해 제공되는 성능 이점을 비교하려면 컴파일 모드 NonePartial의 결과를 비교하면 됩니다.

콘텐츠를 기다리도록 벤치마크 수정

벤치마크는 앱과의 상호작용을 작성하여 기준 프로필 생성기와 유사하게 작성됩니다. 기본적으로, 생성된 벤치마크는 첫 번째 프레임이 렌더링될 때까지만 기다리므로(BaselineProfileGenerator와 유사) 비동기 콘테츠를 기다리도록 개선하는 것이 좋습니다.

이렇게 하려면 생성기에 관해 작성하는 확장 함수를 재사용하면 됩니다. 이 벤치마크는 StartupTimingMetric()을 사용하여 시작 타이밍을 캡처하므로 여기에서는 비동기 콘텐츠 대기만 포함한 후 생성기에 정의된 다른 사용자 경험을 위한 별도의 벤치마크를 작성하는 것이 좋습니다.

// ...
measureBlock = {
   startActivityAndWait()

   // The app is fully drawn when Activity.reportFullyDrawn is called.
   // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
   // from the AndroidX Activity library.
   waitForAsyncContent() // <------- Added to wait for async content.

   // Check the UiAutomator documentation for more information on how to
   // interact with the app.
   // https://d.android.com/training/testing/other-components/ui-automator
}

벤치마크 실행

벤치마크는 계측 테스트를 실행하는 방식과 동일하게 실행할 수 있습니다. 옆에 있는 여백 아이콘으로 테스트 함수 또는 전체 클래스를 실행할 수 있습니다.

587b04d1a76d1e9d.png

실제 기기가 선택되어 있는지 확인합니다. Android Emulator에서 벤치마크를 실행하면 벤치마크가 잘못된 결과를 제공할 수 있다는 경고와 함께 런타임에 실패하기 때문입니다. 기술적으로는 에뮬레이터에서 실행할 수 있지만 호스트 머신 성능을 측정하는 것입니다. 과부하가 발생하는 경우 벤치마크가 느리고 반대로 실행됩니다.

94e0da86b6f399d5.png

벤치마크를 실행하면 앱이 다시 빌드되고 앱에서 벤치마크를 실행합니다. 벤치마크는 정의된 iterations에 따라 앱을 여러 번 시작, 중지, 재설치합니다.

벤치마크가 완료되면 다음 스크린샷과 같이 Android 스튜디오 출력에서 시간을 확인할 수 있습니다.

282f90d5f6ff5196.png

스크린샷을 보면 앱 시작 시간이 CompilationMode마다 다릅니다. 중앙값은 다음 표에 나와 있습니다.

timeToInitialDisplay [ms]

timeToFullDisplay [ms]

없음

202.2

818.8

BaselineProfiles

193.7

637.9

개선사항

4%

28%

timeToFullDisplay의 컴파일 모드 간 차이는 180밀리초이고 이는 기준 프로필을 보유하는 것만으로 28% 개선된 수치입니다. CompilationNone은 앱이 시작되는 동안 기기에서 대부분의 JIT 컴파일을 실행해야 하므로 성능이 좋지 않습니다. CompilationBaselineProfiles는 성능이 더 나은데 이는 기준 프로필이 있는 부분 컴파일이 사용자가 사용할 가능성이 가장 높은 코드를 AOT 컴파일하고 중요하지 않은 코드는 사전 컴파일되지 않은 채로 두므로 즉시 로드하지 않아도 되기 때문입니다.

10. (선택사항) 스크롤 성능 개선 확인

이전 단계와 마찬가지로 스크롤 성능을 측정하고 확인할 수 있습니다. 먼저 벤치마크 규칙과 다른 컴파일 모드를 사용하는 두 가지 테스트 메서드가 포함된 ScrollBenchmarks 테스트 클래스를 만듭니다.

@LargeTest
@RunWith(AndroidJUnit4::class)
class ScrollBenchmarks {

   @get:Rule
   val rule = MacrobenchmarkRule()

   @Test
   fun scrollCompilationNone() = scroll(CompilationMode.None())

   @Test
   fun scrollCompilationBaselineProfiles() = scroll(CompilationMode.Partial())

   private fun scroll(compilationMode: CompilationMode) {
       // TODO implement
   }
}

scroll 메서드 내에서 필수 매개변수와 함께 measureRepeated 함수를 사용합니다. metrics 매개변수의 경우 UI 프레임을 생성하는 데 걸리는 시간을 측정하는 FrameTimingMetric을 사용합니다.

private fun scroll(compilationMode: CompilationMode) {
   rule.measureRepeated(
       packageName = "com.example.baselineprofiles_codelab",
       metrics = listOf(FrameTimingMetric()),
       compilationMode = compilationMode,
       startupMode = StartupMode.WARM,
       iterations = 10,
       setupBlock = {
           // TODO implement
       },
       measureBlock = {
           // TODO implement
       }
   )
}

이번에는 setupBlockmeasureBlock 간의 상호작용을 더 많이 분할하여 첫 번째 레이아웃과 콘텐츠 스크롤 중에만 프레임 지속 시간을 측정해야 합니다. 따라서 기본 화면을 시작하는 함수를 setupBlock에 넣고 이미 만든 확장 함수 waitForAsyncContent()scrollSnackListJourney()measureBlock에 넣습니다.

private fun scroll(compilationMode: CompilationMode) {
   rule.measureRepeated(
       packageName = "com.example.baselineprofiles_codelab",
       metrics = listOf(FrameTimingMetric()),
       compilationMode = compilationMode,
       startupMode = StartupMode.WARM,
       iterations = 10,
       setupBlock = {
           pressHome()
           startActivityAndWait()
       },
       measureBlock = {
           waitForAsyncContent()
           scrollSnackListJourney()
       }
   )
}

벤치마크가 준비되면 이전처럼 실행하여 결과를 가져올 수 있습니다(다음 스크린샷 참고).

84aa99247226fc3a.png

FrameTimingMetric은 50번째, 90번째, 95번째, 99번째 백분위수에서 프레임 시간을 밀리초 단위(frameDurationCpuMs)로 출력합니다. Android 12(API 수준 31) 이상에서는 프레임이 제한(frameOverrunMs)을 초과한 시간도 반환됩니다. 이 값은 음수일 수 있습니다. 즉, 프레임을 생성하는 데 시간이 추가로 남았음을 의미합니다.

결과에서 CompilationBaselineProfiles의 프레임 지속 시간이 평균 2밀리초 더 짧은 것을 확인할 수 있는데 이는 사용자가 눈치채지 못할 수 있습니다. 하지만 다른 백분위수의 경우에는 결과가 더 분명합니다. P99의 경우 차이는 43.5ms입니다. 즉, 90FPS로 작동하는 기기에서 건너뛴 프레임이 3개 이상입니다. 예를 들어 Pixel 6의 경우 프레임을 렌더링하는 데 최대 11ms가 소요됩니다(1,000ms/90FPS = ~11ms).

11. 축하합니다

축하합니다. 이 Codelab을 완료하고 기준 프로필을 사용하여 앱 성능을 개선했습니다.

추가 리소스

다음 추가 리소스를 참고하세요.

참조 문서