最近阅读了有关不可变集合的信息。 当读取操作比写入操作执行得更多时,建议将它们用作安全读取的线程。
然后我要测试读取性能ImmutableDictionary与ConcurrentDictionary。这是一个非常简单的测试(在.NET Core 2.1中):
convert(datetime, 'Dec 30 2006 12:38AM', 109)
您可以运行此代码。滴答度过的时间有时会有所不同,但是ConcurrentDictionary花费的时间比ImmutableDictionary少几倍。
这个实验让我很尴尬。我做错了吗?如果有并发,使用不可变集合的原因是什么?什么时候更合适?
谢谢。
答案 0 :(得分:3)
不可修改的集合不能替代并发集合。而且它们被设计为减少内存消耗的方式,它们势必会变慢,这里要权衡的是使用更少的内存,从而通过使用更少的n个操作来做任何事情。
我们通常将集合复制到其他集合以实现持久化状态的不变性。让我们看看它的意思,
var s1 = ImmutableStack<int>.Empty;
var s2 = s1.Push(1);
// s2 = [1]
var s3 = s2.Push(2);
// s2 = [1]
// s3 = [1,2]
// notice that s2 has only one item, it is not modified..
var s4 = s3.Pop(ref var i);
// s2 = [1];
// still s2 has one item...
请注意,s2始终只有一项。即使所有项目都已删除。
内部存储所有数据的方式是一棵大树,您的集合指向一个分支,该分支具有代表该树的初始状态的后代。
我认为性能不能与目标完全不同的并发收集相匹配。
在并发收集中,所有线程都可以访问该收集的单个副本。
在不可变集合中,您实际上是一棵树的孤立副本,导航该树总是很昂贵的。
它在事务系统中很有用,在该系统中,如果必须回滚事务,则收集状态可以保留在提交点中。
答案 1 :(得分:2)
如Akash所说,ImmutableDictionary
使用内部树而不是哈希集。
一方面,如果您一步构建字典,而不是迭代地添加所有键,则可以稍微提高性能:
immutable = concurrent.ToImmutableDictionary();
枚举哈希集和平衡树都是O(n)
操作。对于不同的容器大小,我在单个线程上进行了几次平均运行,并得到与之一致的结果:
我不知道为什么不变坡度要陡6倍。现在,我只假设它在做棘手的非阻塞树。我认为该类将针对随机存储和读取而不是枚举进行优化。
要确定ImmutableDictionary
赢了什么确切情况,我们需要包装并发字典以提供一定程度的不变性,并面对读写争用级别测试这两个类。
这不是一个认真的建议,但您要进行测试的一个对策是使用不变性通过以下比较来“欺骗” 多次迭代:
private ConcurrentDictionary<object, long> cache = new ConcurrentDictionary<object, long>();
public long ImmutableSum()
{
return cache.GetOrAdd(immutable, (obj) => (obj as ImmutableDictionary<int, int>).Sum(kvp => (long)kvp.Value));
}
public long ConcurrentSum() => concurrent.Sum(kvp => (long)kvp.Value);
这对后续调用求和一个未更改的集合有很大的不同!