Splitter 性能优化
路线图位置:
../desktop-controls-optimization-roadmap.mdTier 2 #6 状态:本轮已完成控件级与 Gallery 长样本实测。
0. 结论
本轮优化有效,但收益主要来自创建/布局路径的轻量化,不改变 Splitter 运行时视觉结构:
- collapse
IconButton保留在 ControlTheme 中,符合 Theme Static Rule;隐藏或不可折叠时不再构造和保留PathIcon。 - lazy 拖拽预览复用已有
TranslateTransform,避免每个 drag delta 重新分配 transform。 SplitterHandle转发拖拽事件时复用原始VectorEventArgs,不再为每个 started/delta/completed 事件复制参数对象。Splitter.Children动态 add 同步到SplitterPanel.Children时不再通过OfType<Control>().ToList()创建 LINQ iterator +List<T>,改为一次 exact-sizeControl[]拷贝;动态 remove 进一步改为 indexedRemoveRange,不再为 remove 构造数组和RemoveAll内部HashSet。SplitterPanel刷新内部 panel 缓存时复用_panels,不再通过Children.ToList()创建临时List<T>和 backing array。- resize 事件上报 sizes 时改为一次 exact-size
double[],去掉Select(...).ToArray()后外层再.ToArray()的双重数组分配。
Gallery 首轮 warmup=5 / iterations=30 出现 repeated mean 噪声回退,但同一轮 alloc 已下降。用临时 worktree 对修改前代码补跑 warmup=15 / iterations=50 后,当前代码在 cold、repeated、P95、alloc 上均为正向;最终结论采用长样本口径。
相关 Avalonia 成本依据:
IsVisible=False会短路 measure / arrange / render:.referenceprojects/Avalonia/src/Avalonia.Base/Layout/Layoutable.cs:546、:671;.referenceprojects/Avalonia/src/Avalonia.Base/Rendering/ImmediateRenderer.cs:34RenderTransform属于 compositor 侧变换路径:.referenceprojects/Avalonia/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.DirtyInputs.cs:89-118
1. 控件级基线
baseline 命令:
dotnet run --project tools/performances/AtomUI.Performance/AtomUI.Performance.csproj \
-c Debug --framework net10.0 --no-build -- \
--suite splitter --count 60 \
--markdown /tmp/splitter-control-baseline.md
current 复测命令:
dotnet run --project tools/performances/AtomUI.Performance/AtomUI.Performance.csproj \
-c Debug --framework net10.0 --no-build -- \
--suite splitter --count 60 \
--markdown /tmp/splitter-control-current.md
第二次 current 复测同命令,输出到 /tmp/splitter-control-current-rerun.md。控件级 headless timing 单次噪声较明显,所以这里同时列出两次 current 和平均改善;alloc 取两次一致值或均值。
| Scenario | baseline ms/item | current ms/item run1 | current ms/item run2 | avg timing 改善 | baseline KB/item | current KB/item | alloc 改善 | 判断 |
|---|---|---|---|---|---|---|---|---|
Splitter.Basic.Vertical2 |
1.129 | 1.089 | 1.042 | 5.62% | 200.1 | 193.6 | 3.25% | 有效 |
Splitter.Basic.Horizontal2 |
1.154 | 1.060 | 0.999 | 10.79% | 201.9 | 195.3 | 3.27% | 有效 |
Splitter.Multi.Vertical4 |
4.203 | 4.443 | 3.630 | 3.96% | 565.3 | 545.9 | 3.43% | timing 抖动,alloc 有效 |
Splitter.Collapsible.Always3 |
6.182 | 4.295 | 5.815 | 18.23% | 922.0 | 922.0 | 0.00% | timing 有效,alloc 持平 |
Splitter.Collapsible.Hidden3 |
2.642 | 1.399 | 2.126 | 33.29% | 371.5 | 358.7 | 3.45% | 有效 |
Splitter.Lazy.Pair |
2.733 | 1.412 | 2.412 | 30.04% | 408.1 | 395.0 | 3.21% | 有效 |
Splitter.GalleryShape |
13.570 | 9.655 | 14.524 | 10.91% | 3130.7 | 3061.2 | 2.22% | timing 噪声,alloc 有效 |
控件级结论:分配下降是稳定信号;Hidden3、Lazy.Pair 和 collapse always 路径的 timing 两次复测均为正向。Multi.Vertical4、GalleryShape 的控件级 timing 有单次反向,不能单独作为收益证明;最终有效性以 Gallery 长样本为主。
1.1 动态 Children 同步结构收益
本轮追加优化点:Splitter.Children 运行时 add/remove 时,旧路径会对 NotifyCollectionChangedEventArgs.NewItems/OldItems 执行 OfType<Control>().ToList(),每次至少创建 1 个 LINQ iterator、1 个 List<Control> 和其 backing array。add 路径直接按 items.Count 拷贝到 Control[] 后传给 SplitterPanel.Children.InsertRange,保持批量同步语义,但少一个 iterator 和一个 List 对象。
后续追加优化点:remove 路径从 RemoveAll(CopyControls(e.OldItems)) 改为 RemoveRange(OldStartingIndex, OldItems.Count)。动态 children 是镜像集合,collection changed args 已提供删除范围;直接 range 删除可以避免 remove 专用数组、RemoveAll 内部 HashSet 和目标集合扫描。
后续追加优化点:SplitterPanel.RefreshPanelsAndHandles 不再用 Children.ToList() 建临时列表;拖拽 resize 的 started/delta/completed 事件不再先 Select(...).ToArray() 再外层 .ToArray(),改为一次手写 exact-size double[] 填充。该收益是运行时结构性分配下降,不声明页面加载 timing 提升。
| metric | baseline | optimized | formula | improvement | conclusion |
|---|---|---|---|---|---|
| Splitter child add/remove temp objects / collection change | 3 objects | 1 array | (3 - 1) / 3 |
66.67% | 结构性分配下降;只声明动态 children 变更路径收益 |
| Splitter child remove temp objects / remove change | 2 objects | 0 objects | (2 - 0) / 2 |
100.00% | 结构性分配下降;remove 不再构造 Control[] 和 HashSet |
| Splitter child remove target scans / remove change | 1 scan | 0 scans | (1 - 0) / 1 |
100.00% | 有效;按 OldStartingIndex 直接删除镜像 children |
| SplitterPanel refresh temp objects / refresh | 2 objects | 0 objects | (2 - 0) / 2 |
100.00% | 结构性分配下降;只声明内部 panel 缓存刷新路径收益 |
| Splitter resize event sizes temp objects / resize event | 3 objects | 1 array | (3 - 1) / 3 |
66.67% | 结构性分配下降;只声明拖拽事件上报 sizes 路径收益 |
2. Gallery 真实场景
最终口径命令:
dotnet run --project tools/performances/AtomUI.GalleryPerformance/AtomUI.GalleryPerformance.csproj \
-c Debug --framework net10.0 --no-build -- \
--showcase splitter --label splitter-current-long \
--cold-iterations 10 --iterations 50 --warmup 15 --timeout-ms 30000 \
--markdown /tmp/splitter-gallery-current-long.md
baseline 取自临时 worktree /tmp/AtomUIV6-splitter-baseline-codex,代码为本轮修改前的 066d915e9。
| 指标 | baseline | current | 改善 | 结论 |
|---|---|---|---|---|
| Cold first navigation mean | 177.55 ms | 154.96 ms | 12.72% | 有效 |
| Cold first navigation median | 162.21 ms | 152.06 ms | 6.26% | 有效 |
| Cold first navigation P95 | 253.34 ms | 175.33 ms | 30.80% | 有效 |
| Cold alloc mean | 6380.36 KB | 6256.48 KB | 1.94% | 小幅有效 |
| Repeated navigation mean | 36.91 ms | 34.66 ms | 6.10% | 有效 |
| Repeated navigation median | 33.12 ms | 32.62 ms | 1.51% | 小幅有效 |
| Repeated navigation P95 | 51.79 ms | 40.43 ms | 21.93% | 有效 |
| Repeated alloc mean | 5770.31 KB | 5659.40 KB | 1.92% | 小幅有效 |
| Runtime visuals | 319 | 319 | 0 | 结构不变 |
| Runtime logical | 31 | 31 | 0 | 结构不变 |
补充:首轮同参数 warmup=5 / iterations=30 的 current repeated mean 为 50.94 ms,高于 baseline 49.15 ms,但样本前段存在明显预热坡度;长样本复测未复现该退化。
3. 改动
| 路径 | 改动 |
|---|---|
src/AtomUI.Desktop.Controls/Splitter/SplitterHandle.cs |
隐藏 collapse button 时清空 Icon;仅 visible 时创建 collapse icon;拖拽事件转发复用原始 VectorEventArgs |
src/AtomUI.Desktop.Controls/Splitter/SplitterPanel.cs |
lazy preview 复用同一个 TranslateTransform 并只更新 X/Y;内部 panel 缓存刷新去掉 Children.ToList();resize event sizes 改为一次数组填充 |
tools/performances/AtomUI.Performance/Suites/Splitter/SplitterStateVerification.cs |
状态验证按 Theme Static Rule 调整为“模板按钮保留,但隐藏态不持有 icon”;新增 transform reuse、动态 children 同步、resize sizes 上报验证 |
4. 验证
dotnet build tools/performances/AtomUI.Performance/AtomUI.Performance.csproj -c Debug --framework net10.0
结果:0 Warning(s), 0 Error(s)。
dotnet run --project tools/performances/AtomUI.Performance/AtomUI.Performance.csproj \
-c Debug --framework net10.0 --no-build -- \
--verify-splitter-states
结果:Splitter state verification passed.
后续追加复测:
dotnet build -c Release -f net10.0 --no-restore tools/performances/AtomUI.Performance/AtomUI.Performance.csproj
dotnet run -c Release -f net10.0 --no-build --project tools/performances/AtomUI.Performance/AtomUI.Performance.csproj -- --verify-splitter-states
结果:构建 0 Warning(s), 0 Error(s);Splitter state verification passed.
dotnet build tools/performances/AtomUI.GalleryPerformance/AtomUI.GalleryPerformance.csproj -c Debug --framework net10.0
结果:构建通过;保留既有 warning DataGridColumn._clipboardContentBinding is never used,非本轮新增。
4.1 追加结构优化:Collapsible parser span trim
SplitterPanelCollapsible.Parse() 旧路径先 input.Trim(),再执行 bool / none / enum 解析。本轮改为 ReadOnlySpan<char>.Trim(),并使用 bool / enum 的 span overload;解析优先级和异常行为保持不变。
| 指标 | 优化前 | 优化后 | 公式 | 提升 | 结论 |
|---|---|---|---|---|---|
| Splitter collapsible trim temp strings / parse | 1 | 0 | (1 - 0) / 1 |
100.00% | 结构收益;span trim 保持 trim 语义 |
| Splitter collapsible parser priority | bool → none → enum | bool → none → enum | n/a | 0.00% | 行为保持 |
| Page-load timing claim | none | none | n/a | n/a | 本轮没有有效前后 timing,不声明页面级速度收益 |
5. 复杂度自评
| 指标 | 数值 |
|---|---|
新增 Ensure*/Clear*/Sync* 方法 |
0 |
新增 _ignoreXxx 标志位 |
0 |
| 新增 disposable / event / timer | 0 |
| axaml 节点搬到 C# 动态创建 | 否 |
| 生产文件范围 | 2 个文件 |