对应用代码进行基准化分析

使用 Jetpack 基准库,您可以在 Android Studio 中快速对 Kotlin 或 Java 代码进行基准化分析。该库会处理预热,衡量代码性能,并将基准化分析结果输出到 Android Studio 控制台。

用例包括滚动 RecyclerView、膨胀重要的 View 层次结构以及执行数据库查询。

您可以在持续集成 (CI) 环境中使用基准库,如在持续集成环境中运行基准化分析所述。

如果您尚未在要进行基准化分析的项目中采用 AndroidX,请参阅使用 Android Studio 迁移现有项目

快速入门

本节提供的简要步骤可帮助您尝试基准化分析,而无需将代码移到模块中。由于这些步骤涉及停用调试功能以获得准确的性能结果,因此您不会将更改提交至源代码控制系统中,但如果您想要执行一次性测量,这样做仍然很有用。

要快速执行一次性基准化分析,请执行以下操作:

  1. 将基准库添加到模块的 build.gradle 文件中:

    project_root/module_dir/build.gradle

        dependencies {
            androidTestImplementation "androidx.benchmark:benchmark-junit4:1.0.0-beta01"
        }
        
  2. 要在测试清单中停用调试功能,请更新 <application> 元素以暂时强制停用调试功能,如下所示:

    project_root/module_dir/src/androidTest/AndroidManifest.xml

    <!-- Important: disable debuggable for accurate performance results -->
        <application
            android:debuggable="false"
            tools:ignore="HardcodedDebugMode"
            tools:replace="android:debuggable"/>
        
  3. 要添加基准,请在 androidTest 目录下的测试文件中添加 BenchmarkRule 的实例。要详细了解如何编写基准,请参阅编写基准

    以下代码段展示了如何将基准添加到 JUnit 测试中:

    Kotlin

        @RunWith(AndroidJUnit4::class)
        class MyBenchmark {
            @get:Rule
            val benchmarkRule = BenchmarkRule()
    
            @Test
            fun benchmarkSomeWork() = benchmarkRule.measureRepeated {
                doSomeWork()
            }
        }
        

    Java

        @RunWith(AndroidJUnit4.class)
        class MyBenchmark {
            @Rule
            public BenchmarkRule benchmarkRule = new BenchmarkRule();
    
            @Test
            public void myBenchmark() {
                final BenchmarkState state = benchmarkRule.getState();
                while (state.keepRunning()) {
                    doSomeWork();
                }
            }
        }
        

对哪些内容进行基准化分析

基准化分析对您的应用中频繁执行的 CPU 工作最有用。RecyclerView 滚动、数据转换/处理以及反复使用的代码段都是不错的示例。

其他类型的代码更难以通过基准化分析来衡量。由于基准循环运行,因此任何不经常运行或在多次调用时以不同方式执行的代码都可能不适合进行基准化分析。

缓存

尽量避免只衡量缓存。例如,自定义视图的布局基准可能只能衡量布局缓存的性能。为避免这种情况,您可以在每个循环中传递不同的布局参数。在其他情况下(例如在衡量文件系统性能时),这可能难以进行,因为操作系统会在循环中缓存文件系统。

不经常运行的代码

Android 运行时 (ART) 不太可能对只会在应用启动期间运行一次的代码进行 JIT 编译。因此,在循环运行此代码时对其进行基准化分析实际上并不能衡量此代码的性能。

对于此类代码,我们建议您在应用中对其进行跟踪性能剖析。请注意,这并不意味着您无法对启动路径中的代码进行基准化分析,而是应该选择那些会循环运行并且很可能采用 JIT 编译方式的代码。

完整的项目设置

要针对常规而非一次性的基准化分析进行设置,您需要将基准分离到专门的模块中。这样可确保其配置(例如将 debuggable 设置为 false)与常规测试分开。

为此,您需要完成以下任务:

  • 将您想要进行基准化分析的代码和资源放入库模块中(如果库模块中还没有这些代码和资源)。

  • 添加一个新的库模块来保存基准。

我们的示例提供了有关如何以这种方式设置项目的示例。

设置 Android Studio 属性

如果您使用的是 Android Studio 3.5,则必须手动设置 Android Studio 属性才能启用基准模块向导支持。Android Studio 3.6 或更高版本不需要手动设置。

要启用 Android Studio 基准化分析模板,请执行以下操作:

  1. 在 Android Studio 3.5 中,依次点击 Help > Edit Custom Properties。

  2. 将以下行添加到随即打开的文件中:

    npw.benchmark.template.module=true

  3. 保存并关闭该文件。

  4. 重启 Android Studio。

创建新模块

基准化分析模块模板会自动配置基准化分析设置。

要使用模块模板创建新模块,请执行以下操作:

  1. 右键点击您的项目或模块,然后依次选择 New > Module。

  2. 选择 Benchmark Module,然后点击 Next。

    图 1.基准模块

  3. 输入模块名称,选择语言,然后点击 Finish

    针对基准化分析进行了预先配置的模块现已创建完毕,其中添加了基准目录并将 debuggable 设置为 false

编写基准

基准是标准的插桩测试。要创建基准,请使用基准库提供的 BenchmarkRule 类。要对 Activity 进行基准化分析,请使用 ActivityTestRuleActivityScenarioRule。要对界面代码进行基准化分析,请使用 @UiThreadTest

以下代码展示了一个示例基准:

Kotlin

    @RunWith(AndroidJUnit4::class)
    class ViewBenchmark {
        @get:Rule
        val benchmarkRule = BenchmarkRule()

        @Test
        fun simpleViewInflate() {
            val context = ApplicationProvider.getApplicationContext()
            val inflater = LayoutInflater.from(context)
            val root = FrameLayout(context)

            benchmarkRule.keepRunning {
                inflater.inflate(R.layout.test_simple_view, root, false)
            }
        }
    }
    

Java

    @RunWith(AndroidJUnit4::class)
    public class ViewBenchmark {
        @Rule
        public BenchmarkRule benchmarkRule = new BenchmarkRule();

        @Test
        public void simpleViewInflate() {
            Context context = ApplicationProvider.getApplicationContext();
            final BenchmarkState state = benchmarkRule.getState();
            LayoutInflater inflater = LayoutInflater.from(context);
            FrameLayout root = new FrameLayout(context);

            while (state.keepRunning()) {
                inflater.inflate(R.layout.test_simple_view, root, false);
            }
        }
    }
    

您可以对不想测量的代码段停用计时功能,如以下代码示例所示:

Kotlin

    @Test
    fun bitmapProcessing() = benchmarkRule.measureRepeated {
        val input: Bitmap = runWithTimingDisabled { constructTestBitmap() }
        processBitmap(input)
    }
    

Java

    @Test
    public void bitmapProcessing() {
        final BenchmarkState state = benchmarkRule.getState();
        while (state.keepRunning()) {
            state.pauseTiming();
            Bitmap input = constructTestBitmap();
            state.resumeTiming();

            processBitmap(input);
        }
    }
    

如需了解如何运行基准,请参阅运行基准

运行基准

在 Android Studio 中,像运行任何 Android @Test 一样运行基准。在 Android Studio 3.4 及更高版本上,您可以看到发送到控制台的输出。

要运行基准,请在模块中转到 benchmark/src/androidTest,然后按 Control+Shift+F10(在 Mac 上,按 Command+Shift+R)。基准的结果会显示在控制台中,如图 2 所示:

对 Android Studio 中的输出进行基准化分析

图 2.对 Android Studio 中的输出进行基准化分析

从命令行运行常规 connectedCheck

./gradlew benchmark:connectedCheck
    

收集数据

包含额外指标和设备信息的完整基准报告以 JSON 格式提供。

androidx.benchmark Gradle 插件默认会启用 JSON 输出。要在非 Gradle 编译环境中手动启用 JSON 输出,需要传递一个插桩参数 androidx.benchmark.output.enable 并将其设置为 true

以下是使用 adb shell am instrument 命令的示例:

    adb shell am instrument -w -e "androidx.benchmark.output.enable" "true" com.android.foo/androidx.benchmark.junit4.AndroidBenchmarkRunner
    

默认情况下,JSON 报告会写入设备上磁盘的测试 APK 的外部共享“下载内容”文件夹中,该文件夹通常位于以下位置:

    /storage/emulated/0/Download/app_id-benchmarkData.json
    

您可以使用插桩参数 additionalTestOutputDir 配置在设备上保存基准报告的位置。

    adb shell am instrument -w -e "androidx.benchmark.output.enable" "true" -e "additionalTestOutputDir" "/path_to_a_directory_on_device_test_has_write_permissions_to/" com.android.foo/androidx.benchmark.junit4.AndroidBenchmarkRunner
    

系统会将 JSON 报告从设备复制到主机。这些报告将写入主机上的以下位置:

    project_root/module/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/device_id/app_id-benchmarkData.json
    

Android Gradle 插件 3.6 及更高版本

使用 Gradle 从命令行运行基准时,使用 Android Gradle 插件 3.6 及更高版本的项目可以将以下标记添加到项目的 gradle.properties 文件中:

    android.enableAdditionalTestOutput=true
    

这样,实验性的 Android Gradle 插件功能就可以从搭载 API 16 及更高版本的设备中提取基准报告。

Android Gradle 插件 3.5 及更低版本

对于使用旧版 Android Gradle 插件的用户,androidx.benchmark Gradle 插件会处理将 JSON 基准报告从设备复制到主机的操作。

如果针对 API 级别 29 或更高级别使用 AGP 3.5 或更低版本,您需要在基准测试的 androidTest 目录中为 Android 清单添加一个标记,以启用旧版外部存储行为。要详细了解如何选择停用分区存储,请参阅选择停用过滤视图

    <manifest ... >
      <!-- This attribute is "false" by default on apps targeting Android 10. -->
      <application android:requestLegacyExternalStorage="true" ... >
        ...
      </application>
    </manifest>
    

时钟稳定性

移动设备上的时钟会动态地从高频状态(高性能)变为低频状态(以节省电量,或者在设备变热时)。这些变化的时钟可能会使您的基准数据变化幅度很大,因此基准库提供了解决此问题的方法。

锁定时钟(需要 root 权限)

锁定时钟是获得稳定性能的最佳方式。它可以确保时钟频率绝不会过高而导致设备过热,也不会过低而导致基准不能充分利用 CPU。虽然这是确保稳定性能的最佳方法,但大部分设备都不支持这样做,因为它需要 adb root 权限。

要锁定时钟,请将提供的帮助程序插件添加到主 build.gradle 文件的顶级项目类路径中:

buildscript {
        ...
        dependencies {
            ...
            classpath "androidx.benchmark:benchmark-gradle-plugin:1.0.0-beta01"
        }
    }
    

在您要进行基准化分析的模块中,于 build.gradle 中应用该插件:

apply plugin: com.android.app
    apply plugin: androidx.benchmark
    ...
    

这会将基准化分析 Gradle 任务(包括 ./gradlew lockClocks./gradlew unlockClocks)添加到您的项目中。借助这些任务可以使用 adb 锁定和解锁设备的 CPU。

如果 adb 可以检测到多个设备,请使用环境变量 ANDROID_SERIAL 指定应在哪个设备上执行 Gradle 任务:

    ANDROID_SERIAL=device-id-from-adb-devices ./gradlew lockClocks
    

持续性能模式

某些设备支持 Window.setSustainedPerformanceMode() 功能,该功能可让应用选择降低 CPU 频率上限。在受支持的设备上运行时,基准库会使用此 API 的组合并启动自己的 Activity,以防止出现温控降频并获得稳定的结果。

默认情况下,Gradle 插件设置的 testInstrumentationRunner 会启用此功能。如果您要使用自定义运行程序,可以创建 AndroidBenchmarkRunner 的子类并将其用作 testInstrumentationRunner

该运行程序会启动一个不透明的全屏 Activity,以确保基准在前台运行,并避免任何其他应用参与绘图。

自动暂停执行

如果既未使用时钟锁定也未使用持续性能模式,基准库会执行自动温控降频检测。启用后,将会定期运行内部基准,以确定设备温度何时达到了足以导致 CPU 性能降低的程度。当检测到 CPU 性能降低时,基准库会暂停执行以便设备冷却,然后重试当前的基准。

配置错误

基准库会检测以下条件是否得到满足,确保项目和环境设置达到发布性能:

  • Debuggable 已设为 false
  • 正在使用的是物理设备,而不是模拟器。
  • 如果设备启用了 root 权限,时钟已被锁定。
  • 设备的电池电量充足。

如果上述任一项检查失败,基准将抛出错误以避免不准确的测量结果。

要抑制这些显示为警告的错误,同时阻止它们抛出错误并中止基准,请将您要抑制的错误类型以逗号分隔列表的形式传递给插桩参数 androidx.benchmark.suppressErrors

    adb shell am instrument -w -e "androidx.benchmark.suppressErrors" "DEBUGGABLE" com.android.foo/androidx.benchmark.junit4.AndroidBenchmarkRunner
    

您可以在 Gradle 中按如下方式设置:

    android {
        defaultConfig {
            testInstrumentationRunnerArgument 'androidx.benchmark.suppressErrors', 'DEBUGGABLE'
        }
    }
    

请注意,抑制错误可让您在配置有误的状态下运行基准,但会导致系统刻意损坏基准的输出(具体方式为在测试名称前附加错误类型)。也就是说,如果通过上述抑制操作运行可调试基准,系统会在测试名称前附加 DEBUGGABLE_

基准化分析示例

以下项目中提供了示例基准代码:

示例项目包括:

  • BenchmarkSample:这是一个独立的示例,展示了如何使用基准模块来测量代码和界面。

  • PagingWithNetworkSample:Android 架构组件示例,展示了如何对 RecyclerView 性能进行基准化分析。

  • WorkManagerSample:Android 架构组件示例,展示了如何对 WorkManager 工作器进行基准化分析。

提供反馈

要在使用基准化分析时报告问题或提交功能请求,请参阅公开问题跟踪器