Files
AtomUI/docs/performances/TreeSelect

TreeSelect 性能优化

路线图位置:../desktop-controls-optimization-roadmap.md Tier 1 #3
状态:本轮已完成 Gallery 实测。


0. 结论

本轮没有继续改 TreeSelect 的 popup / binding 结构;这些尝试已被实测否定。最终收益来自 TreeSelect 依赖的共享 SelectHandle:原模板为每个 handle 固定创建 OpenIndicator、LoadingIndicator、SearchIndicator 三套 indicator visual,实际同一时刻只应显示一个。现在模板保留一个 IconPresenter#OpenIndicator,由 CurrentIndicatorIcon / IsCurrentIndicatorVisible 选择当前图标。

搜索图标仍由 SelectHandleTheme.axaml 通过 IconTemplate 声明;C# 不硬编码 SearchOutlined,也不新增 Ensure*/Clear* 动态 visual 管线。

指标 baseline optimized 改善
Cold first navigation mean 474.44 ms 409.20 ms 13.7%
Cold median 456.79 ms 405.25 ms 11.3%
Cold P95 571.90 ms 443.28 ms 22.5%
Repeated navigation mean 124.72 ms 108.95 ms 12.6%
Repeated median 119.78 ms 101.99 ms 14.9%
Repeated P95 164.29 ms 147.20 ms 10.4%
Repeated alloc mean 19996.64 KB 18118.79 KB 9.4%
Runtime visuals 909 858 5.6%
IconPresenter 38 21 44.7%
Icon / PathIcon 51 17 66.7%

1. 资格门槛

Gallery source:controlgallery/AtomUIGallery/ShowCases/Views/DataEntry/TreeSelectShowCase.axaml

项 数值
<atom:TreeSelect> 17
ShowCaseItem 11
Runtime TreeSelect 17
Runtime AddOnDecoratedBox 17
Runtime CheckBox 12

真实会话操作频率:

  • 页面导航:至少 1 次。
  • 下拉打开:基础、多选、checkable、filter、async load 等示例都会触发。
  • filter / search:filter 示例会触发 SelectHandle 的搜索 indicator 状态。
  • clear / loading:allow-clear、async load 路径会触发 handle 右侧状态切换。

结论:实例数 > 5,操作 > 1/session,满足 SKILL Tier 1 §13。


2. 根因

SelectHandleTheme.axaml 原模板固定创建三套 indicator:

  • IconPresenter#OpenIndicator
  • IconPresenter#LoadingIndicator
  • SearchOutlined#SearchIndicator

TreeSelectShowCase 有 17 个 TreeSelect,这三套 indicator 在页面加载时放大为 51 个 icon / path icon 节点,其中大多数只是隐藏状态。

Avalonia source reference:

  • .referenceprojects/Avalonia/src/Avalonia.Controls/Primitives/TemplatedControl.cs:333:template.Build(this) 创建模板树。
  • .referenceprojects/Avalonia/src/Avalonia.Controls/Primitives/TemplatedControl.cs:338:模板根加入 visual children。
  • .referenceprojects/Avalonia/src/Avalonia.Base/Layout/Layoutable.cs:546 / :624:IsVisible=false 会跳过 measure,但模板创建成本已经发生。
  • src/AtomUI.Controls/Icon/IconPresenter.cs:50:IconProperty 影响 measure。
  • src/AtomUI.Controls/Icon/IconPresenter.cs:63-66、:95-96:IconPresenter 切换 icon 时释放旧 visual/logical child 并挂载新 child。

主导子系统:Templates / visual tree structure,其次是 IconPresenter 子图标结构。


3. 被否定的尝试

本轮先验证了 TreeSelect 自身 binding 方向:

尝试 结果
将模板绑定改为 TemplateBinding 破坏运行时视觉形态,回退
改回 ancestor binding 视觉恢复,但 repeated mean 回退
使用 direct observable binding 分配小幅下降,但耗时明显回退

结论:TreeSelect 当前主要问题不在这条 binding 链路;继续叠加 binding 改动不符合 3-round budget,已回退。


4. 最终改动

4.1 SelectHandle 增加运行态 indicator

新增:

  • CurrentIndicatorIcon:当前交给模板 presenter 显示的 PathIcon?。
  • IsCurrentIndicatorVisible:当前没有可显示 icon 时隐藏 presenter。
  • FilterIndicatorTemplate:主题声明的 filter icon 模板。

状态映射:

状态 当前图标
IsFilterEnabled && IsDropDownOpen FilterIndicatorTemplate.Build()
IsLoading LoadingIcon
默认 OpenIndicator

clear 按钮仍在 XAML 中保留,hover / pressed clear 状态只隐藏共享 indicator presenter,不新增动态按钮创建。

4.2 模板保留单个 presenter

SelectHandleTheme.axaml 将三套 indicator 收敛为:

<atom:IconPresenter Name="OpenIndicator"
                    Icon="{TemplateBinding CurrentIndicatorIcon}"
                    IsVisible="{TemplateBinding IsCurrentIndicatorVisible}"
                    IsMotionEnabled="{TemplateBinding IsMotionEnabled}" />

SearchOutlined 通过 theme-level FilterIndicatorTemplate 保留:

<Setter Property="FilterIndicatorTemplate">
    <atom:IconTemplate>
        <antdicons:SearchOutlined />
    </atom:IconTemplate>
</Setter>

5. 验证

5.1 构建

dotnet build src/AtomUI.Desktop.Controls/AtomUI.Desktop.Controls.csproj -c Debug --framework net10.0

结果:0 Warning(s), 0 Error(s)。

dotnet build tools/performances/AtomUI.Performance/AtomUI.Performance.csproj -c Debug --framework net10.0

结果:0 Warning(s), 0 Error(s)。

dotnet build tools/performances/AtomUI.GalleryPerformance/AtomUI.GalleryPerformance.csproj -c Debug --framework net10.0

结果:构建通过;保留既有 warning DataGridColumn._clipboardContentBinding is never used,非本轮新增。

命令:

dotnet run --project tools/performances/AtomUI.GalleryPerformance/AtomUI.GalleryPerformance.csproj \
  -c Debug --framework net10.0 --no-build -- \
  --showcase treeselect --label baseline \
  --cold-iterations 10 --iterations 30 --warmup 5 --timeout-ms 30000 \
  --markdown /tmp/treeselect-showcase-baseline.md
dotnet run --project tools/performances/AtomUI.GalleryPerformance/AtomUI.GalleryPerformance.csproj \
  -c Debug --framework net10.0 --no-build -- \
  --showcase treeselect --label optimized-selecthandle-rerun \
  --cold-iterations 10 --iterations 30 --warmup 5 --timeout-ms 30000 \
  --markdown /tmp/treeselect-showcase-optimized-selecthandle-rerun.md
Set Mean Median P95 Alloc mean Visuals IconPresenter PathIcon
Cold baseline 474.44 ms 456.79 ms 571.90 ms n/a 909 38 51
Cold optimized 409.20 ms 405.25 ms 443.28 ms 20407.88 KB 858 21 17
Repeated baseline 124.72 ms 119.78 ms 164.29 ms 19996.64 KB 909 38 51
Repeated optimized 108.95 ms 101.99 ms 147.20 ms 18118.79 KB 858 21 17

5.3 Shared surface smoke

SelectHandle 同时被 Select / Cascader 使用,本轮只对它们做 shared surface smoke,不声明页面级收益:

dotnet run --project tools/performances/AtomUI.GalleryPerformance/AtomUI.GalleryPerformance.csproj \
  -c Debug --framework net10.0 --no-build -- \
  --showcase select --label selecthandle-smoke \
  --cold-iterations 3 --iterations 10 --warmup 2 --timeout-ms 30000 \
  --markdown /tmp/select-showcase-selecthandle-smoke.md

结果:SelectShowCase 完成,runtime Select=33、IconPresenter=59、Icon/PathIcon=46。

dotnet run --project tools/performances/AtomUI.GalleryPerformance/AtomUI.GalleryPerformance.csproj \
  -c Debug --framework net10.0 --no-build -- \
  --showcase cascader --label selecthandle-smoke \
  --cold-iterations 3 --iterations 10 --warmup 2 --timeout-ms 30000 \
  --markdown /tmp/cascader-showcase-selecthandle-smoke.md

结果:CascaderShowCase 完成,runtime Cascader=23、IconPresenter=42、Icon/PathIcon=56。

5.4 状态验证现状

已更新 VerifySelectHandleIndicatorSlots 和 VerifySelectLoadingLifecycle 的期望:SelectHandle 现在验证共享 presenter,而不是旧的 LoadingIndicator / SearchIndicator 独立 visual。

两条历史宽验证仍失败,但失败项不在本轮 SelectHandle:

dotnet run --project tools/performances/AtomUI.Performance/AtomUI.Performance.csproj \
  -c Debug --framework net10.0 --no-build -- \
  --verify-icon-hidden-slots

剩余失败:MenuItem、ToggleIconButton、NavMenu indicator、Button loading icon 等既有 hidden-slot 断言。

dotnet run --project tools/performances/AtomUI.Performance/AtomUI.Performance.csproj \
  -c Debug --framework net10.0 --no-build -- \
  --verify-select-states

剩余失败:Select / TreeSelect closed popup lazy 断言与 DropDown event count 等既有断言。


6. 复杂度自评

指标 数值
新增 Ensure*/Clear*/Sync* 方法 0
新增 _ignoreXxx / try-finally 标志位 0
新增 disposable 字段 0
新增订阅 / timer 0
同生命周期 RelayBind 0
生产文件范围 2 个文件,均在共享 SelectHandle
状态验证文件 2 个文件,匹配新单 presenter 语义

本轮保留 TemplateBinding,没有引入同优先级 binding 碰撞。搜索图标由 theme IconTemplate 声明,避免把具体 icon 类型硬编码到 C#。


7. 后续

TreeSelect 当前独立热点更可能在 popup 打开后的 TreeSelectTreeView、filter 交互和 checkable tree item 路径。下一轮需要先建立交互动作级基线,而不是继续改页面首次导航的 handle 结构。