为什么需要 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