本文原作者: codelang,原文发布于: 扣浪
为什么需要 Baseline Profiles?
官方提供的数据非常吸引人,但具体落地到项目中是否能和 Google 提供的数据差不多还需要自测。
Baseline Profiles 流程图
本流程图更专注于 Baseline Profiles 在开发层面的执行过程,像官方罗列的 Cloud 部分,本文不阐述。
接下来,我们来讲述这三个部分。
编写时
在官方文档中有介绍如何通过 Macrobenchmark 来获取自己项目的 baselie-profile.txt,但需要注意的是,该方式需要准备一台 Android 9 及其以上 root 过的手机。
baselie-profile.txt
https://developer.android.google.cn/topic/performance/baselineprofiles#creating-profile-rules
Rule syntax
https://developer.android.google.cn/topic/performance/baselineprofiles#rule_syntax
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()}
checkPlugin
打印结果如下:
从结果上看,Compose 相关的依赖基本都含有一份自己的 baseline-profile.txt 文件,我们看下 compose.ui 的 baseline-profile.txt,看到了熟悉的 AndroidComposeView:
这个地方可以注意一下这个判断,如果不想启用 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 目录下参与打包,打包效果:
检查 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 文件提取出来,放入到自己项目即可。
运行时
项目运行时,会通过 profileinstaller 模块将 assets/dexopt/baseline.prof 文件写入到 /data/misc/profiles/cur/0/包名/primary.prof 下,profileinstaller 模块在哪引入的呢?我们来打印下依赖树:
应用启动时,通过 startup 库将 ProfileInstallerInitializer 启动起来,我们可以简单看下 ProfileInstallerInitializer 类:
Android 7.0 以下不支持,直接返回不处理;
为了避免因为写入 baseline.prof 影响到应用的启动,固注册在第一帧之后再延迟 5s 左右执行写入操作。
看下写入的操作:
判断是否强制写入或是已经写入过,强制写入默认是 false,如果已经写入则不处理;
transcodeAndWrite 在子线程中开始执行写入操作。
在 transcodeAndWrite 方法内有判断 /data/misc/profiles/cur/0/包名路径是否可写,如果各厂商把这块设置成不可写的话,则无法写入; profileinstaller 是在应用安装完成第一次启动的时候会做写入操作,在打开应用尚未写入完成时,这个时候是无法享受 AOT 带来的优化,所以,这次启动数据会有一定的劣化,不过,只有第一次安装打开时才会,尚可忽略。
衡量 Baseline Profiles 带来的提升
我们需要测量 Compose 项目有无 Baseline Profiles 加持时性能的对比,默认我们的 compose 项目就有了 Profiles 加持,我们需要移除 Profiles 能力来测试启动性能,有两种办法可以解决:
从 baseline.prof 入手
我们只需要解决不将 baseline.prof 文件打入 apk 即可,或是说即使打入进去了,不将 profileinstaller 依赖打进 apk 也可以,这样的话,在运行期间就不会将 prof 文件写入到本地。最简单的方式就是 gradle.properties 中配置 ArtProfiles 为 false:
android.enableArtProfiles=false该值对应到上文编译章节开头描述的 variant.service.projectOptions[BooleanOption.ENABLE_ART_PROFILES] 判断,该值默认是个 true,我们设置 false 即可不启用 ArtProfile task,在产出包之后,通过 adb 命令即可获得启动数据:
adb shell am start -W 包名/启动类
MacroBenchmark 测试
测量结果:
△ 测试 10 组数据,中位数的值比没有 Profiles 加持快 30ms 左右
这里就贴一个样本吧,因为在多次的测试过程中,大部分都是有 Profiles 加持的情况下比没有的快,但也遇到一次奇葩的时候:
△ 测试 10 组数据,中位数的值比没有 Profiles 加持慢了 70ms 左右
这让我对 MacroBenchmark 保持了怀疑的态度,后面有时间,等我用 adb 的方式测一下吧。
长按右侧二维码
查看更多开发者精彩分享
"开发者说·DTalk" 面向中国开发者们征集 Google 移动应用 (apps & games) 相关的产品/技术内容。欢迎大家前来分享您对移动应用的行业洞察或见解、移动开发过程中的心得或新发现、以及应用出海的实战经验总结和相关产品的使用反馈等。我们由衷地希望可以给这些出众的中国开发者们提供更好展现自己、充分发挥自己特长的平台。我们将通过大家的技术内容着重选出优秀案例进行谷歌开发技术专家 (GDE) 的推荐。
点击屏末 | 阅读原文 | 即刻报名参与 "开发者说·DTalk"