GetOrAdd 新的与出厂性能

GetOrAdd new vs factory performance

以下两段代码中哪一段在不同情况下表现更好,为什么?

1.

private readonly ConcurrentDictionary<int, List<T>> _coll;
_coll.GetOrAdd(1, new List<T>());

这会在每次调用时创建一个新的 List,即使在不需要时也是如此(如果我们将 capacity 作为 0 传递,此语句还有多少意义?)。

2.

private readonly ConcurrentDictionary<int, List<T>> _coll;
_coll.GetOrAdd(1, (val) => new List<T>());

这只按需创建 List,但有委托调用。

我无法想象您会发现性能有很大差异,除非您使用的是非常大的数据集。它还取决于您的每个项目被击中的可能性。泛型在运行时级别得到了非常好的优化,使用委托会导致分配。

我的建议是使用 Enumerable.Empty<T>(),因为您将为自己节省每个数组项的分配。

在内存方面,第一种方式每次都会导致分配,而第二种方式将使用缓存的委托对象,因为它不捕获任何变量。编译器处理缓存委托的生成。容量设置为零的第一种情况没有区别,因为 List<T> 的默认构造函数在初始化时使用空数组,与显式容量为 0 相同。

在执行指令方面,由于没有使用第二个参数,因此在找到密钥时它们是相同的。如果找不到密钥,第一种方法只需读取局部变量,而第二种方法将有一个间接层来调用委托。此外,looking into the source code,带有工厂的 GetOrAdd 似乎将进行额外的查找(通过 TryGetValue)以避免调用工厂。委托也可能被执行多次。 GetOrAdd 只是保证您在字典中看到一个条目,而不是只调用一次工厂。

总而言之,如果通常找不到密钥,第一种方法可能会更高效,因为无论如何都需要进行分配,并且没有通过委托进行间接访问。但是,如果通常找到密钥,则第二种方法性能更高,因为分配更少。对于缓存中的实现,您通常希望有很多命中,所以如果是这样的话,我会推荐第二种方法。实际上,两者之间的差异取决于整个应用程序对此代码路径中的分配有多敏感。

此外,使用它的任何实现都可能需要在返回的 List<T> 周围实现锁定,因为它不是 thread-safe。