Macrobenchmark로 앱 성능 검사

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

1. 시작하기 전에

이 Codelab에서는 Macrobenchmark 라이브러리를 사용하는 방법을 알아봅니다. 사용자 참여의 핵심 측정항목인 앱 시작 시간과 앱에서 버벅거림이 발생할 수 있는 위치를 알려주는 프레임 시간을 측정합니다.

필요한 항목

실행할 작업

  • 기존 애플리케이션에 벤치마킹 모듈 추가
  • 앱 시작 및 프레임 시간 측정

학습할 내용

  • 애플리케이션 성능 측정

2. 설정

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

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

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

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

  1. 'Welcome to Android Studio' 창에서 c01826594f360d94.png Open an Existing Project를 선택합니다.
  2. [Download Location]/android-performance/benchmarking 폴더를 선택합니다(도움말: build.gradle이 포함된 benchmarking 디렉터리를 선택해야 함).
  3. Android 스튜디오에서 프로젝트를 가져오면 app 모듈을 실행하여 벤치마킹할 샘플 애플리케이션을 빌드할 수 있는지 확인합니다.

3. Jetpack Macrobenchmark 소개

Jetpack Macrobenchmark 라이브러리는 시작, UI와의 상호작용, 애니메이션 등 더 큰 최종 사용자 상호작용의 성능을 측정합니다. 이 라이브러리는 테스트 중인 성능 환경을 직접 제어할 수 있도록 합니다. 이를 통해 앱 시작, 프레임 시간, 추적된 코드 섹션을 직접 측정하도록 애플리케이션의 컴파일, 시작, 중지를 제어할 수 있습니다.

Jetpack Macrobenchmark를 사용하면 다음 작업을 할 수 있습니다.

  • 확정적인 실행 패턴과 스크롤 속도로 앱을 여러 번 측정
  • 여러 테스트 실행의 결과를 평균화하여 성능 편차 완화
  • 앱의 컴파일 상태 제어: 성능 안정성의 주요 요소
  • Google Play 스토어에서 실행한 설치 시간 최적화의 로컬 재현으로 실제 성능 확인

이 라이브러리를 사용하는 계측은 애플리케이션 코드를 직접 호출하지 않고 대신 터치, 클릭, 스와이프 등 사용자가 하듯이 애플리케이션을 탐색합니다. 이러한 상호작용 중에 기기에서 측정이 이루어집니다. 애플리케이션 코드의 일부를 직접 측정하려면 대신 Jetpack Microbenchmark를 참고하세요.

벤치마크 작성은 계측 테스트 작성과 유사하지만 앱의 상태를 확인할 필요가 없다는 점은 다릅니다. 벤치마크는 JUnit 문법(@RunWith, @Rule, @Test 등)을 사용하지만 테스트는 별도의 프로세스에서 실행되어 앱을 다시 시작하거나 사전 컴파일할 수 있습니다. 이를 통해 사용자와 마찬가지로 내부 상태를 방해하지 않고 앱을 실행할 수 있습니다. 이렇게 하려면 UiAutomator를 사용하여 타겟 애플리케이션과 상호작용하면 됩니다.

샘플 앱

이 Codelab에서는 JetSnack 샘플 애플리케이션을 사용합니다. Jetpack Compose를 사용하는 가상 스낵 주문 앱입니다. 애플리케이션의 성능을 측정하기 위해 앱의 아키텍처에 관한 세부정보를 알 필요는 없습니다. 벤치마크에서 UI 요소에 액세스할 수 있도록 앱의 동작 방식과 UI 구조를 알아야 합니다. 앱을 실행하고 원하는 스낵을 주문하여 기본 화면을 익히세요.

70978a2eb7296d54.png

4. Macrobenchmark 라이브러리 추가

Macrobenchmark를 사용하려면 프로젝트에 새 Gradle 모듈을 추가해야 합니다. 프로젝트에 추가하는 가장 쉬운 방법은 Android 스튜디오 모듈 마법사를 사용하는 것입니다.

새 모듈 대화상자를 엽니다. Project 패널에서 프로젝트 또는 모듈을 마우스 오른쪽 버튼으로 클릭하고 New > Module을 선택합니다.

54a3ec4a924199d6.png

Templates 창에서 Benchmark를 선택하고 Macrobenchmark가 벤치마크 모듈 유형으로 선택되어 있는지 확인하고 세부정보가 예상대로인지 확인합니다.

Macrobenchmark가 선택되어 있는 벤치마크 모듈 유형

  • Target application: 벤치마킹할 앱입니다.
  • Module name: 벤치마킹 Gradle 모듈의 이름입니다.
  • Package name: 벤치마크의 패키지 이름입니다.
  • Minimum SDK: Android 6(API 수준 23) 이상이 필요합니다.

Finish를 클릭합니다.

모듈 마법사의 변경사항

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

macrobenchmark(또는 개발자가 마법사에서 선택한 이름)라는 Gradle 모듈을 추가합니다. 이 모듈은 com.android.test 플러그인을 사용합니다. 이 플러그인은 Gradle에 애플리케이션에 포함하지 않도록 지시하므로 테스트 코드(또는 벤치마크)만 포함할 수 있습니다.

마법사는 개발자가 선택한 타겟 애플리케이션 모듈도 변경합니다. 특히 다음 스니펫과 같이 새 benchmark 빌드 유형을 :app 모듈 build.gradle에 추가합니다.

benchmark {
   initWith buildTypes.release
   signingConfig signingConfigs.debug
   matchingFallbacks = ['release']
   debuggable false
}

이 buildType은 release buildType을 최대한 가깝게 에뮬레이션해야 합니다. release buildType과 다른 점은 프로덕션 키 저장소 없이 로컬에서 앱을 빌드할 수 있도록 필요한 debugsigningConfig가 설정되어 있다는 점입니다.

그러나 debuggable 플래그가 사용 중지되었으므로 마법사는 <profileable> 태그를 AndroidManifest.xml에 추가하여 벤치마크가 출시 성능으로 앱을 프로파일링할 수 있도록 합니다.

<application>

  <profileable
     android:shell="true"
     tools:targetApi="q" />

</application>

<profileable>의 기능에 관한 자세한 내용은 문서를 참고하세요.

마법사가 하는 마지막 작업은 시작 시간을 벤치마킹하는 스캐폴드를 만드는 것입니다(다음 단계에서 사용함).

이제 벤치마크를 작성해 보겠습니다.

5. 앱 시작 측정

앱 시작 시간 또는 사용자가 앱 사용을 시작하는 데 걸리는 시간은 사용자 참여에 영향을 미치는 주요 측정항목입니다. 모듈 마법사는 다음과 같이 앱 시작 시간을 측정할 수 있는 ExampleStartupBenchmark 테스트 클래스를 만듭니다.

@RunWith(AndroidJUnit4::class)
class ExampleStartupBenchmark {
   @get:Rule
   val benchmarkRule = MacrobenchmarkRule()

   @Test
   fun startup() = benchmarkRule.measureRepeated(
       packageName = "com.example.macrobenchmark_codelab",
       metrics = listOf(StartupTimingMetric()),
       iterations = 5,
       startupMode = StartupMode.COLD,
   ){
        pressHome()
        startActivityAndWait()
   }
}

모든 매개변수는 무엇을 의미하나요?

벤치마크를 작성할 때 진입점은 MacrobenchmarkRulemeasureRepeated 함수입니다. 이 함수는 벤치마크의 모든 것을 처리하지만 다음 매개변수는 지정해야 합니다.

  • packageName: 벤치마크는 테스트 중인 앱과 별도의 프로세스에서 실행되므로 측정할 애플리케이션을 지정해야 합니다.
  • metrics: 벤치마크 중에 측정하려는 정보 유형입니다. 여기서는 앱 시작 시간에 관심이 있습니다. 다른 유형의 측정항목은 문서를 참고하세요.
  • iterations: 벤치마크가 반복되는 횟수입니다. 많이 반복할수록 결과의 안정성이 높아지지만 실행 시간이 길어집니다. 이상적인 횟수는 이 특정 측정항목의 노이즈가 앱에 얼마나 발생하는지에 따라 다릅니다.
  • startupMode: 벤치마크를 시작할 때 애플리케이션을 시작하는 방법을 정의할 수 있습니다. COLD, WARM, HOT을 사용할 수 있습니다. 여기서는 COLD를 사용합니다. 앱에서 해야 하는 가장 큰 작업량을 나타내기 때문입니다.
  • measureBlock(마지막 람다 매개변수): 이 함수에서 개발자는 벤치마크 중에 측정하려는 작업을 정의하고(활동 시작, UI 요소 클릭, 스크롤, 스와이프 등) Macrobenchmark는 이 블록 중에 정의된 metrics를 수집합니다.

벤치마크 작업 작성 방법

Macrobenchmark가 앱을 재설치하고 다시 시작합니다. 앱의 상태와 독립적으로 상호작용을 작성해야 합니다. Macrobenchmark는 앱과 상호작용하는 데 유용한 함수와 매개변수를 제공합니다.

가장 중요한 항목은 startActivityAndWait()입니다. 이 함수는 기본 활동을 시작하고 첫 프레임을 렌더링할 때까지 기다린 후 벤치마크의 안내를 계속 진행합니다. 다른 활동을 시작하거나 시작 인텐트를 조정하려면 선택적으로 intent 또는 block 매개변수를 사용하면 됩니다.

또 다른 유용한 함수는 pressHome()입니다. 이 함수를 사용하면 반복할 때마다 앱을 종료하지 않는 경우(예: StartupMode.HOT 사용 시) 벤치마크를 기본 조건으로 재설정할 수 있습니다.

다른 상호작용의 경우 device 매개변수를 사용하면 UI 요소를 찾고 스크롤하며 일부 콘텐츠를 기다리는 등의 작업을 할 수 있습니다.

시작 벤치마크를 정의했으므로 이제 다음 단계에서 실행해 보겠습니다.

6. 벤치마크 실행

벤치마크 테스트를 실행하기 전에 Android 스튜디오에서 올바른 빌드 변형이 선택되어 있는지 확인합니다.

  1. Build Variants 패널을 선택합니다.
  2. Active Build Variantbenchmark로 변경합니다.
  3. Android 스튜디오가 동기화될 때까지 기다립니다.

b8a622b5a347e9f3.gif

이렇게 하지 않으면 런타임 시 벤치마크가 실패하고 debuggable 애플리케이션을 벤치마킹하면 안 된다는 오류가 표시됩니다.

java.lang.AssertionError: ERRORS (not suppressed): DEBUGGABLE
WARNINGS (suppressed):

ERROR: Debuggable Benchmark
Benchmark is running with debuggable=true, which drastically reduces
runtime performance in order to support debugging features. Run
benchmarks with debuggable=false. Debuggable affects execution speed
in ways that mean benchmark improvements might not carry over to a
real user's experience (or even regress release performance).

계측 인수 androidx.benchmark.suppressErrors = "DEBUGGABLE"을 사용하여 이 오류를 일시적으로 억제할 수 있습니다. Android Emulator에서 벤치마크 실행 단계에서와 동일한 단계를 따르면 됩니다.

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

e72cc74b6fecffdb.png

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

8d883cb84d4cfb26.png

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

7. (선택사항) Android Emulator에서 벤치마크 실행

실제 기기가 없지만 여전히 벤치마크를 실행하려는 경우 계측 인수 androidx.benchmark.suppressErrors = "EMULATOR"를 사용하여 런타임 오류를 억제할 수 있습니다.

오류를 억제하려면 실행 구성을 수정합니다.

  1. 실행 메뉴에서 'Edit Configurations...'를 선택합니다. 354500cd155dec5b.png
  2. 열린 창에서 '계측 인수' 옆에 있는 '옵션' 아이콘 d628c071dd2bf454.png을 선택합니다. a4c3519e48f4ac55.png
  3. ➕를 클릭하고 세부정보를 입력하여 계측 추가 매개변수를 추가합니다. a06c7f6359d6b92c.png
  4. OK를 클릭하여 선택사항을 확인합니다. 'Instrumentation arguments' 행에 인수가 표시됩니다. c30baf54c420ed79.png
  5. OK를 클릭하여 실행 구성을 확인합니다.

또는 코드베이스에 영구적으로 저장해야 하는 경우 다음과 같이 :macrobenchmark 모듈의 build.gradle에서 실행할 수 있습니다.

defaultConfig {
    // ...
    testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = 'EMULATOR'
}

8. 시작 결과 이해

벤치마크의 실행이 완료되면 다음 스크린샷과 같이 Android 스튜디오에 직접 결과가 제공됩니다.

timeToInitialDisplayMs 값은 최솟값 197.9, 중앙값 201.8, 최댓값 220.3입니다.

이 사례에서 확인할 수 있듯이 Google Pixel 3의 시작 시간은 최솟값 197.9ms, 중앙값 201.8ms, 최댓값 220.3ms였습니다. 기기에서는 동일한 벤치마크를 실행할 때 다른 결과가 나타날 수도 있습니다. 결과는 다음과 같은 여러 요소의 영향을 받을 수 있습니다.

  • 기기 성능의 강력함 정도
  • 사용하는 시스템 버전
  • 백그라운드에서 실행 중인 앱

따라서 동일한 기기에서 결과를 비교하는 것이 중요합니다(동일한 상태에 있는 기기면 더 좋음). 이렇게 하지 않으면 차이가 클 수 있습니다. 동일한 상태를 보장할 수 없는 경우 iterations의 수를 늘려 결과 이상점을 제대로 처리하는 것이 좋습니다.

조사를 위해 Macrobenchmark 라이브러리는 벤치마크를 실행하는 동안 시스템 트레이스를 기록합니다. 편의를 위해 Android 스튜디오에서는 각 반복 및 측정된 시간을 시스템 트레이스 링크로 표시하므로 이를 쉽게 열어 조사할 수 있습니다.

9. (선택적 연습) 앱을 사용할 준비가 되면 선언

Macrobenchmark는 앱에서 렌더링된 첫 번째 프레임까지의 시간(timeToInitialDisplay)을 자동으로 측정할 수 있습니다. 그러나 첫 번째 프레임이 렌더링될 때까지 앱 콘텐츠의 로드가 완료되지 않는 것이 일반적이며 개발자는 앱을 사용할 수 있을 때까지 사용자가 기다려야 하는 시간을 알고 싶을 수 있습니다. 이를 전체 표시 시간이라고 합니다. 앱에서 콘텐츠를 완전히 로드하여 사용자가 상호작용할 수 있습니다. Macrobenchmark 라이브러리는 이 시간을 자동으로 감지할 수 있지만 언제 발생했는지 알 수 있도록 개발자는 앱을 조정해야 합니다.

Activity.reportFullyDrawn() 함수

샘플에서는 데이터가 로드될 때까지 간단한 진행률 표시줄을 보여주므로 데이터가 준비되어 스낵 목록이 배치되고 그려질 때까지 기다리는 것이 좋습니다. 샘플 애플리케이션을 조정하고 reportFullyDrawn() 호출을 추가해 보겠습니다.

Project 창의 .ui.home 패키지에서 Feed.kt 파일을 엽니다.

800f7390ca53998d.png

이 파일에서 스낵 목록 구성을 담당하는 SnackCollectionList 컴포저블을 찾습니다.

먼저 데이터가 준비되었는지 확인해야 합니다. 콘텐츠가 준비될 때까지 ​​snackCollections 매개변수에서 빈 목록을 가져오므로 비어 있는지 확인할 수 있습니다.

if (snackCollections.isNotEmpty()) {
  // TODO call reportFullyDrawn()
}

Box(modifier) {
   LazyColumn {
   // ...
}

이제 모든 리컴포지션 시 이를 호출하는 것을 방지하는 부작용을 사용해야 합니다. LaunchedEffectUnit 매개변수와 함께 사용하여 한 번만 호출되도록 할 수 있습니다.

if (snackCollections.isNotEmpty()) {
   LaunchedEffect(Unit) {
       // TODO call reportFullyDrawn
   }
}

마지막 단계는 실제로 Activity.reportfullyDrawn() 메서드를 호출하는 것입니다. Compose는 호스팅 Activity에 액세스할 수 없으므로 개발자는 LocalView를 사용하여 Context에 액세스합니다. Context는 컴포저블로 setContent{ }를 호출하는 경우 Activity입니다.

호출을 doOnPreDraw에 래핑하여 프레임 중간에 호출하지 않도록 해야 합니다.

if (snackCollections.isNotEmpty()) {
    val view = LocalView.current
    LaunchedEffect(Unit) {
        // Get Activity from View's context
        val activity = view.context as? Activity
        // Call reportFullyDrawn before draw happens
        view.doOnPreDraw { activity?.reportFullyDrawn() }
    }
}

그런 다음에는 콘텐츠를 기다리도록 ExampleStartupBenchmark를 조정해야 합니다. 그러지 않으면 벤치마크가 첫 번째 렌더링된 프레임으로 완료되어 측정항목을 건너뛸 수 있습니다.

현재 시작 벤치마크는 첫 번째 렌더링된 프레임만 기다립니다. 대기 자체가 startActivityAndWait() 함수에 포함되어 있습니다.

@Test
fun startup() = benchmarkRule.measureRepeated(
   packageName = "com.example.macrobenchmark_codelab",
   metrics = listOf(StartupTimingMetric()),
   iterations = 5,
   startupMode = StartupMode.COLD,
) {
   pressHome()
   startActivityAndWait()

   // TODO wait until content is ready
}

여기서는 콘텐츠 목록에 하위 요소가 포함될 때까지 기다릴 수 있으므로 다음 스니펫과 같이 wait()를 추가합니다.

@Test
fun startup() = benchmarkRule.measureRepeated(
   //...
) {
   pressHome()
   startActivityAndWait()

   val contentList = device.findObject(By.res("snack_list"))
   val searchCondition = Until.hasObject(By.res("snack_collection"))
   // Wait until a snack collection item within the list is rendered
   contentList.wait(searchCondition, 5_000)
}

스니펫에서 어떤 일이 발생하는지 설명하는 방법은 다음과 같습니다.

  1. Modifier.testTag("snack_list") 덕분에 스낵 목록을 찾습니다.
  2. snack_collection을 대기할 요소로 사용하는 검색 조건을 정의합니다.
  3. UiObject2.wait 함수를 사용하여 5초 시간 제한으로 UI 객체 내에서 조건을 기다립니다.

이제 벤치마크를 다시 실행할 수 있으며 라이브러리는 다음 스크린샷과 같이 timeToInitialDisplaytimeToFullDisplay를 자동으로 측정합니다.

timeToFullDisplayMs 결과는 최솟값 581.0, 중앙값 605.4, 최댓값 651.5이고 timeToInitialDisplayMs 결과는 최솟값 191.5, 중앙값 200.0, 최댓값 221.7입니다.

여기서 TTID와 TTFD의 차이는 405ms인 것을 알 수 있습니다. 즉, 사용자는 200ms가 지난 후에 렌더링된 첫 번째 프레임을 볼 수 있지만 목록을 스크롤하려면 405ms가 더 지나야 합니다.

10. 벤치마크 프레임 시간

사용자가 앱을 방문한 후 사용자가 접하게 되는 두 번째 측정항목은 앱의 원활한 정도입니다. 다시 말하면 앱이 프레임을 드롭하는지 여부입니다. 이를 측정하기 위해 FrameTimingMetric을 사용합니다.

예를 들어 항목 목록의 스크롤 동작을 측정하려고 하지만 이 시나리오 전에 아무것도 측정하지 않고자 한다고 가정해 보겠습니다. 벤치마크를 측정된 상호작용과 측정되지 않은 상호작용으로 분할해야 합니다. 이를 위해 setupBlock 람다 매개변수를 사용합니다.

측정되지 않은 상호작용(setupBlock에서 정의됨)에서 기본 활동을 시작하고 측정된 상호작용(measureBlock에서 정의됨)에서 UI 목록 요소를 찾아 목록을 스크롤한 후 화면에서 콘텐츠가 렌더링될 때까지 기다립니다. 상호작용을 두 부분으로 분할하지 않으면 앱을 시작하는 동안 생성된 프레임과 목록 스크롤 중에 생성된 프레임을 구별할 수 없습니다.

프레임 시간 벤치마크 만들기

설명된 흐름을 달성하기 위해 스크롤 프레임 시간 벤치마크가 포함될 scroll() 테스트로 새 ScrollBenchmarks 클래스를 만들어 보겠습니다. 먼저 벤치마크 규칙과 빈 테스트 메서드를 사용하여 테스트 클래스를 만듭니다.

@RunWith(AndroidJUnit4::class)
class ScrollBenchmarks {
   @get:Rule
   val benchmarkRule = MacrobenchmarkRule()

   @Test
   fun scroll() {
       // TODO implement scrolling benchmark
   }
}

그런 다음 필수 매개변수로 벤치마크 스켈레톤을 추가합니다.

@Test
fun scroll() {
   benchmarkRule.measureRepeated(
       packageName = "com.example.macrobenchmark_codelab",
       iterations = 5,
       metrics = listOf(FrameTimingMetric()),
       startupMode = StartupMode.COLD,
       setupBlock = {
           // TODO Add not measured interactions.
       }
   ) {
       // TODO Add interactions to measure list scrolling.
   }
}

벤치마크는 metrics 매개변수와 setupBlock을 제외하고 startup 벤치마크와 동일한 매개변수를 사용합니다. FrameTimingMetric은 애플리케이션에서 생성된 프레임 시간을 수집합니다.

이제 setupBlock을 채워보겠습니다. 앞서 설명한 것처럼 이 람다에서 상호작용은 벤치마크에 의해 측정되지 않습니다. 이 블록을 사용하여 앱을 열고 첫 번째 프레임이 렌더링될 때까지 기다리면 됩니다.

@Test
fun scroll() {
   benchmarkRule.measureRepeated(
       packageName = "com.example.macrobenchmark_codelab",
       iterations = 5,
       metrics = listOf(FrameTimingMetric()),
       startupMode = StartupMode.COLD,
       setupBlock = {
           // Start the default activity, but don't measure the frames yet
           pressHome()
           startActivityAndWait()
       }
   ) {
       // TODO Add interactions to measure list scrolling.
   }
}

이제 measureBlock(마지막 람다 매개변수)을 작성해 보겠습니다. 먼저 스낵 목록에 항목을 제출하는 것은 비동기 작업이므로 콘텐츠가 준비될 때까지 기다려야 합니다.

benchmarkRule.measureRepeated(
   // ...
) {
    val contentList = device.findObject(By.res("snack_list"))

    val searchCondition = Until.hasObject(By.res("snack_collection"))
    // Wait until a snack collection item within the list is rendered
    contentList.wait(searchCondition, 5_000)

   // TODO Scroll the list
}

초기 레이아웃 설정을 측정하는 데 관심이 없다면 setupBlock에서 콘텐츠가 준비될 때까지 기다릴 수도 있습니다.

다음으로, 동작 여백을 스낵 목록에 설정합니다. 이 작업을 해야 하는 이유는 그러지 않으면 앱이 시스템 탐색을 트리거하고 콘텐츠를 스크롤하는 대신 앱을 종료할 수 있기 때문입니다.

benchmarkRule.measureRepeated(
   // ...
) {
   val contentList = device.findObject(By.res("snack_list"))

   val searchCondition = Until.hasObject(By.res("snack_collection"))
   // Wait until a snack collection item within the list is rendered
   contentList.wait(searchCondition, 5_000)

   // Set gesture margin to avoid triggering system gesture navigation
   contentList.setGestureMargin(device.displayWidth / 5)

   // TODO Scroll the list
}

마지막으로 fling() 동작으로 목록을 실제로 스크롤(스크롤하려는 양과 속도에 따라 scroll() 또는 swipe()도 사용 가능)하고 UI가 유휴 상태가 될 때까지 기다립니다.

benchmarkRule.measureRepeated(
   // ...
) {
   val contentList = device.findObject(By.res("snack_list"))

   val searchCondition = Until.hasObject(By.res("snack_collection"))
   // Wait until a snack collection item within the list is rendered
   contentList.wait(searchCondition, 5_000)

   // Set gesture margin to avoid triggering gesture navigation
   contentList.setGestureMargin(device.displayWidth / 5)

   // Scroll down the list
   contentList.fling(Direction.DOWN)

   // Wait for the scroll to finish
   device.waitForIdle()
}

라이브러리는 정의된 작업을 실행할 때 앱에서 생성된 프레임 시간을 측정합니다.

이제 벤치마크를 실행할 준비가 되었습니다.

벤치마크 실행

시작 벤치마크와 동일한 방식으로 벤치마크를 실행할 수 있습니다. 테스트 옆에 있는 여백 아이콘을 클릭하고 Run 'scroll()'을 선택합니다.

30043f8d11fec372.png

벤치마크 실행에 관한 자세한 내용은 벤치마크 실행 단계를 확인하세요.

결과 이해

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

cf37f3536955b358.png

34cccbfa0b43b62c.png 결과를 보면 Google Pixel 6 Pro에서 프레임을 만들기 위한 중앙값(P50)이 프레임 시간 제한보다 9.7ms 낮은 4.2ms임을 알 수 있습니다. 또한 99(P99)를 넘는 백분위수에서 건너뛴 프레임이 있을 수 있습니다. 프레임이 생성되는 데 18.3ms(제한보다 14.6ms 초과)가 걸렸기 때문입니다.

앱 시작 결과와 마찬가지로 iteration을 클릭하여 벤치마크 중에 기록된 시스템 트레이스를 열고 결과 시간에 영향을 미친 요소를 조사할 수 있습니다.

11. 축하합니다

축하합니다. Jetpack Macrobenchmark를 사용하여 성능을 측정하는 방법에 관한 Codelab을 완료했습니다.

다음 단계

기준 프로필을 사용하여 앱 성능 개선 Codelab을 확인하세요. Macrobenchmark 및 기타 성능 샘플이 포함된 성능 샘플 GitHub 저장소도 확인하세요.

참조 문서