10 KiB
10 KiB
Performance Optimization
本节定义性能优化策略和最佳实践,确保控件库满足 NFR 性能要求。
Performance Targets (from NFR)
| 指标 | 目标值 | 测量方法 |
|---|---|---|
| UI 渲染帧率 | 60fps (16ms/frame) | Visual Studio Performance Profiler |
| 主题切换时间 | < 100ms | Stopwatch 测量 |
| PropertyGrid 生成(50 属性) | < 200ms | Stopwatch 测量 |
| 应用启动时间 | < 2s | 从启动到窗口显示 |
| 内存占用 | < 100MB (空闲时) | Task Manager / dotnet-counters |
UI Rendering Performance
关键原则:避免在 UI 线程执行耗时操作
❌ 反面示例 - 阻塞 UI 线程:
// 错误:在属性 getter 中执行反射
public ObservableCollection<PropertyItem> Properties
{
get
{
var items = new ObservableCollection<PropertyItem>();
if (SelectedObject != null)
{
// 耗时操作会阻塞 UI 渲染
var properties = SelectedObject.GetType().GetProperties();
foreach (var prop in properties)
{
items.Add(CreatePropertyItem(SelectedObject, prop));
}
}
return items;
}
}
✅ 正确示例 - 异步加载:
private ObservableCollection<PropertyItem> _properties = new();
public ObservableCollection<PropertyItem> Properties => _properties;
private async void RefreshProperties(object? obj)
{
_properties.Clear();
if (obj == null) return;
// 在后台线程执行反射
var items = await Task.Run(() =>
{
var result = new List<PropertyItem>();
var properties = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in properties)
{
result.Add(CreatePropertyItem(obj, prop));
}
return result;
});
// 回到 UI 线程更新集合
foreach (var item in items)
{
_properties.Add(item);
}
}
Reflection Optimization
问题:反射是 PropertyGrid 的性能瓶颈
优化策略 1:缓存 PropertyInfo
private static readonly ConcurrentDictionary<Type, PropertyInfo[]> _propertyCache = new();
private PropertyInfo[] GetCachedProperties(Type type)
{
return _propertyCache.GetOrAdd(type, t =>
t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
);
}
优化策略 2:缓存 Attribute 读取
private static readonly ConcurrentDictionary<PropertyInfo, CategoryAttribute?> _categoryCache = new();
private string? GetCategory(PropertyInfo prop)
{
var attr = _categoryCache.GetOrAdd(prop, p =>
p.GetCustomAttribute<CategoryAttribute>()
);
return attr?.Category;
}
优化策略 3:使用编译表达式替代反射调用(可选)
// 反射调用(慢)
var value = propertyInfo.GetValue(obj);
// 编译表达式(快 10-100 倍)
private static Func<object, object> CreateGetter(PropertyInfo prop)
{
var instance = Expression.Parameter(typeof(object), "instance");
var convert = Expression.Convert(instance, prop.DeclaringType!);
var property = Expression.Property(convert, prop);
var convertResult = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<object, object>>(convertResult, instance).Compile();
}
性能对比:
反射 GetValue: ~1000 ns/调用
编译表达式: ~10 ns/调用
直接属性访问: ~1 ns/调用
Theme Switching Performance
优化主题切换速度(目标 < 100ms):
❌ 反面示例 - 逐个替换资源:
// 错误:逐个替换资源会触发多次 UI 刷新
foreach (var key in themeColors.Keys)
{
Application.Current.Resources[key] = themeColors[key];
}
✅ 正确示例 - 批量替换 ResourceDictionary:
public static void ApplyTheme(ThemeType type)
{
var app = Application.Current;
if (app == null) return;
// 移除旧主题
var oldTheme = app.Resources.MergedDictionaries
.FirstOrDefault(d => d.Source?.ToString().Contains("Theme") == true);
if (oldTheme != null)
app.Resources.MergedDictionaries.Remove(oldTheme);
// 一次性加载新主题(单次 UI 刷新)
var uri = type == ThemeType.Light
? new Uri("avares://Penguin.AvaloniaUI/Themes/LightTheme.axaml")
: new Uri("avares://Penguin.AvaloniaUI/Themes/DarkTheme.axaml");
var newTheme = new ResourceDictionary { Source = uri };
app.Resources.MergedDictionaries.Add(newTheme);
_currentTheme = type;
ThemeChanged?.Invoke(null, type);
}
预加载主题资源(可选优化):
private static ResourceDictionary? _cachedLightTheme;
private static ResourceDictionary? _cachedDarkTheme;
static ThemeManager()
{
// 应用启动时预加载主题
_cachedLightTheme = LoadThemeResource(ThemeType.Light);
_cachedDarkTheme = LoadThemeResource(ThemeType.Dark);
}
public static void ApplyTheme(ThemeType type)
{
var theme = type == ThemeType.Light ? _cachedLightTheme : _cachedDarkTheme;
// 直接使用缓存的主题,无需重新加载
}
Memory Management
避免内存泄漏的常见陷阱:
问题 1:事件订阅未取消
// ❌ 可能导致内存泄漏
public class PropertyGrid : TemplatedControl
{
public PropertyGrid()
{
ThemeManager.ThemeChanged += OnThemeChanged;
// 没有取消订阅!
}
private void OnThemeChanged(object? sender, ThemeType type)
{
RefreshTheme();
}
}
// ✅ 正确实现 IDisposable
public class PropertyGrid : TemplatedControl, IDisposable
{
public PropertyGrid()
{
ThemeManager.ThemeChanged += OnThemeChanged;
}
public void Dispose()
{
ThemeManager.ThemeChanged -= OnThemeChanged;
}
private void OnThemeChanged(object? sender, ThemeType type)
{
RefreshTheme();
}
}
问题 2:ObservableCollection 过度使用
// ❌ 每次都创建新集合
public ObservableCollection<PropertyItem> Properties
{
get => new ObservableCollection<PropertyItem>(GetProperties());
}
// ✅ 复用同一个集合
private ObservableCollection<PropertyItem> _properties = new();
public ObservableCollection<PropertyItem> Properties => _properties;
private void RefreshProperties()
{
_properties.Clear();
foreach (var item in GetProperties())
{
_properties.Add(item);
}
}
Virtualization (Post-MVP)
如果 PropertyGrid 需要支持数百个属性,使用虚拟化:
<ItemsControl Items="{Binding Properties}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- VirtualizingStackPanel 仅渲染可见项 -->
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
虚拟化性能对比:
100 个属性(非虚拟化): ~300ms 生成 + 50MB 内存
100 个属性(虚拟化): ~100ms 生成 + 10MB 内存
Avoid Common Pitfalls
陷阱 1:在循环中触发属性变化通知
// ❌ 每次循环触发 UI 刷新
for (int i = 0; i < 1000; i++)
{
Properties.Add(new PropertyItem()); // 触发 CollectionChanged
}
// ✅ 批量添加后触发一次刷新
var items = new List<PropertyItem>();
for (int i = 0; i < 1000; i++)
{
items.Add(new PropertyItem());
}
Properties.Clear();
foreach (var item in items)
{
Properties.Add(item);
}
陷阱 2:频繁的字符串拼接
// ❌ 每次拼接创建新字符串
string result = "";
for (int i = 0; i < 100; i++)
{
result += properties[i].Name + ", ";
}
// ✅ 使用 StringBuilder
var sb = new StringBuilder();
for (int i = 0; i < 100; i++)
{
sb.Append(properties[i].Name).Append(", ");
}
var result = sb.ToString();
陷阱 3:XAML 中的复杂绑定
<!-- ❌ 每次渲染都执行转换 -->
<TextBlock Text="{Binding PropertyType, Converter={StaticResource ComplexConverter}}" />
<!-- ✅ 在 ViewModel 中预计算 -->
<TextBlock Text="{Binding PropertyTypeDisplayName}" />
Performance Measurement
使用 Stopwatch 测量关键操作:
#if DEBUG
private void RefreshProperties(object? obj)
{
var sw = Stopwatch.StartNew();
// 核心逻辑
_properties.Clear();
var items = GetProperties(obj);
foreach (var item in items)
{
_properties.Add(item);
}
sw.Stop();
Debug.WriteLine($"[PropertyGrid] RefreshProperties took {sw.ElapsedMilliseconds}ms for {items.Count} properties");
}
#endif
Visual Studio Performance Profiler:
- 调试 → 性能探查器
- 选择"CPU 使用情况"或".NET 对象分配跟踪"
- 启动分析
- 执行性能关键操作(如生成 50 个属性)
- 停止分析并查看热点代码
dotnet-counters 监控内存:
# 安装工具
dotnet tool install --global dotnet-counters
# 监控运行中的应用
dotnet-counters monitor --process-id <pid> --counters System.Runtime
Optimization Checklist
开发时检查清单:
- 反射操作已缓存(PropertyInfo、Attribute)
- 耗时操作(> 50ms)在后台线程执行
- 事件订阅在控件销毁时取消
- ObservableCollection 复用而非重建
- 批量更新集合,避免频繁触发 CollectionChanged
- 字符串拼接使用 StringBuilder
- XAML 绑定避免复杂转换器
- 主题切换批量替换 ResourceDictionary
发布前性能测试:
- PropertyGrid 50 属性生成 < 200ms
- 主题切换 < 100ms
- 应用启动 < 2s
- 空闲时内存 < 100MB
- 无明显的 UI 卡顿(帧率 > 50fps)