Statistic 性能优化
路线图位置:
../desktop-controls-optimization-roadmap.mdTier 2 #2 状态:本轮已完成正确性、生命周期与 Gallery 实测收尾。
0. 结论
本轮没有保留 Skeleton / Theme 结构层面的性能改动,最终只保留了对 Statistic 和 TimerStatistic 的低风险正确性、生命周期修复:
Statistic明确区分内部生成的Content与用户显式设置的Content。Value、分隔符和精度变化后,内部生成的文本会继续同步;Value = null会清空内部生成文本。- 显式
Content不会被后续Value更新覆盖。 - 替换
StatisticCountUp内容时,会释放旧内容指向 owner 的DataContext。 TimerStatistic只在进入 visual tree 后创建DispatcherTimer,detach 时停止并释放。- countdown 结束只触发一次
CountdownFinished,并把RemainingTimeclamp 到TimeSpan.Zero。
StatisticShowCase 的主要收益体现在真实页面的分配和稳定视觉树形态:
| 指标 | baseline | optimized | 改善 |
|---|---|---|---|
| Gallery cold mean | 248.01 ms | 235.08 ms | 5.2% |
| Gallery cold alloc mean | 8813.85 KB | 8638.98 KB | 174.87 KB |
| Gallery repeated mean | 61.78 ms | 62.69 ms | -1.5% |
| Gallery repeated median | 51.80 ms | 50.44 ms | 2.6% |
| Gallery repeated alloc mean | 7471.87 KB | 7228.74 KB | 243.13 KB |
| Gallery repeated visuals | 387 | 384 | 3 |
repeated mean 和 P95 在这轮样本里仍有噪声,因此本轮不宣称稳定页面速度提升;以 Gallery 分配下降、重复导航 visual tree 少 3 个、timer 生命周期释放和状态正确性作为主要结论。
1. 资格门槛
Gallery source:controlgallery/AtomUIGallery/ShowCases/Views/DataDisplay/StatisticShowCase.axaml
| 项 | 数值 |
|---|---|
<atom:Statistic> ready 条件 |
>= 9 |
<atom:TimerStatistic> ready 条件 |
>= 6 |
<atom:StatisticCountUp> ready 条件 |
>= 2 |
| Runtime visuals baseline | 390 cold / 387 repeated |
Runtime SkeletonLine baseline |
17 |
真实会话操作频率:
- 独立
StatisticShowCase页面导航:至少 1 次。 - dashboard / summary / analytics 类页面会天然集中使用多个
Statistic。 TimerStatistic在 attach 后长期 tick,必须有明确的创建和释放路径;这属于生命周期风险,不只是启动分配问题。
Statistic 是 Tier 2 单功能控件,实例数不如 Select / TreeView 高,但 TimerStatistic 的常驻 timer 和 StatisticCountUp 的 owner binding 都值得补状态验证。
2. 根因
2.1 Statistic.Content ownership 不清晰
旧实现只在初始化时把 EffectiveValue 写入 Content。后续 Value、Precision、分隔符变化会更新 EffectiveValue,但不会同步到已生成的 Content。同时,控件无法区分 Content 是内部生成,还是用户显式提供的 StatisticCountUp / 自定义内容。
这个问题会带来两类后果:
- 默认文本路径可能显示 stale value。
- 如果简单在每次
Value变化时写Content,又会覆盖用户显式Content。
2.2 StatisticCountUp.DataContext 缺少替换清理
Content 为 StatisticCountUp 时会把 owner 作为 DataContext。旧内容被替换后没有清空这个指针,容易留下旧 child 到 owner 的引用。
2.3 TimerStatistic timer 生命周期过早
旧实现会在 Value 或 RefreshDuration 变化时直接创建 DispatcherTimer,即使控件还没有进入 visual tree。detach 后虽然会释放 timer,但 pre-attach 创建会把未显示控件也放进计时器生命周期。
同时 countdown 结束路径依赖 RemainingTime <= TimeSpan.Zero,需要显式验证只触发一次并停在零。
3. 改动
涉及文件:
src/AtomUI.Desktop.Controls/Statistic/Statistic.cssrc/AtomUI.Desktop.Controls/Statistic/TimerStatistic.cstools/performances/AtomUI.Performance/Suites/Statistic/StatisticStateVerification.cs
Statistic 新增 generated content ownership:
_generatedContent_isUsingGeneratedContent_isInitialized
默认 Content == null 时调用 SetGeneratedContent(EffectiveValue);后续 value/format 变化只在 Content 为空或仍是内部生成内容时同步。用户显式设置 Content 后,_isUsingGeneratedContent 会关闭,后续 value 变化不再覆盖。
TimerStatistic 新增 attach-gated timer 生命周期:
_isAttachedToVisualTree_isCountdown_hasCountdownFinishedReleaseTimer()HasTimerTarget()NotifyCountdownFinished()
BuildTimer() 会先释放旧 timer,再捕获当前是 countdown 还是 countup。timer 只在 attach 后、且 Value != default 时创建;detach 时停止、解绑 Tick 并置空。
4. 未保留的尝试
本轮曾尝试过多种 Skeleton active 路径优化,包括:
- 在 Statistic theme 里加
IsActive="{TemplateBinding IsLoading}"。 - 用 style trigger 驱动
Skeleton.IsActive。 - 在 C# 里同步
PART_Skeleton状态。 - 给 Skeleton 增加内部 effective active 状态。
这些候选要么造成 Statistic.Loading 分配回退,要么把 theme 固定视觉的状态逻辑转移到 C#,不符合性能 skill 的 Theme Static Rule。按 3-round 预算规则,Skeleton 相关性能改动全部撤回;只保留 correctness / lifecycle 修复和状态验证。
同时删除了旧的 VerifyStatisticLazySkeleton 验证。该验证要求 IsLoading=false 时动态移除 SkeletonLine,这本身违反 Theme Static Rule:Statistic theme 中固定声明的 Skeleton 视觉应保留在 axaml,由 IsVisible / 状态控制可见性,而不是运行时动态创建和释放。
5. 验证
5.1 状态验证
dotnet run --project tools/performances/AtomUI.Performance/AtomUI.Performance.csproj \
-c Debug --framework net10.0 -- \
--verify-statistic-states
结果:Statistic state verification passed.
覆盖:
- 默认生成
Content后,Value变化会同步文本。 Value = null会清空内部生成文本。- 显式
Content不会被 generated value 覆盖。 - 替换
StatisticCountUp时旧 child 的DataContext会清空。 TimerStatisticattach 前不创建 timer,attach 后创建,detach 后释放。- countdown 结束事件只触发一次,
RemainingTime停在零。
5.2 控件级基准
dotnet run --project tools/performances/AtomUI.Performance/AtomUI.Performance.csproj \
-c Debug --framework net10.0 -- \
--suite statistic --count 40 \
--markdown /tmp/atomui-statistic-baseline.md
dotnet run --project tools/performances/AtomUI.Performance/AtomUI.Performance.csproj \
-c Debug --framework net10.0 -- \
--suite statistic --count 40 \
--markdown /tmp/atomui-statistic-after.md
控件级 suite 的普通 Statistic 场景 visual tree 保持不变;TimerStatistic ms/item 在 Debug headless 下波动明显,因此不作为本轮主要收益证明。最终看 Gallery 真实页面的分配、visual tree 和生命周期验证。
基线摘要:
| Scenario | ms/item | KB/item | Visual/root |
|---|---|---|---|
Statistic.Basic |
1.586 | 283.6 | 18 |
Statistic.Precision |
1.809 | 289.5 | 18 |
Statistic.Loading |
1.438 | 285.6 | 16 |
Statistic.PrefixSuffix |
2.518 | 355.2 | 21 |
Statistic.CountUp |
2.116 | 314.5 | 19 |
TimerStatistic.Default |
1.629 | 308.7 | 18 |
TimerStatistic.MillisecondFormat |
2.022 | 313.2 | 18 |
Statistic.GalleryShape |
49.162 | 7556.7 | 337 |
5.3 Gallery 导航基准
dotnet run --project tools/performances/AtomUI.GalleryPerformance/AtomUI.GalleryPerformance.csproj \
-c Debug --framework net10.0 -- \
--showcase statistic --label statistic-baseline \
--cold-iterations 10 --iterations 30 --warmup 5 --timeout-ms 30000 \
--markdown /tmp/statistic-showcase-baseline.md
dotnet run --project tools/performances/AtomUI.GalleryPerformance/AtomUI.GalleryPerformance.csproj \
-c Debug --framework net10.0 -- \
--showcase statistic --label statistic-optimized \
--cold-iterations 10 --iterations 30 --warmup 5 --timeout-ms 30000 \
--markdown /tmp/statistic-showcase-after.md
| Set | Mean | Median | P95 | Alloc mean | Visuals | TextBlock | SkeletonLine |
|---|---|---|---|---|---|---|---|
| Cold baseline | 248.01 ms | 244.71 ms | 272.39 ms | 8813.85 KB | 390 | 42 | 17 |
| Cold optimized | 235.08 ms | 234.00 ms | 253.07 ms | 8638.98 KB | 390 | 42 | 17 |
| Repeated baseline | 61.78 ms | 51.80 ms | 105.72 ms | 7471.87 KB | 387 | 39 | 17 |
| Repeated optimized | 62.69 ms | 50.44 ms | 109.85 ms | 7228.74 KB | 384 | 36 | 17 |
5.4 构建
dotnet build tools/performances/AtomUI.Performance/AtomUI.Performance.csproj \
-c Debug --framework net10.0
dotnet build tools/performances/AtomUI.GalleryPerformance/AtomUI.GalleryPerformance.csproj \
-c Debug --framework net10.0
结果:两个构建均通过。
6. 复杂度自评
| 指标 | 数值 |
|---|---|
新增 _ignoreXxx 标志位 |
0 |
| 新增 axaml 固定视觉动态创建 | 0 |
| 新增 timer 字段 | 0;复用原 _timer,补 release helper |
| 新增事件订阅 | 0;原有 Tick 订阅现在有明确 release |
| 新增状态验证 | --verify-statistic-states 覆盖 generated content、CountUp DataContext、timer lifecycle、countdown finish |
| 保留 Skeleton 性能实验 | 否,全部撤回 |