【发布时间】:2018-09-30 18:57:58
【问题描述】:
最近阅读了关于不可变集合的内容。 当读取操作的执行频率高于写入操作时,建议将它们用作读取的线程安全。
然后我想测试读取性能ImmutableDictionary 与ConcurrentDictionary。这是这个非常简单的测试(在 .NET Core 2.1 中):
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace ImmutableSpeedTests
{
class Program
{
public class ConcurrentVsImmutable
{
public int ValuesCount;
public int ThreadsCount;
private ImmutableDictionary<int, int> immutable = ImmutableDictionary<int, int>.Empty;
private ConcurrentDictionary<int, int> concurrent = new ConcurrentDictionary<int, int>();
public ConcurrentVsImmutable(int valuesCount, int threadsCount)
{
ValuesCount = valuesCount;
ThreadsCount = threadsCount;
}
public void Setup()
{
// fill both collections. I don't measure time cause immutable is filling much slower obviously.
for (var i = 0; i < ValuesCount; i++)
{
concurrent[i] = i;
immutable = immutable.Add(i, i);
}
}
public async Task<long> ImmutableSum() => await Sum(immutable);
public async Task<long> ConcurrentSum() => await Sum(concurrent);
private async Task<long> Sum(IReadOnlyDictionary<int, int> dic)
{
var tasks = new List<Task<long>>();
// main job. Run multiple tasks to sum all values.
for (var i = 0; i < ThreadsCount; i++)
tasks.Add(Task.Run(() =>
{
long x = 0;
foreach (var key in dic.Keys)
{
x += dic[key];
}
return x;
}));
var result = await Task.WhenAll(tasks.ToArray());
return result.Sum();
}
}
static void Main(string[] args)
{
var test = new ConcurrentVsImmutable(1000000, 4);
test.Setup();
var sw = new Stopwatch();
sw.Start();
var result = test.ConcurrentSum().Result;
sw.Stop();
// Convince that the result of the work is the same
Console.WriteLine($"Concurrent. Result: {result}. Elapsed: {sw.ElapsedTicks}.");
sw.Reset();
sw.Start();
result = test.ImmutableSum().Result;
sw.Stop();
Console.WriteLine($" Immutable. Result: {result}. Elapsed: {sw.ElapsedTicks}.");
Console.ReadLine();
}
}
}
您可以运行此代码。以滴答为单位的经过时间会不时变化,但ConcurrentDictionary 花费的时间比ImmutableDictionary 少几倍。
这个实验让我很尴尬。我做错了吗?如果我们有并发,使用不可变集合的原因是什么?什么时候更可取?
【问题讨论】:
-
虽然我无法解释你的结果,但我可以说基准测试并不是一件简单的事情。有完整的框架专门用于确保正确、清晰和稳健的结果。我建议至少使用不同的配置和循环数运行您的测试。这里可能会有大量的数据,尤其是因为它涉及到并发性,这总是会增加更多的复杂性
-
@Dave 是的,我使用
BenchmarkDotNet进行了相同的实验,线程数为 1、2 和 4。结果相同 - 从ConcurrentDictionary读取更快。 -
我明白了。与糟糕的基准测试、并发性或任务无关。 ImmutableDictionary 的索引器只是慢的尴尬,这个皇帝没有衣服。考虑提交性能错误,github.com/dotnet/corefx/issues
-
@HansPassant 您如何看待 Akash Kava 的答案?树的索引器可以更快吗?
-
我认为它对你没有帮助。我没有仔细看他们使用的存储算法,如果它实际上是一棵树,那就放弃所有希望。 Dictionary 和 ConcurrentDictionary 都不使用树,数组对于引用的局部性非常重要。顺便说一句,您的测试带来了树设计的最糟糕之处,字典往往以一种使对象添加更加分散的方式建立起来,因此失去了数组的一些优势。我还尝试将密钥设为字符串,但它仍然慢 3 倍。使用你从这次测试中学到的东西,它是准确的。
标签: c# .net concurrency concurrent-collections immutable-collections