Jetpack Macrobenchmark를 사용하는 사용 사례 벤치마크

Macrobenchmark를 사용하면 Android M(API 23) 이상을 실행하는 기기의 앱과 관련된 시작 및 런타임 성능 테스트를 직접 작성할 수 있습니다.

Macrobenchmark는 최신 버전의 Android 스튜디오(2020.3.1 베타 4 이상)와 함께 사용하는 것이 좋습니다. 최신 버전의 IDE에는 Macrobenchmark와 통합된 새로운 기능이 있기 때문입니다. 이전 버전의 Android 스튜디오 사용자는 이 주제의 뒷부분에 나오는 추가 안내에 따라 트레이스 파일을 사용할 수 있습니다.

벤치마킹 테스트는 Macrobenchmark 라이브러리에서 MacrobenchmarkRule JUnit4 규칙 API를 통해 제공됩니다.

Kotlin

    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    fun startup() = benchmarkRule.measureRepeated(
        packageName = "mypackage.myapp",
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.COLD
    ) { // this = MacrobenchmarkScope
        pressHome()
        val intent = Intent()
        intent.setPackage("mypackage.myapp")
        intent.setAction("mypackage.myapp.myaction")
        startActivityAndWait(intent)
    }
  

자바

    @Rule
    MacrobenchmarkRule benchmarkRule = MacrobenchmarkRule()

    @Test
    void startup() = benchmarkRule.measureRepeated(
        "mypackage.myapp", // packageName
        listOf(StartupTimingMetric()), // metrics
        5, // iterations
        StartupMode.COLD // startupMode
    ) { scope ->
        scope.pressHome()
        Intent intent = Intent()
        intent.setPackage("mypackage.myapp")
        intent.setAction("mypackage.myapp.myaction")
        scope.startActivityAndWait(intent)
    }
  

측정항목은 Android 스튜디오에 바로 표시되며 JSON 파일의 CI 사용과 관련해 출력되는 값이기도 합니다.

스튜디오 결과 샘플

모듈 설정

매크로 벤치마크에는 앱을 측정하는 테스트를 실행하는 앱 코드와 분리된 com.android.test 모듈이 필요합니다.

Arctic Fox

Arctic Fox에서는 라이브러리 모듈을 만들어 테스트 모듈로 변환합니다.

새 모듈 추가

프로젝트에 새 모듈을 추가합니다. 이 모듈에는 Macrobenchmark 테스트가 포함됩니다.

  1. Android 스튜디오의 Project 패널에서 프로젝트 또는 모듈을 마우스 오른쪽 버튼으로 클릭하고 New > Module을 클릭합니다.
  2. Templates 창에서 Android Library를 선택합니다.
  3. 모듈 이름으로 macrobenchmark를 입력합니다.
  4. Minimum SDKAPI 23: Android M으로 설정합니다.
  5. Finish를 클릭합니다.

새 라이브러리 모듈 구성

Gradle 파일 수정

Macrobenchmark 모듈의 build.gradle을 다음과 같이 맞춤설정합니다.

  1. 플러그인을 com.android.library에서 com.android.test로 변경합니다.
  2. 그 밖에 필요한 테스트 모듈 속성을 android {} 블록에 추가합니다.
  3.    targetProjectPath = ":app" // Note that your module name may be different
    
       // Enable the benchmark to run separately from the app process
       experimentalProperties["android.experimental.self-instrumenting"] = true
       buildTypes {
           // Declare a build type (release) to match the target app's build type
           release {
               debuggable = true
           }
       }
  4. testImplementation 또는 androidTestImplementation이라는 이름의 모든 종속 항목을 implementation으로 변경합니다.
  5. Macrobenchmark 라이브러리에 대한 종속 항목을 추가합니다.
    • implementation 'androidx.benchmark:benchmark-macro-junit4:1.1.0-alpha11'
  6. android {} 블록 뒤, dependencies {} 블록 앞에 다음을 추가합니다.
  7.    androidComponents {
          beforeVariants(selector().all()) {
              // Enable only the benchmark buildType, since we only want to measure
              // release-like build performance (should match app buildType)
              enabled = buildType == 'benchmark'
          }
       }

디렉터리 구조 단순화

com.android.test 모듈에는 모든 테스트에 사용할 수 있는 소스 디렉터리가 한 개만 있습니다. src/testsrc/androidTest를 포함한 다른 소스 디렉터리는 사용하지 않으므로 삭제합니다.

참고용 샘플 Macrobenchmark 모듈을 확인하세요.

매크로 벤치마크 만들기

앱의 패키지 이름을 입력하여 그 모듈의 새 테스트 클래스를 정의합니다.

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

    @Test
    fun startup() = benchmarkRule.measureRepeated(
        packageName = "mypackage.myapp",
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.COLD
    ) { // this = MacrobenchmarkScope
        pressHome()
        val intent = Intent()
        intent.setPackage("mypackage.myapp")
        intent.setAction("mypackage.myapp.myaction")
        startActivityAndWait(intent)
    }
}
   

Bumblebee

Android 스튜디오 Bumblebee Canary 3에서는 Macrobenchmark 모듈 설정을 간소화할 수 있는 템플릿이 제공됩니다.

새 모듈 추가

벤치마킹 모듈 템플릿이 샘플 시작 벤치마크를 포함해 앱 모듈에서 빌드한 앱을 측정할 수 있는 모듈을 프로젝트에서 자동으로 생성합니다.

모듈 템플릿을 사용하여 새 모듈을 만들려면 다음 단계를 따르세요.

  1. Android 스튜디오의 Project 패널에서 프로젝트 또는 모듈을 마우스 오른쪽 버튼으로 클릭하고 New > Module을 클릭합니다.

  2. Benchmark Module을 선택합니다.

    벤치마크 모듈 템플릿

  3. 새로운 macrobenchmark 모듈에서는 패키지 및 모듈 이름 외에도 대상 애플리케이션(벤치마킹할 앱)을 맞춤설정할 수 있습니다.

  4. Finish를 클릭합니다.

앱 설정

앱(매크로 벤치마크의 타겟이라고 함)을 벤치마크하려면 앱이 프로파일링 가능해야 합니다. 그래야 자세한 트레이스 정보 읽기를 사용 설정할 수 있습니다. 이는 앱의 AndroidManifest.xml에 있는 <application> 태그에서 가능합니다.

<application ... >
    <!-- Profileable to enable Macrobenchmark profiling -->
    <!-- Suppress AndroidElementNotAllowed -->
    <profileable android:shell="true"/>
    ...
</application>

벤치마크된 앱을 가능한 한 사용자 환경과 최대한 비슷하게 구성합니다. 앱을 디버그가 불가능하도록 설정하고 가급적이면 축소를 설정합니다. 그러면 성능이 향상됩니다. 일반적으로 release 변형의 benchmark 복사본을 만들면 가능합니다. 복사본은 기능은 동일하지만 debug 키를 사용해 논리적으로 서명됩니다.

buildTypes {
    benchmark {
        // duplicate any release build type settings for measurement accuracy,
        // such as "minifyEnabled" and "proguardFiles" in this block

        debuggable false
        signingConfig signingConfigs.debug
    }
}

Gradle 동기화를 수행하고 왼쪽에 있는 Build Variants 패널을 열고 앱과 Macrobenchmark 모듈의 benchmark 변형을 모두 선택합니다. 그러면 벤치마크를 실행하여 앱의 정확한 변형을 빌드하고 테스트할 수 있습니다.

벤치마크 변형 선택

내부 활동에 관해 Macrobenchmark를 실행하려면 추가적인 단계가 필요합니다. exported=false 내부 활동을 벤치마크하려면 setupBlockMacrobenchmarkRule.measureRepeated()에 전달하여 벤치마크할 코드로 이동한 다음, measureBlock을 사용하여 측정할 스크롤 동작이나 실제 활동 시작을 호출합니다.

매크로 벤치마크 맞춤설정

CompilationMode

매크로 벤치마크에서는 앱의 컴파일 방법을 정의하는 CompilationMode를 지정할 수 있습니다.

기본적으로 벤치마크는 SpeedProfile로 실행됩니다. SpeedProfile은 프로필 기반 컴파일을 위한 프로파일링 데이터를 사용하여 측정 전에 벤치마크를 몇 번 반복하여 실행합니다. SpeedProfile은 이전에 시작되어 실행된 UI 코드의 성능 또는 앱을 설치한 저장소에 의해 사전 컴파일된 UI 코드의 성능을 시뮬레이션할 수 있습니다.

사전 컴파일 없이 설치 직후의 최저 성능을 시뮬레이션하려면 None을 전달합니다.

이 기능은 ART 컴파일 명령어를 기반으로 빌드됩니다. 각 벤치마크는 벤치마크 간의 간섭이 발생하지 않도록 시작 전에 프로필 데이터를 지웁니다.

시작하기

활동 시작을 실행하려면 미리 정의된 시작 모드(COLD, WARM 또는 HOT 중 하나)를 measureRepeated() 함수에 전달하면 됩니다. 이 매개변수는 활동 시작 방식과 테스트 시작 시의 프로세스 상태를 변경합니다.

시작 유형에 관한 자세한 내용은 Android vitals 시작 문서를 참고하세요.

스크롤 및 애니메이션

대부분의 Android UI 테스트와 달리 Macrobenchmark 테스트는 앱 자체와 분리된 프로세스에서 실행됩니다. 이 같은 분리는 앱 프로세스 종료, 셸 명령어를 사용한 앱 컴파일 같은 작업을 실행하기 위해 필요합니다.

UI Automator 라이브러리 또는 테스트 프로세스에서 타겟 애플리케이션을 제어할 수 있는 다른 메커니즘을 사용하여 앱을 구동할 수 있습니다. Espresso 또는 ActivityScenario 같은 접근 방식은 작동하지 않습니다. 이러한 방식은 앱과 공유된 프로세스에서 실행되기 때문입니다.

다음 예에서는 관련 리소스 ID를 사용하여 RecyclerView를 찾고 몇 번 아래로 스크롤합니다.

@Test
fun measureScroll() {
    benchmarkRule.measureRepeated(
        packageName = "mypackage.myapp",
        metrics = listOf(FrameTimingMetric()),
        compilationMode = compilationMode,
        iterations = 5,
        setupBlock = {
            // before starting to measure, navigate to the UI to be measured
            val intent = Intent()
            intent.action = ACTION
            startActivityAndWait(intent)
        }
    ) {
        val recycler = device.findObject(By.res("mypackage.myapp", "recycler_id"))
        // Set gesture margin to avoid triggering gesture nav
        // with input events from automation.
        recycler.setGestureMargin(device.displayWidth / 5)

        // Scroll down several times
        for (i in 1..10) {
            recycler.scroll(Direction.DOWN, 2f)
            device.waitForIdle()
        }
    }
}

테스트에 FrameTimingMetric이 지정되면 프레임 타이밍이 기록되고 상위 수준의 프레임 타이밍 분포 개요(50번째, 90번째, 95번째, 99번째 백분위수)로 보고됩니다.

벤치마크에서는 UI를 스크롤하지 않아도 됩니다. 대신 애니메이션 실행 같은 작업이 가능합니다. 또한 벤치마크에서는 UI Automator를 특별히 사용할 필요가 없습니다. Compose로 생성된 프레임을 포함하여 뷰 시스템에서 프레임이 생성되는 한 성능 측정항목이 수집됩니다. Espresso와 같은 프로세스 내 메커니즘은 작동하지 않습니다. 테스트 앱 프로세스에서 앱을 대신 구동해야 하기 때문입니다.

매크로 벤치마크 실행

Android 스튜디오 내에서 테스트를 실행하여 기기에서 앱 성능을 측정합니다. 에뮬레이터에서 최종 사용자 환경을 나타내는 성능 수치를 생성하지 않으므로 에뮬레이터가 아닌 실제 기기에서 테스트를 실행해야 합니다.

지속적 통합으로 벤치마크를 실행하고 모니터링하는 방법에 관한 자세한 내용은 CI의 벤치마킹 섹션을 참고하세요.

명령줄에서 connectedCheck 명령어를 실행하여 모든 벤치마크를 실행할 수도 있습니다.

$ ./gradlew :macrobenchmark:connectedCheck

구성 오류

앱이 잘못 구성(디버그 가능 또는 프로파일링 불가)되면 Macrobenchmark는 부정확하거나 불완전한 측정을 보고하는 대신 오류를 발생시킵니다. androidx.benchmark.suppressErrors 인수를 사용하여 이러한 오류를 억제할 수 있습니다.

에뮬레이터를 측정하려고 하거나 기기에 배터리가 부족한 경우에도 오류가 발생합니다. 이 경우 코어 가용성 및 클록 속도가 저하될 수 있기 때문입니다.

트레이스 검사

측정된 반복마다 별개의 시스템 트레이스가 캡처됩니다. 이 주제의 Jetpack Macrobenchmark 섹션 이미지에 나와 있는 것처럼 Test Results 창에 있는 링크 중 하나를 클릭하여 결과 트레이스를 열 수 있습니다 트레이스가 로드되면 분석할 프로세스를 선택하라는 메시지가 Android 스튜디오에 표시됩니다. 선택 항목은 타겟 앱 프로세스로 미리 채워집니다.

스튜디오 트레이스 프로세스 선택

트레이스 파일이 로드되면 스튜디오의 CPU 프로파일러 도구에 결과가 표시됩니다.

스튜디오 트레이스

수동으로 트레이스 파일에 액세스

이전 버전의 Android 스튜디오(2020.3.1 이전)를 사용하거나 Perfetto 도구를 사용하여 트레이스 파일을 분석하는 경우 추가 단계가 필요합니다.

먼저 기기에서 트레이스 파일을 가져옵니다.

# The following command pulls all files ending in .perfetto-trace from the directory
# hierarchy starting at the root /storage/emulated/0/Android.
$ adb shell find /storage/emulated/0/Android/ -name "*.perfetto-trace" \
    | tr -d '\r' | xargs -n1 adb pull

참고로, additionalTestOutputDir 인수로 파일 경로를 맞춤설정한다면 출력 파일 경로가 다를 수 있습니다. logcat에서 트레이스 경로 로그를 찾아 작성된 위치를 확인할 수 있습니다. 예:

I PerfettoCapture: Writing to /storage/emulated/0/Android/data/androidx.benchmark.integration.macrobenchmark.test/cache/TrivialStartupBenchmark_startup[mode=COLD]_iter002.perfetto-trace.

Gradle 명령줄(예: ./gradlew macrobenchmark:connectedCheck)을 대신 사용하여 테스트를 호출하는 경우 테스트 결과 파일을 호스트 시스템의 테스트 출력 디렉터리에 복사할 수 있습니다. 그렇게 하려면 프로젝트의 gradle.properties 파일에 다음 행을 추가하면 됩니다.

android.enableAdditionalTestOutput=true

테스트 실행의 결과 파일이 다음과 같이 프로젝트의 빌드 디렉터리에 표시됩니다.

build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/<device-name>/TrivialStartupBenchmark_startup[mode=COLD]_iter002.perfetto-trace

호스트 시스템에 트레이스 파일이 있으면 Android 스튜디오에서 File > Open 메뉴로 트레이스 파일을 열 수 있습니다. 그러면 이전 섹션에 표시된 프로파일러 도구 뷰가 표시됩니다.

Perfetto 도구를 대신 사용할 수도 있습니다. Perfetto는 트레이스 중에 기기에서 발생한 모든 프로세스를 검사하는 반면, Android 스튜디오의 CPU 프로파일러는 검사를 단일 프로세스로 제한합니다.

맞춤 이벤트로 트레이스 데이터 개선

맞춤 트레이스 이벤트로 애플리케이션을 계측하는 것이 유용할 수 있습니다. 트레이스 보고서의 나머지 부분에 표시되는 맞춤 트레이스 이벤트는 앱에 특정된 문제를 파악하는 데 도움이 될 수 있습니다. 맞춤 트레이스 이벤트를 만드는 방법에 관한 자세한 내용은 맞춤 이벤트 정의 가이드를 참고하세요.

CI에서의 벤치마킹

Gradle 없이 CI에서 테스트를 실행하거나, 다른 빌드 시스템을 사용하는 경우에는 로컬에서 테스트를 실행하는 것이 일반적입니다. 이 섹션에서는 런타임 시 CI 사용을 위해 Macrobenchmark를 구성하는 방법을 설명합니다.

결과 파일: JSON 및 트레이스

Macrobenchmark는 JSON 파일 한 개와 트레이스 파일 여러 개를 출력합니다. 각 MacrobenchmarkRule.measureRepeated 루프의 측정된 반복마다 하나씩 생기는 것입니다.

런타임 시 다음과 같은 계측 인수를 전달하여 이러한 파일의 작성 위치를 정의할 수 있습니다.

-e additionalTestOutputDir "device_path_you_can_write_to"

편의상 /sdcard/에 경로를 지정할 수 있습니다. 하지만 Macrobenchmark 모듈에서 requestLegacyExternalStoragetrue로 설정하여 범위 지정 저장소를 선택 해제해야 합니다.

<manifest ... >
  <application android:requestLegacyExternalStorage="true" ... >
    ...
  </application>
</manifest>

또는 테스트에서 범위 지정 저장소를 우회하기 위한 계측 인수를 전달합니다.

-e no-isolated-storage 1

JSON 샘플

다음은 단일 시작 벤치마크의 JSON 출력 샘플입니다.

{
    "context": {
        "build": {
            "device": "walleye",
            "fingerprint": "google/walleye/walleye:10/QQ3A.200805.001/6578210:userdebug/dev-keys",
            "model": "Pixel 2",
            "version": {
                "sdk": 29
            }
        },
        "cpuCoreCount": 8,
        "cpuLocked": false,
        "cpuMaxFreqHz": 2457600000,
        "memTotalBytes": 3834605568,
        "sustainedPerformanceModeEnabled": false
    },
    "benchmarks": [
        {
            "name": "startup",
            "params": {},
            "className": "androidx.benchmark.integration.macrobenchmark.SampleStartupBenchmark",
            "totalRunTimeNs": 77969052767,
            "metrics": {
                "startupMs": {
                    "minimum": 228,
                    "maximum": 283,
                    "median": 242,
                    "runs": [
                        238,
                        283,
                        256,
                        228,
                        242
                    ]
                }
            },
            "warmupIterations": 3,
            "repeatIterations": 5,
            "thermalThrottleSleepSeconds": 0
        }
    ]
}

추가 리소스

샘플 프로젝트는 GitHub에서 Android/performance-samples 저장소의 일부로 사용할 수 있습니다.

성능 회귀 감지 방법에 관한 안내는 CI의 벤치마크로 회귀 방지를 참고하세요.

의견

Jetpack Macrobenchmark에 관한 문제를 보고하거나 기능 요청을 제출하려면 공개 Issue Tracker를 참고하세요.