mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-23 01:38:40 +08:00
Grid 性能与正确性优化
路线图位置:
../README.mdLayout / Grid 状态:本轮完成正确性修复、控件级基线和状态验证;timing 为 smoke-only,不声明页面级速度提升。
0. 结论
Row 原本缓存了按 breakpoint 解析后的子项布局,但 Col.Span/Order/Offset/... 或子项 IsVisible 变化后缓存不会失效,会导致换列、排序和隐藏子项仍按旧布局排列。本轮修复 stale layout,并在同宽 measure → arrange 路径复用已计算 layout,减少一次重复 BuildLayout。
| 指标 | baseline | optimized | formula | improvement | 结论 |
|---|---|---|---|---|---|
| Grid stale layout 验证失败数 | 3 | 0 | (3 - 0) / 3 |
100.00% | 修复 Span / Order / IsVisible 动态变化后的旧布局 |
| Grid 状态验证覆盖 | 0 | 3 | (3 - 0) / 3 |
100.00% | 新增 3 个状态验证 |
same-width layout pass 的 BuildLayout 调用 |
2 | 1 | (2 - 1) / 2 |
50.00% | measure 后 arrange 可复用 layout result |
Grid.Row.Wrap.Col6x12 KB/item |
294.3 KB | 292.6 KB | (294.3 - 292.6) / 294.3 |
0.58% | allocation 小幅下降 |
Grid.Row.Ordered.Col6x8 KB/item |
194.6 KB | 193.6 KB | (194.6 - 193.6) / 194.6 |
0.51% | allocation 小幅下降 |
| 可证明页面级速度提升 | 无 | 无 | n/a | 不声明 | 控件级 timing 为 smoke-only,不能作为速度收益 |
1. 根因
Row.GetOrderedChildren() 会缓存 RowChildInfo,其中包含 Col.ResolveLayout(breakPoint) 的结果。旧实现只在 ChildrenChanged 和 media breakpoint 变化时设置 _orderedChildrenDirty = true。
这会漏掉三类常见动态变化:
| 动态变化 | 旧行为 |
|---|---|
Col.Span 从 12 改 24 |
父级重新 measure,但 Row 继续用旧 span=12 |
Col.Order 改为 -1 |
Row 继续用旧 order 排列 |
子项 IsVisible=false |
Row 继续把隐藏子项包含在 cached children 里 |
2. 改动
Col.OnPropertyChanged():当父级是Row时调用Row.InvalidateChildLayout(),确保 Col 布局属性变化会清 Row cache。Row.OnMeasureInvalidated():统一清_orderedChildrenDirty和_measureLayoutResult,覆盖子项IsVisible触发的父级 measure invalidation。Row.ArrangeOverride():当 arrange width 与上一次 measure width 一致时复用 measure 阶段的LayoutResult,避免重复BuildLayout(finalSize, false)。Row.GetOrderedChildren():使用 static comparison,避免每次 sort 创建 comparison lambda。
3. 新增基准场景
tools/performances/AtomUI.Performance 新增 grid suite:
| Scenario | 覆盖点 |
|---|---|
Grid.Row.Col12x2 |
2 个 Col 半宽排列 |
Grid.Row.Col8x6 |
6 个 Col 三列排列 |
Grid.Row.Wrap.Col6x12 |
12 个 Col wrap 多行 |
Grid.Row.Ordered.Col6x8 |
Order 排序 |
Grid.Row.Responsive.Col8x6 |
responsive Sm/Md layout |
Grid.Row.Gutter.Col6x8 |
horizontal / vertical gutter |
新增参数:
--suite grid
--verify-grid-states
4. 控件级 smoke 数据
Baseline 与 optimized 都是 --suite grid --count 80 单次 smoke;timing 不作为收益证明。
| Scenario | baseline ms/item | optimized ms/item | baseline KB/item | optimized KB/item | Visual | Logical |
|---|---|---|---|---|---|---|
Grid.Row.Col12x2 |
0.231 | 0.244 | 54.1 | 53.6 | 7 | 5 |
Grid.Row.Col8x6 |
0.685 | 0.745 | 150.2 | 149.2 | 19 | 13 |
Grid.Row.Wrap.Col6x12 |
1.425 | 1.608 | 294.3 | 292.6 | 37 | 25 |
Grid.Row.Ordered.Col6x8 |
0.437 | 0.468 | 194.6 | 193.6 | 25 | 17 |
Grid.Row.Responsive.Col8x6 |
0.348 | 0.410 | 148.4 | 147.5 | 19 | 13 |
Grid.Row.Gutter.Col6x8 |
0.392 | 0.457 | 193.4 | 192.4 | 25 | 17 |
5. 验证
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-grid-states
dotnet run -c Release -f net10.0 --no-build --project tools/performances/AtomUI.Performance/AtomUI.Performance.csproj -- --suite grid --count 80
6. Converter Parse Structural Pass
本轮补充 Grid parser 字符串解析路径:
RowJustifyConverter/RowAlignConverter旧实现Trim().ToLowerInvariant()后再匹配别名;第一步去掉 lowercase 临时字符串,本轮继续把Trim()改为ReadOnlySpan<char>.Trim(),fallback 仍交给Enum.Parse(..., ignoreCase: true)。GridColSize.Parse旧实现先Trim(),再Split(','),每个 segment 继续Trim()/ range substring;本轮改为 span 逐段扫描,保留空 segment 跳过、key/value 校验和异常语义。GridColSpanInfo.Parse/GridGutterInfo.Parse单值 fast path 改为 span trim 后直接解析。GridGutter.Parse的;双段和,数值 pair 解析改为 span 扫描,不再为Split(..., RemoveEmptyEntries)创建临时数组;GridGutterInfo.Parse增加 span overload,避免双段解析时重新构造 trimmed string。
| 指标 | 优化前 | 优化后 | 公式 | 提升 | 结论 |
|---|---|---|---|---|---|
| RowJustify alias parse lowercase temp strings / conversion | 1 | 0 | (1 - 0) / 1 |
100.00% | 有效;别名大小写不敏感语义保持 |
| RowAlign alias parse lowercase temp strings / conversion | 1 | 0 | (1 - 0) / 1 |
100.00% | 有效;别名大小写不敏感语义保持 |
| RowJustify alias parse trim temp strings / conversion with surrounding whitespace | 1 | 0 | (1 - 0) / 1 |
100.00% | 结构收益;span trim 保持 trim 语义 |
| RowAlign alias parse trim temp strings / conversion with surrounding whitespace | 1 | 0 | (1 - 0) / 1 |
100.00% | 结构收益;span trim 保持 trim 语义 |
| GridColSize comma split arrays / key-value parse | 1 array | 0 arrays | (1 - 0) / 1 |
100.00% | 结构收益;span 逐段扫描 |
| GridColSize segment/key/value trim temp strings / segment | 3 | 0 | (3 - 0) / 3 |
100.00% | 结构收益;span trim 保持解析语义 |
| GridColSpanInfo single-value trim temp strings / parse | 1 | 0 | (1 - 0) / 1 |
100.00% | 结构收益;span trim fast path |
| GridGutterInfo single-value trim temp strings / parse | 1 | 0 | (1 - 0) / 1 |
100.00% | 结构收益;span trim fast path |
| GridGutter semicolon split arrays / responsive gutter parse | 1 array | 0 arrays | (1 - 0) / 1 |
100.00% | 结构收益;span 双段扫描保持 RemoveEmptyEntries 语义 |
| GridGutter pair split arrays / numeric pair parse | 1 array | 0 arrays | (1 - 0) / 1 |
100.00% | 结构收益;span 双段扫描保持 pair 校验 |
| GridGutterInfo segment trim temp strings / responsive gutter segment | 1 string risk | 0 strings | (1 - 0) / 1 |
100.00% | 结构收益;双段路径直接传 span |
| Page-load timing claim | none | none | n/a | n/a | 本轮没有有效前后 timing,不声明页面级速度收益 |