在开始一个项目之前,我写了一个简单的测试来比较 (System.Collections.Concurrent) 中的 ConcurrentBag 相对于锁定 & 列表的性能。我非常惊讶 ConcurrentBag 比使用简单列表锁定要慢 10 倍以上。据我了解,当读者和作者是同一个线程时,ConcurrentBag 效果最好。但是,我没想到它的性能会比传统锁差那么多。
我已经使用两个 Parallel for 循环写入和读取列表/包进行了测试。但是,写入本身显示出巨大的差异:
private static void ConcurrentBagTest()
{
int collSize = 10000000;
Stopwatch stopWatch = new Stopwatch();
ConcurrentBag<int> bag1 = new ConcurrentBag<int>();
stopWatch.Start();
Parallel.For(0, collSize, delegate(int i)
{
bag1.Add(i);
});
stopWatch.Stop();
Console.WriteLine("Elapsed Time = {0}",
stopWatch.Elapsed.TotalSeconds);
}
在我的机器上,这需要 3-4 秒才能运行,而这段代码需要 0.5 - 0.9 秒:
private static void LockCollTest()
{
int collSize = 10000000;
object list1_lock=new object();
List<int> lst1 = new List<int>(collSize);
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
Parallel.For(0, collSize, delegate(int i)
{
lock(list1_lock)
{
lst1.Add(i);
}
});
stopWatch.Stop();
Console.WriteLine("Elapsed = {0}",
stopWatch.Elapsed.TotalSeconds);
}
正如我所提到的,进行并发读写对并发包测试没有帮助。我做错了什么还是这个数据结构真的很慢?
[编辑] - 我删除了任务,因为我在这里不需要它们(完整代码有另一个任务阅读)
[编辑] 非常感谢您的回答。我很难选择“正确答案”,因为它似乎是几个答案的混合体。
正如 Michael Goldshteyn 所指出的,速度真的取决于数据。Darin 指出 ConcurrentBag 应该有更多的争用更快,而 Parallel.For 不一定启动相同数量的线程。要带走的一点是不要做任何你不必在锁内做的事情。在上述情况下,我看不到自己在锁内做任何事情,除了可能将值分配给临时变量。
此外,sixlettervariables 指出,恰好正在运行的线程数也可能会影响结果,尽管我尝试以相反的顺序运行原始测试并且 ConcurrentBag 仍然较慢。
我从 15 个任务开始进行了一些测试,结果取决于集合大小等。但是,对于多达 100 万次插入,ConcurrentBag 的性能几乎与锁定列表一样好或更好。超过 100 万,有时锁定似乎要快得多,但我的项目可能永远不会有更大的数据结构。这是我运行的代码:
int collSize = 1000000;
object list1_lock=new object();
List<int> lst1 = new List<int>();
ConcurrentBag<int> concBag = new ConcurrentBag<int>();
int numTasks = 15;
int i = 0;
Stopwatch sWatch = new Stopwatch();
sWatch.Start();
//First, try locks
Task.WaitAll(Enumerable.Range(1, numTasks)
.Select(x => Task.Factory.StartNew(() =>
{
for (i = 0; i < collSize / numTasks; i++)
{
lock (list1_lock)
{
lst1.Add(x);
}
}
})).ToArray());
sWatch.Stop();
Console.WriteLine("lock test. Elapsed = {0}",
sWatch.Elapsed.TotalSeconds);
// now try concurrentBag
sWatch.Restart();
Task.WaitAll(Enumerable.Range(1, numTasks).
Select(x => Task.Factory.StartNew(() =>
{
for (i = 0; i < collSize / numTasks; i++)
{
concBag.Add(x);
}
})).ToArray());
sWatch.Stop();
Console.WriteLine("Conc Bag test. Elapsed = {0}",
sWatch.Elapsed.TotalSeconds);