Jetpack 基准库简介

使用 Jetpack 基准库,您可以从 Android Studio 中快速对基于 Kotlin 或 Java 的代码进行基准化分析。该库会处理预热,衡量代码性能和分配计数,并将基准化分析结果的更多详细信息输出到 Android Studio 控制台JSON 文件

用例包括滚动 RecyclerView、执行数据库查询,以及衡量代码中任何运行速度慢而您希望提高其运行速度的部分。

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

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

快速入门

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

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

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

    project_root/module_dir/build.gradle

    Groovy

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

    Kotlin

    dependencies {
        androidTestImplementation("androidx.benchmark:benchmark-junit4:1.0.0")
    }
    

    Groovy

    dependencies {
        androidTestImplementation "androidx.benchmark:benchmark-junit4:1.0.0"
    }
    
  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 benchmarkSomeWork() {
            final BenchmarkState state = benchmarkRule.getState();
            while (state.keepRunning()) {
                doSomeWork();
            }
        }
    }
    

何时进行基准化分析

建议您在编写基准之前先剖析代码性能。这有助于找到开销较高、值得优化的操作,而且还可以显示操作运行时发生的情况,从而揭示操作运行缓慢的原因。例如,具体原因可能包括运行操作所用的线程优先级较低、因访问磁盘而休眠,或意外调用高开销函数(例如位图解码)。

通过 TraceCompat API(或 -ktx 封装容器)添加自定义跟踪数据点,可以在 Android Studio CPU 性能剖析器Systrace 中进行查看:

Kotlin

fun proccessBitmap(bitmap: Bitmap): Bitmap {
    trace("processBitmap") {
        // perform work on bitmap...
    }
}

Java

public Bitmap processBitmap(Bitmap bitmaptest) {
    TraceCompat.beginSection("processBitmap");
    try {
        // perform work on bitmap...
    } finally {
        TraceCompat.endSection();
    }
}

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

基准化分析最适用于应用中频繁运行的 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<Context>()
        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<Context>();
        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 基准报告从设备复制到主机的操作。

如果您使用的是 AGP 3.5 或更低版本且以 API 级别 29 或更高版本为目标平台,就需要在基准的 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")
    }
}

在基准模块的 build.gradle 中应用该插件:

Groovy

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

Kotlin

plugins {
    id("com.android.app")
    id("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 中按如下方式设置:

Groovy

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

Kotlin

android {
    defaultConfig {
        testInstrumentationRunnerArguments(mapOf(
            "androidx.benchmark.suppressErrors" to "DEBUGGABLE"
        ))
    }
}

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

性能剖析

您可以剖析基准,以调查所测量的代码运行缓慢的原因。

请将以下内容添加到基准模块的 build.gradle 文件中:

Groovy

android {
    defaultConfig {
        // must be one of: none, sampled, or method
        testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'sampled'
    }
}

Kotlin

android {
    defaultConfig {
        // must be one of: none, sampled, or method
        testInstrumentationRunnerArgument = mapOf(
            "androidx.benchmark.profiling.mode", "sampled"
        )
    }
}

当您运行基准时,系统会将输出 .trace 文件连同 JSON 结果一起复制到主机的目录下。在 Android Studio 中点击 File > Open 打开此文件,以在 CPU 性能剖析器中检查性能剖析结果。

方法跟踪

使用方法跟踪功能时,基准会在捕获方法跟踪记录之前预热,记录基准调用的每种方法。请注意,捕获每种方法进入/退出操作的开销将显著影响性能结果。

方法性能剖析

使用方法采样时,基准会在预热完成后以 100 微秒的间隔对堆栈轨迹进行采样。基准将以比平时更长的时间进行循环,以确保捕获足够的样本,从而获得有意义的结果,并且衡量结果会受到一定程度的影响。

如需详细了解如何使用方法跟踪和采样,请参阅 CPU 性能剖析配置

基准化分析示例

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

示例项目包括:

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

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

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

其他资源

如需详细了解基准化分析,请参阅下面列出的其他资源。

博客

提供反馈

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