ViewCompose Performance
1. 文档定位
本文档是性能规范版,定义:
- 当前性能基线
- 性能门禁指标
- 设计与实现层约束
- 后续优化路线
历史长版分析见:
2. 当前性能基线(2026-07)
2.1 已建立能力
- viewcompose-benchmark 模块已接入,具备稳定测试入口。
- renderer 已具备节点级“是否重绑”判断能力(
rebound/skipped统计)。 - diagnostics 已有 render/layout 基础指标可观测能力。
- runtime 已切到
SlotTable Lite:RenderSession采用“首帧 compose + 节点组级增量 recompose”,未脏组复用VNode引用。 - 组级失效队列支持祖先合并去重(
InvalidationQueue),并对emit(spec/modifier)输入变化做脏标记,避免参数变化漏更新。 - patch pipeline 已支持 subtree skip(
SkipSubtree)并新增skippedSubtrees统计;previousVNode === nextVNode命中同引用快路径。 - 延迟 session 容器的 keyed diff 已切到
DiffUtil引擎(保留 key 缺失/重复 fallback)。 - framework 托管的
RecyclerView容器默认不共享RecycledViewPool且保留系统itemAnimator;可按需通过容器参数reusePolicy/motionPolicy对单个容器启用共享池与动画器策略。 - renderer 内部尺寸换算统一走
viewcompose-renderer/view/DimensionUtils.kt,避免容器层重复定义density/dpToPx带来的行为漂移。 - runtime 状态系统已升级为
SnapshotMutationPolicy + MVCC + MutableSnapshot事务模型;重组读取运行在一致性快照内。 RenderSession失效重绘调度已升级为Choreographer帧对齐合并;显式render()仍保持立即执行语义。- 动画主链已统一到
MonotonicFrameClock(host 注入Choreographer实现),animate*AsState/Animatable/Transition与重组调度对齐。 graphicsLayer已接入 renderer resolved modifier 与 patch 语义,状态驱动变换不再依赖全量重绑。- 手势分发已统一为单 view dispatcher,消费优先级固定为“gesture > clickable fallback”,并支持方向锁/slop/priority 冲突策略。
- 列表/分页容器支持 opt-in motion 策略(insert/remove/move/change),与
DiffUtil + ItemAnimator协同且不改变默认容器行为。 - graphics 主链已落地 Canvas 节点与 draw modifiers;
drawWithCache支持跨帧缓存命中/失效,避免高频绘制场景重复构建命令。 - graphics 执行器已收口 v2 基线:
DrawRoundRect四角半径语义正确、Drawable绘制支持DrawPaint组合、ImageFilterModel.Chain可递归合并生效。 - 发布态基线使用 R8 + resource shrink 的非 debuggable
benchmarktarget;ReleaseBaselineBenchmark固定覆盖无 ART 预编译的冷启动与 state patch 帧耗时。 - 列表性能对比使用同一 target、同一份 1000 项数据与完全一致的交互脚本,分别运行 ViewCompose
LazyColumn和 Jetpack ComposeLazyColumn;覆盖双向快速滚动与 keyed reorder + payload 内容更新。 - 复杂布局对比使用同一份 18 卡片仪表盘模型,分别运行 ViewCompose
ScrollableColumn与 ComposeColumn.verticalScroll;全部子树一次挂载,覆盖深层嵌套滚动、全卡片字段更新和条件详情子树变更。 - 两组对照均采集帧耗时与最大 heap/RSS;
compare_macrobenchmarks.py自动生成 Markdown/JSON 配对报告,并支持以 Compose 为同次运行控制组的归一化回归门禁。 - 高级阴影建立独立有界外/内栅格缓存,平移/缩放/旋转/alpha 重绘复用同一栅格;
ShadowPerformanceComparisonBenchmark覆盖 1000 项 Lazy 与复杂布局的滚动/变更,并用 Compose 作为同轮设备波动控制组。
2.2 发布态基准入口
构建门禁:
./gradlew qaRelease
该任务同时构建 R8 优化的 release、非 debuggable benchmark target 和 benchmark
instrumentation APK,可在没有设备时发现 shrink/R8/variant 回归。
设备基准:
./gradlew benchmarkRelease
设备基准并生成 Compose 对照报告:
./gradlew benchmarkCompare
结果默认写入:
build/reports/benchmarks/compose-comparison.mdbuild/reports/benchmarks/compose-comparison.json
对已有结果重新生成报告:
./gradlew benchmarkComparisonReport \
-PbenchmarkResult=/path/to/current-benchmarkData.json
与同设备历史基线比较并执行回归门禁:
./gradlew benchmarkComparisonReport \
-PbenchmarkResult=/path/to/current-benchmarkData.json \
-PbenchmarkBaseline=/path/to/baseline-benchmarkData.json
发布态权威基线是 ReleaseBaselineBenchmark:
- target 为 R8 优化、resource shrink、非 debuggable 的 benchmark variant。
CompilationMode.None隔离 ART 预编译收益,直接暴露交付二进制回归。- 固定场景为冷启动和 state patch。
- 结果只在同设备、同系统版本、同温控状态下纵向比较。
Compose 对照基线是 ListPerformanceComparisonBenchmark:
- 两个引擎运行在同一个 R8 target 中,排除应用配置、资源和进程环境差异。
- 使用
CompilationMode.None,避免 ART 预编译掩盖框架交付成本。 viewComposeListScroll/composeListScroll使用相同手势轨迹。viewComposeListMutation/composeListMutation使用相同的 37 项旋转和每 16 项内容更新。- 对比结论必须来自同一次设备运行;不同设备产生的数据不能横向相除。
复杂布局对照基线是 ComplexLayoutPerformanceComparisonBenchmark:
viewComposeComplexLayoutScroll/composeComplexLayoutScroll对比非 Lazy 整树滚动。viewComposeComplexLayoutUpdate/composeComplexLayoutUpdate同时更新 18 个卡片的数据,并切换条件详情子树。- 两端保持相同的卡片、指标、标签、条件内容数量和嵌套顺序。
- 该场景专门观察 ViewGroup 深度、全树 measure/layout 与局部 patch 成本,不用于评价 Lazy 容器。
高级阴影对照基线是 ShadowPerformanceComparisonBenchmark:
- ViewCompose 与 Compose 使用相同的阴影层数、颜色、尺寸、shape、列表数据和复杂布局模型。
- 固定覆盖阴影列表滚动/变更、阴影复杂布局滚动/更新,共 8 个成对方法。
shadowRenderPolicy=exact_bitmap|render_node|auto只切换 ViewCompose 后端,不改变工作负载;Compose 结果用于归一化设备温度和后台噪声。- 2026-07-30 在 Samsung SM-G991B / Android 13 上各运行 10 轮,RenderNode 相对 ExactBitmap 的 P50、P95 与 RSS 方向混杂,没有证明稳定收益。
- 因此
Auto继续固定为ExactBitmap;RenderNodeDisplayList保留为显式实验策略,不能作为发布默认值。
同机后端对比入口:
./gradlew :viewcompose-benchmark:connectedBenchmarkAndroidTest \
-Pandroid.testInstrumentationRunnerArguments.class=com.viewcompose.benchmark.ShadowPerformanceComparisonBenchmark \
-Pandroid.testInstrumentationRunnerArguments.shadowRenderPolicy=exact_bitmap
./gradlew :viewcompose-benchmark:connectedBenchmarkAndroidTest \
-Pandroid.testInstrumentationRunnerArguments.class=com.viewcompose.benchmark.ShadowPerformanceComparisonBenchmark \
-Pandroid.testInstrumentationRunnerArguments.shadowRenderPolicy=render_node
完整数据与决策见高级阴影执行记录。
自动报告与回归规则:
- 对照表固定输出 frame CPU P50/P95、frame overrun P50/P95、heap max 与 RSS anon max。
- 每个场景的 ViewCompose 与 Compose 结果必须来自同一份 benchmark JSON。
- 历史回归只允许同设备型号、系统 fingerprint、CPU lock 状态和 compilation mode。
- 门禁必须同时满足“ViewCompose 原始指标超过阈值”和“ViewCompose/Compose 归一化比值超过阈值”才失败。
- 默认阈值维护在
tools/performance/benchmark_policy.json,小于绝对噪声下限的变化不会失败。 - 报告会计算各 iteration P50 的变异系数;超过
0.15标记为不稳定,数据应重跑而不是直接形成结论。
2.3 当前结论
- 当前阶段优先级不是“追求极限 FPS”,而是先控制回归风险和错误用法。
- 最关键收益来自:
- 正确复用
- 组级脏区重组 + 跳过不必要更新
- 容器刷新语义稳定
- 基线更新(2026-03-08):
SlotTable Lite+ 子树级重组已接入主链路,qaQuick通过;qaFull结果按当前设备门禁状态在 roadmap 持续登记。
3. 性能门禁指标
每次性能相关改动,至少关注下面 4 类成本:
- 重建成本:状态变化后产树与 reconcile 开销
- 绑定成本:View rebind 与 patch 执行开销
- 布局成本:measure/layout 次数与深度
- 容器成本:延迟 session 容器的刷新与复用稳定性
建议固定输出:
- viewcompose-benchmark 数据(同机型、同路径)
- render stats(含 rebound/skipped)
- layout pass 关键计数
4. 设计约束(必须遵守)
- 新控件必须先定义“高频路径”和“可接受开销”,再扩参数。
- 新 modifier/props 不得引入无条件全量 rebind。
- 复用型容器必须有“结构稳定仍刷新可见内容”路径。
AndroidView视为性能隔离区;可重放配置放在update/onReset/nativeView,外部提交副作用放在onCommit,资源解绑放在只执行一次的onRelease。- 不为短期优化破坏模块边界和可维护性。
- 节点组开发必须保持 group key 稳定;若无法稳定,需显式接受“祖先回退重组 + 告警”成本。
- 状态并发写入必须通过 snapshot apply 语义验证,禁止在性能优化中绕过冲突合并与失败路径。
- 禁止将重组调度回退到
container.post;帧对齐路径是默认实现边界。 - 动画实现必须复用
MonotonicFrameClock,禁止在动画 API 内新增并行调度器破坏帧对齐收敛。 - 手势冲突策略调整必须同步验证滚动容器(Lazy/Scrollable/Pager)场景,避免通过“全量拦截”掩盖性能退化。
- 图形绘制链路优化必须优先保证
drawWithCache语义稳定(依赖变化才重建缓存);禁止把缓存重建放回每帧路径。 - 图像绘制优化不得牺牲语义:
Drawable路径必须保持DrawPaint生效,ImageFilter.Chain不得被静默降级为 no-op。 - 静态阴影缓存 key 必须覆盖尺寸、density、layout direction、shape 与完整阴影规格;不得缓存 View、Session 或可变业务对象。
- 节点仅发生 translation/scale/rotation/alpha 变化时必须复用已有阴影栅格;blur/spread/shape/尺寸变化才允许重建。
- 阴影后端默认策略的任何调整都必须提供同设备、同构建、同工作负载的多轮配对数据,并通过 Compose 归一化门禁。
- 大尺寸、逐帧 blur/spread 或 RenderEffect 路径必须先定义内存/离屏预算;预算落地前不得进入默认列表或转场路径。
5. 反模式清单
- 用深层嵌套布局代替明确的容器策略。
- 无基准数据支撑就引入复杂优化。
- 把性能问题都归因于运行时,而忽略页面/容器结构问题。
- 在无回归测试情况下改动核心渲染热点。
6. 分阶段路线
Phase 1:基线与可观测性
状态:已完成基础落地
目标:viewcompose-benchmark 路径稳定、核心指标可读取
Phase 2:跳过更新能力
状态:已完成(本轮闭环)
目标:扩大节点级 skip 更新覆盖,降低无效 rebind
阶段备注(2026-03-07):Lazy/Pager keyed diff 引擎已切换至 DiffUtil,并已新增 SkipSubtree + skippedSubtrees 路径与统计;后续增量在 Phase 3/4 持续推进。
Phase 3:诊断增强
状态:核心可视化已完成 目标:render tree、patch、CompositionLocal 与重组原因已可直接读取;后续补节点高亮、跨 session 关联与逐节点耗时
Phase 4:容器与布局收口
状态:列表、复杂布局 Compose 对照、内存指标、自动报告与归一化回归门禁已建立 目标:收敛高频容器和复杂页面的布局开销
Phase 5:发布态优化
状态:R8 release 基准已建立,baseline profile 待推进 目标:在当前无 ART 预编译基线上继续量化 baseline profile 等发布链路收益
Phase 6:高级阴影后端
状态:静态精确后端、缓存、Compose 对照和首轮设备决策已完成;动态 RenderEffect 仍为研究项
目标:维持 Auto = ExactBitmap,继续积累设备矩阵;只有在明确的内存与帧预算内评估动态 blur/转场阴影
7. 评审与提交流程
性能相关 PR 至少满足:
- 说明改动针对哪类成本
- 提供改动前后关键指标
- 说明是否影响容器刷新语义
- 同步更新本文件或相关规范文档
若改动涉及可见行为(布局、交互、overlay、输入),额外要求:
- 至少补一条对应 instrumentation 回归,或登记明确豁免与补齐时间
协作规则见:
8. 关联文档
- 架构规范:overview.md
- 容器专项清单:session-containers.md
- 统一路线图:roadmap.md
- 文档入口:docs/README.md
- 状态快照规范:state-snapshots.md