Baseline Profiles 在 Compose 中的应用

2022-11-30 14:48:55 浏览数 (1)

为什么需要 Baseline Profiles ?

应用启动运行时解释执行(JIT)代码,并将热点代码翻译为机器代码,此过程需要时间,因此会降低性能,那 Baseline Profiles 是做什么的呢?就是将原本运行时解释执行的热点代码提前准备好,在应用处于空闲时,可以直接将热点代码编译成机器代码,这时在下次运行时可被直接执行,省去了运行时解释代码的过程,为应用启动提升性能。

如下是官方使用 Baseline Profiles 在应用启动上提升的百分比,来自 《 Performance best practices for Jetpack Compose[1]》:

官方提供的数据非常吸引人,但具体落地到项目中是否能和 Google 提供的数据差不还需要自己自测。

Baseline Profiles 流程图

从编写、编译到安装的整体流程图

本流程图更专注于 Baseline Profiles 在开发层面的执行过程,像官方罗列的 Cloud 部分,本文不阐述。

接下来,我们来讲述这三个部分。

1、编写时

在官方文档中有介绍如何通过 Macrobenchmark 来获取自己项目的 baselie-profile.txt [2],但需要注意的是,该方式需要准备一台 Android 9 及其以上 root 过的手机。

既然是文本文件的话,那是不是我们自己也可以手动去写?答案是的,但不是很建议,因为项目代码一直在变,手动录入的话会导致维护困难,官方有列出 baseline-profile 的格式:

具体的规则可以查看官方的 Rule syntax[3] 章节,接下来,我们需要探索下 Compose 项目中,这个文件是放置在哪的。

稍微改了下 checkPlugin[4] 插件,只打印 aar 中有含有 baseline-prof.txt 文件的依赖:

代码语言:javascript复制
list.forEach { path ->
    ...
    while (zipInputStream.nextEntry.also { ze = it } != null) {
        if (ze!!.isDirectory) {
            continue
        }
        // 打印 aar 里面含 baseline-prof.txt 的依赖
      .if(ze!!.name == "baseline-prof.txt"){
            println(path " --> name=" ze!!.name)
        }
    }
    zipInputStream.closeEntry()
    input.close()
}

打印结果如下:

从结果上看,Compose 相关的依赖基本都含有一份自己的 baseline-profile.txt 文件,我们看下 compose.ui 的 baseline-profile.txt,看到了熟悉的 AndroidComposeView:

baseline-profile.txt 在模块目录中的结构如下,与 AndroidManifest.xml 同级:

2、编译时

在编译阶段,AGP 会将所有的依赖的 baseline-profile.txt 合并成一个文件,然后编译输出 baseline.prof 文件

从 AGP 7.0 源码来看,最主要的两个 task 为 CompileArtProfileTask 和 MergeArtProfileTask,我们对这两个 task 简单的跟踪下:

这个地方可以注意一下这个判断,如果不想启用 ArtProfile task 的话,可以设置 android.enableArtProfiles 为 false,或者 deuggable 设置成 true。

MergeArtProfileTask

获取所有模块的 baseline-profile.txt 文件:

然后将所有模块的 baseline-profile.txt 内容进行合并:

合并很简单,就是将所有的文件内容汇总写入到 outputFile 里,我们来看下这个最终输出的文件:

CompileArtProfileTask:

编译解析合并后的 baseline-profile.txt 中的规则,具体可以看该 Task 下的 HumanReadableProfile 类,将提取出来的规则与 DexFile 比对,找出匹配的访问标识、 profile method 和 profile class 存储到 profileData 中, 最终用 ArtProfile 包裹起来 save 到 baseline.prof 中 ,这个地方的写入是有格式的(例如魔数),具体可以看 ArtProfileSerializer,下面贴个图:

所以,如果 baseline-profile.txt 文件描述的类和方法与实际的 class 不一致的话,则不会参与最终的 profile 优化,因为会被剔除掉,这个需要注意。

baseline.prof 的产物如下:

最终打包的时候,会将该文件添加到 assets/dexopt 目录下参与打包,打包效果:

如何检查自己的 AGP 是否支持 Baseline Profiles 的打包呢(前提是打非 debuggable 的包)?

  • 检查 gradle task 的输出,是否有如上两个 task,例如 app:mergeReleaseArtProfile 和 :app:compileReleaseArtProfile

这个地方需要注意,在我之前的文章中有介绍 AGP 4.2.x 版本是支持正式版 Compose 的,但在看 4.2.x 版本源码的时候,是没有 ART Profiles 相关的 task 的,这也说明,在 AGP 4.2.x 生成不了 baseline.prof 文件,继而享受不到 Baseline Profile 带来的优化。

不过也有解决办法,那就是在高版本的 AGP 中打包,然后将 apk 里 assets 下的 baseline.prof 文件提取出来,放入到自己项目即可。

3、运行时

项目运行时,会通过 profileinstaller 模块将 assets/dexopt/baseline.prof 文件写入到 /data/misc/profiles/cur/0/包名/primary.prof 下,profileinstaller 模块在哪引入的呢?我们来打印下依赖树:

profileinstaller 依赖被 compose.ui 模块给带进去了,并且 profileinstaller 还把 startup 库也给带进去了,来看下最终 apk 包的清单文件:

应用启动时,通过 startup 库将 ProfileInstallerInitializer 启动起来,我们可以简单看下 ProfileInstallerInitializer 类:

  • Android 7.0 以下不支持,直接返回不处理
  • 为了避免因为写入 baseline.prof 影响到应用的启动,固注册在第一帧之后再延迟 5s 左右执行写入操作

看下写入的操作:

  • 判断是否强制写入或是已经写入过,强制写入默认是 false,如果已经写入则不处理
  • transcodeAndWrite 在子线程中开始执行写入操作

profileinstaller 的源码非常简单,总共才 10 个类,但这里面需要注意一些细节:

  • 在 transcodeAndWrite 方法内有判断 /data/misc/profiles/cur/0/包名 路径是否可写,如果各厂商把这块设置成不可写的话,则无法写入
  • profileinstaller 是在应用安装完成第一次启动的时候会做写入操作,在打开应用尚未写入完成时,这个时候是无法享受 AOT 带来的优化,所以,这次启动数据会有一定的劣化,不过,只有第一次安装打开时才会,尚可忽略

衡量 Baseline Profiles 带来的提升

我们需要测量 Compose 项目有无 Baseline Profiles 加持时性能的对比,默认我们的 compose 项目就有了 Profiles 加持,我们需要移除 Profiles 能力来测试启动性能,有两种办法可以解决:

1、从 baseline.prof 入手

我们只需要解决不将 baseline.prof 文件打入 apk 即可,或是说即使打入进去了,不将 profileinstaller 依赖打进 apk 也可以,这样的话,在运行期间就不会将 prof 文件写入到本地。最简单的方式就是 gradle.properties 中配置 ArtProfiles 为 false:

代码语言:javascript复制
android.enableArtProfiles=false

该值对应到上文编译章节开头描述的 variant.service.projectOptions[BooleanOption.ENABLE_ART_PROFILES] 判断,该值默认是个 true,我们设置 false 即可不启用 ArtProfile task,在产出包之后,通过 adb 命令即可获得启动数据:

adb shell am start -W 包名/启动类

2、MacroBenchmark 测试

上面的测试可能会比较麻烦,可以利用 MacroBenchmark 来自动化测试有无 Baseline Profiles 加持的启动数据,单元测试如下:

测量结果:

测试 10 组数据,中位数的值比没有 Profiles 加持快 30ms 左右

这里就贴一个样本吧,因为在多次的测试过程中,大部分都是有 Profiles 加持的情况下比没有的快,但也遇到一次奇葩的时候:

测试 10 组数据,中位数的值比没有 Profiles 加持慢了 70ms 左右

这让我对 MacroBenchmark 保持了怀疑的态度,后面有时间,等我用 adb 的方式测一下吧。

贴个友链:

  • Google I/O : 使用 Baseline Profiles 优化启动性能
  • Android 强推的 Baseline Profiles 国内能用吗?我找 Google 工程师求证了!

参考资料

[1]

Performance best practices for Jetpack Compose: https://www.youtube.com/watch?v=EOQB8PTLkpY&ab_channel=AndroidDevelopers

[2]

baselie-profile.txt : https://developer.android.com/topic/performance/baselineprofiles#creating-profile-rules

[3]

Rule syntax: https://developer.android.com/topic/performance/baselineprofiles#rule_syntax

[4]

checkPlugin: https://github.com/MRwangqi/pluginDemo

0 人点赞