在开始一个项目之前,我写了一个简单的测试来比较 (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);