PropertyChangedEventArgs pooling



有没有人研究过做一个PropertyChangedEventArgs对象池是否有好处?

(对于那些不在主题中的人,我将解释-PropertyChangedEventArgs对象是MVVM patternINotifyPropertyChanged接口的一部分)

例如:

public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
var arg = _pool
.GetOrAdd(propertyName, name => new PropertyChangedEventArgs(name));
PropertyChanged?.Invoke(this, arg);
}
private readonly static ConcurrentDictionary<string, PropertyChangedEventArgs> _pool 
= new();
}

我想减少GC的负载,但同时,String.GetHashCode()方法不缓存,每次都要计算,这增加了CPU的负载。

女士们,先生们,你们怎么看这个问题?

在我看来,这里更有用的事情是如果没有人在听:

不做任何事情
var handler = PropertyChanged;
if (handler is not null)
{
var arg = // TODO: your choice of implementation here
handler.Invoke(this, arg);
}

也可以缩写为:

PropertyChanged?.Invoke(this, /* TODO, initialize inline */);

我不能说我在这里做过研究,但是我有很多使用INPC的经验,包括在XAML以外的环境中。特别是对于我的web应用程序,我使用了一个自己开发的MVVM框架,它通过我自己的绑定框架将React与c#/WASM结合起来。因此,性能,特别是inpc,对我来说是至关重要的。

你在这里谈论的是为每个属性更改通知创建一个新的PropertyChangedEventArgs实例与在ConcurrentDictionary中缓存参数的利弊。

为每个属性更改创建新参数-

  • 在全局堆上分配少量内存,然后对其进行GC。记住。net无论如何都会缓存所有硬编码字符串(你的属性名会是这样),所以你每次实际上并没有为字符串本身分配额外的堆空间,只是为args实例和指向字符串的指针分配额外的堆空间。这些都是非常便宜的操作。

您的方法要求,对于每个属性更改-

  • 计算属性名称字符串
  • 的哈希值
  • 锁定线程(当你使用ConcurrentDictionary时)-便宜但不是免费的
  • 一个O(logN)字典操作,用于查找现有参数或向字典中添加新实例。很明显,当你添加越来越多的有更改通知的属性时,这将是不可忽略的。

在没有运行性能基准测试的情况下,我的经验告诉我,方法#2将比方法#1带来更大的CPU负担。

但是如果你做了性能基准测试我希望你把结果贴出来,会很好奇的。


虽然这不是一个直接的答案,但如果您真的想从中挤出每一个可能的最后一点性能,您可以为每个视图模型类中的每个属性创建一个静态PropertyChangedEventArgs字段,并使用该静态实例在setter中引发PropertyChanged。更多的代码行,但它将实现您的目标。如果它能带来明显的性能提升,我会感到很惊讶。


最后是一个"可爱"字。选项-保持参数的缓存,但作为一个简单的可增长列表,关键是代码的行号。(注意这对partial类不起作用!!)

private static GrowableList<PropertyChangedEventArgs> _argCache;
private void OnPropertyChanged(
[CallerMemberName] 
string property = null,
[CallerLineNumber] 
int key = 0)
{
var args = _argCache[key] ?? (_argCache[key] = new PropertyChangedEventArgs(property));
this.PropertyChanged?.Invoke(this, args);
}

GrowableList只是一个列表类型的对象,它会自动增长到给定的索引,并根据需要插入null的值。

当然,这是浪费内存,但每个类只有一次,但由于它只使用数组索引,它几乎和单个静态字段一样快。

您还可以将这些方法结合起来,并将字典与代码行号结合起来,这也比使用属性名要快一些。

最新更新