TMG-Skia Cache Miss 3.0-多应用Shader共享
1、背景
基于Skia 2D渲染引擎的Android HWUI渲染,不同应用的渲染过程是基本一致的。许多渲染场景会在多个应用中被反复使用,因此就会存在相同shader在多个应用中被重复编译,因此这就导致了:
- 占用编译时间
- 占用内存空间
基于以上问题,我们调研了Top50应用启动阶段时的Shader: Top50 shader复用调研
top5应用:全部通用shader 13个
top10应用:全部通用shader 6个
top20应用:全部通用shader 4个
| 100% | 80% | 60% | total | |
|---|---|---|---|---|
| Top5 | 13 | 15 | 20 | 15~29 |
| Top10 | 6 | 12 | 14 | 15~31 |
| Top20 | 4 | 9 | 11 | 15~48 |
说明:以Top5为例,100%代表被所有5个应用都共享的shader个数,80%代表被至少4个应用共享的shader个数,60%代表被至少3个应用共享的shader个数。
2、前期成果
最初的1.0预置缓存方案,针对第三方Top7应用,在Rom打包阶段进行了Shader的二进制预缓存,从而有效地消除了应用运行时的Cache Miss,提高应用冷启动效率和滑动流畅度。
2.0方案则主要针对home、systemui和personalassistant这些系统应用,采用用预置Shader SKSL源码的方式,实现了应用启动阶段的预编译,进而避免了因GPU驱动升级造成的Cache失效问题,有效的解决了诸如相机等系统应用在首次启动和退出时的卡顿问题。
TMG-Skia cache miss 2.0预制缓存优化方案
优化前
http://minio.898311.xyz:8900/blogfile/17407265333243.mp4
优化后
http://minio.898311.xyz:8900/blogfile/17407265943445.mp4
| 优化开启 | 优化关闭 | |||||||
|---|---|---|---|---|---|---|---|---|
| case | 分数 | 丢帧率 | 客观丢帧率 | 严重丢帧率 | 分数 | 丢帧率 | 客观丢帧率 | 严重丢帧率 |
| 相机 | 98.06 | 1.94% | 0.00% | 0.00% | 0 | 42.00% | 37.00% | 37.00% |
| 浏览器 | 77.5 | 4.85% | 2.91% | 0.00% | 0 | 20.79% | 14.85% | 8.91% |
| 短信 | 93.15 | 3.85% | 0.00% | 0.00% | 0 | 13.86% | 9.90% | 9.90% |
| 电话 | 100 | 0.00% | 0.00% | 0.00% | 93.97 | 3.03% | 0.00% | 0.00% |
| 笔记 | 100 | 0.00% | 0.00% | 0.00% | 76.47 | 5.15% | 3.09% | 0.00% |
| 手机管家 | 93.88 | 3.12% | 0.00% | 0.00% | 92 | 5.00% | 0.00% | 0.00% |
| 相册 | 94.98 | 2.02% | 0.00% | 0.00% | 93.94 | 3.06% | 0.00% | 0.00% |
| 设置 | 93.04 | 3.96% | 0.00% | 0.00% | 98.04 | 1.96% | 0.00% | 0.00% |
| 应用商店 | 100 | 0.00% | 0.00% | 0.00% | 100 | 0.00% | 0.00% | 0.00% |
| 游戏中心 | 100 | 0.00% | 0.00% | 0.00% | 100 | 0.00% | 0.00% | 0.00% |
| 小米视频 | 100 | 0.00% | 0.00% | 0.00% | 100 | 0.00% | 0.00% | 0.00% |
| 小米商城 | 94.96 | 2.04% | 0.00% | 0.00% | 94.96 | 2.04% | 0.00% | 0.00% |
| 平均值 | 95.46 | 0.01815 | 0.002425 | 0 | 70.78 | 0.080741667 | 0.054033333 | 0.046508333 |
可以看到针对首次启动时相机、浏览器和短信的优化效果是非常明显的,基本完全消除了首次启动时因cache miss导致的严重卡顿问题。同时相比于1.0方案,只需要在开发阶段预制一次SKSL缓存即可,有效的减少了后期适配和维护的人力成本。当前已落地N18、N16T、O81和O82等机型。
3、方案设计
2.0方案基本解决了用户最为关注的诸如相机等应用首次启动和退出时时的严重卡顿问题,但该方案存在的问题是需要应用启动阶段做预编译,该过程比较耗时,对于三方应用启动将是不可接受的,因此当前只针对home、systemui和personalassistant做了优化,由于这些系统应用的启动是在黑屏阶段完成的,因此可以避免感官上对用户使用造成影响。

综上,为了进一步改进当前预编译方案,同时也为了更好的优化三方应用的Shader编译过程,我们规划了3.0方案,主要包括以下两点:
- 将Shader预编译从应用进程剥离,在应用启动前完成预编译,减少对应用启动的影响
- 建立跨应用进程Shader共享机制,减少Shader编译时间和磁盘空间占用

3.1 统一预编译
统一预编译主要基于以下两点原因:
- 在应用启动阶段做预编译会占用应用的启动时间,从而影响应用的上屏显示
- 多应用的Shader共享要求在所有应用启动前完成共享Shader的预编译
综上,3.0方案中将在SystemServer系统启动阶段,将Rom开发阶段得到的SKSL Shader源码编译为二进制文件,这里编译得到的二进制文件保存了programID和inputs信息,进一步的会在Shader实际使用时加上uniform,samplser,attribute等信息,进而提供给GPU使用。

这里会涉及到预编译产物在预编译进程和应用进程间的共享,当前的处理方法是将预编译产物做二进制导出,应用启动时直接读取前面的预编译产物。
为了统一管理和避免每次启动都做预编译,预编译进程会检查如下几个信息:
- GPU Version:代表当前GPU驱动是否有更新
- Cache File Size:代表当前应用的SKSL Cache文件是否更新
- 云控Property
3.2 Shader共享
Shadercache是以Key-Value的形式保存的,程序运行过程中使用Shader的的缓存和查询都依赖于Key值:
//保存shader cahce
this->gpu()->getContext()->priv().getPersistentCache()->store(*key, *data, description);
//查询Shader cache
builder.fCached = persistentCache->load(*key);应用在绘帧时先shadercache::load,load为空,进入cache_miss后会进行shader的创建、编译、链接等操作后会将此次编译的shadercache进行store,下次使用时load到会省去shader创建、编译、链接等操作,即空间换时间,提升性能。这边可以看出应用的store和load都是依据key缓存键。不同程序间key缓存键一致,那么load就可以进行复用。
Key是用于唯一标识着色器程序的字符串或二进制数据,缓存键的生成依赖于描述着色器程序的所有信息。
sk_sp<SkData> key = SkData::MakeWithoutCopy(desc.asKey(), desc.keyLength());其中的desc对应于GrProgramDesc,其依赖于GrProgramInfo和GrCaps:
- GrProgramInfo:包含了生成着色器程序所需的详细信息(着色器信息、渲染目标、渲染管线、几何处理器配置还有程序自定义的模板测试等信息)
- GrCaps:提供了当前图形硬件的能力信息(当前硬件支持的着色器版本和特性、纹理格式和类型和渲染目标特性纹理最大尺寸、渲染目标数量)
skia会使用GrProgramDesc生成唯一的Key,这个Key会用来查询是否已经存在唯一的着色器程序,如果已经存在唯一的着色器程序则直接重用,没有则会利用GrProgramDesc包含的信息会创建新的着色器程序。Key的保存形式是一组整型数,其组成部分是几何处理、片段处理、混合处理、通道调制模式、顶点是否对齐到像素中心、图形程序的原始类型以及部分后续添加的数据,其形式如下:
20541_5242880_939786264_4294705152_1548222463_939786272_4294705200_4294967295_262143_0_7602176_4294967294_2057149985_33_3905941544_0_524288_147324928_794014660_4_0_2682278169_465_134217728_1476395008_8388625_436207616_1640450_0_30408704_268435456_2147483648_400_0_1758279824_111_33_66_132_0_1758279937_111_596455892_1852072096_1758280288_111_2273552336_3182192_1_0_1758289920_111_1165742392_3019898991_1169919808_3019898991_1276059648_3019898991_1165568992_3019898991_1758280256_111_1758280680_111_0_0_1170040128_3019898991_1758280193_111_0_3943664_1_0_0_0_224_0_596455892_1852072096_1758280336_111_2273823164_5404272_1758289920_111_1165694976_3019898991_0_0_1758280680_111_0_0_1758281048_111_1758280624_111_1758281056_111_1274591232_3019898991_1170040128_3019898991_1_111_596455892_1852072096_536870912_536870912_596455892_1852072096_536870912_536870912_1165568768_3019898991_1758280384_111_2273058176_7195120_1165860048_3019898991_400_0_1165860448_3019898991_4_0_1_0_1758289920_111
当不同的应用程序在使用相同的着色器源代码、编译参数和渲染状态,生存的缓存键可以是相同的。所以我们只要在不同的应用程序中找到相同的着色器源代码,当编译参数和渲染状态一致,我们可以达成应用间复用相同的着色器程序。
shader复用主要基于两点:
- 减少shader code编译时间
- 减少预置shader cache时应用间相同shader编译生成cache占用的磁盘空间
使用多个FileBlobCache协调细分使用shadercache复用

4、收益
- 统一预编译:home、systemui和personalassistant的预编译时长(ms)

- Shader复用:目前针对top20 应用,内置10个通用shader,验证其内存和冷启动时间:
- Disk总计减少应用原数据总值的28%
- 应用启动时间:平均优化时长61.49ms
| TOP APP | Disk大小 | 冷启动时间 | 复用/总值 | ||||
|---|---|---|---|---|---|---|---|
| 优化前 | 优化后 | ROM占比 | 优化前 | 优化后 | 优化时长 | ||
| 微信 | 29212 | 11604 | 60.28% | 270.33 | 272 | -1.67 | 7/15 |
| 抖音 | 41916 | 22400 | 46.56% | 1077.67 | 1067.67 | 10 | 8/18 |
| 快手 | 72640 | 50568 | 30.39% | 780.5 | 633 | 147.5 | 9/29 |
| B站 | 76036 | 51580 | 32.16% | 1066.33 | 837 | 229.33 | 10/29 |
| 头条 | 50940 | 33684 | 33.88% | 699.67 | 599.67 | 100 | 7/20 |
| 66496 | 51460 | 22.61% | 466 | 421 | 45 | 6/24 | |
| 拼多多 | 43080 | 27224 | 36.81% | 589.67 | 545 | 44.67 | 7/17 |
| 百度 | 67176 | 51320 | 23.60% | 454.67 | 398.67 | 56 | 7/27 |
| 西瓜视频 | 37412 | 17896 | 52.17% | 695.67 | 561.33 | 134.33 | 8/16 |
| 腾讯视频 | 77232 | 61400 | 20.50% | 688 | 708.33 | -20.33 | 7/31 |
| 淘宝 | 67872 | 48356 | 28.75% | 913.67 | 808.67 | 105 | 8/26 |
| 爱奇艺 | 76268 | 54196 | 28.94% | 557.67 | 607.2 | -49.53 | 9/39 |
| UC浏览器 | 72016 | 50116 | 30.41% | 1080.33 | 983.33 | 97 | 9/30 |
| QQ浏览器 | 76640 | 57124 | 25.46% | 512 | 515 | -3 | 8/29 |
| 优酷视频 | 81572 | 61744 | 24.31% | 600.33 | 618.33 | -18 | 8/31 |
| 微博 | 55016 | 35500 | 35.47% | 828 | 668 | 160 | 8/21 |
| 支付宝 | 72428 | 55132 | 23.88% | 329 | 320 | 9 | 7/48 |
5、风险评估
- 预置缓存会占用一定的内存空间。以10个shader在O16U平台复用的情况下,占内存24.4kb。
- Shader 的识别和编译会有额外的负载。以B站为例,在10个shader复用的情况下,shader查找时间为35us,shader加载时间为342us。
- Vulkan的切换会导致整体功能失效。目前为止Android V版本还未全面切Vulkan,依然使用OpenGLES。
6、落地计划
预计落地O8/O9

7、团队成员
| 姓名 | 职责 | 备注 |
|---|---|---|
| 叶男 | 三方Top应用跨进程shader复用 | |
| 刘飞 | Home、Systemui和Personalassistant的统一预编译 |