【问题标题】:C# - Performance comparison of ConcurrentBag vs ListC# - ConcurrentBag 与 List 的性能比较
【发布时间】:2016-02-15 18:30:27
【问题描述】:

前言:我问这个只是因为我没有一个环境(足够大的数据集 + 计算能力)来以可靠的方式对其进行测试。

问题:给定一个Concurrent Bag,加载了数十亿个项目,被单个线程访问/使用,它的性能是否类似于List?换句话说,Concurrent Bag 上的枚举是否比List<T> 上的性能更高或更低?

【问题讨论】:

  • 您的基准测试结果让您得出什么结论?
  • @LucasTrzesniewski 我的基准测试没有定论,因为测试结果非常疯狂。正如问题中提到的,我的开发环境非常弱(仅 8gb ram 和 i3 处理器),我没有一个好的数据集来真正强调它......
  • 您将如何访问该集合?你会一直列举它吗? (ConcurrentBag<T> 没有索引器。)
  • @Douglas 包可以迭代,如foreach
  • 如果您只打算从单个线程中使用它,为什么还要使用专门设计为可从多个线程访问的集合?另请注意,在不知道您实际使用该集合做什么的情况下,我们不可能回答这样的问题。

标签: c# .net concurrent-collections


【解决方案1】:

ConcurrentBag<T> 的性能将不可避免地低于List<T>。尽管您只能从单个线程访问它,但该结构仍然需要有适当的机制来防止出现并发访问时出现竞争风险的可能性。

如果您将在开始枚举之前从单个线程加载集合,则可以通过使用ConcurrentBag(IEnumerable<T>) 构造函数来避免性能开销,而不是通过@987654325 单独添加每个项目@方法。

ConcurrentBag<T> 为枚举提供“即时快照”语义;请参阅其GetEnumerator 方法的备注。当您从foreach 循环访问ConcurrentBag<T> 时,它会首先将其全部内容复制到一个普通的List<T> 中,然后对其进行枚举。每次在循环中使用它时,都会产生大量的性能开销(计算和内存方面)。

如果您的情况是您的列表将由多个线程填充,但随后仅由一个线程读取,那么您应该在编写器完成后立即将其转换为 List<T>

【讨论】:

    【解决方案2】:

    数十亿的项目和列表或并发包?那是“不行”。

    就性能而言,试试这个来测试添加:(随意修改这个来测试其他操作)

    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace ConcurrentBagTest
    {
        // You must compile this for x64 or you will get OutOfMemory exception
        class Program
        {
            static void Main(string[] args)
            {
                ListTest(10000000);
                ListTest(100000000);
                ListTest(1000000000);
                ConcurrentBagTest(10000000);
                ConcurrentBagTest(100000000);
    
                Console.ReadKey();
    
            }
    
    
            static void ConcurrentBagTest(long count)
            {
                try
                {
                    var bag = new ConcurrentBag<long>();
                    Console.WriteLine($"--- ConcurrentBagTest count = {count}");
                    Console.WriteLine($"I will use {(count * sizeof(long)) / Math.Pow(1024, 2)} MiB of RAM");
                    Stopwatch stopwatch = new Stopwatch();
                    stopwatch.Start();
                    for (long i = 0; i < count; i++)
                    {
                        bag.Add(i);
                    }
                    stopwatch.Stop();
                    Console.WriteLine($"Inserted {bag.LongCount()} items in {stopwatch.Elapsed.TotalSeconds} s");
                    Console.WriteLine();
                    Console.WriteLine();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
    
            static void ListTest(long count)
            {
                try
                {
                    var list = new List<long>();
                    Console.WriteLine($"--- ListTest count = {count}");
                    Console.WriteLine($"I will use {(count * sizeof(long)) / Math.Pow(1024, 2)} MiB of RAM");
                    Stopwatch stopwatch = new Stopwatch();
                    stopwatch.Start();
                    for (long i = 0; i < count; i++)
                    {
                        list.Add(i);
                    }
                    stopwatch.Stop();
                    Console.WriteLine($"Inserted {list.LongCount()} items in {stopwatch.Elapsed.TotalSeconds} s");
                    Console.WriteLine();
                    Console.WriteLine();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
        }
    }
    

    我的输出:

    --- ListTest count = 10000000
    I will use 76,2939453125 MiB of RAM
    Inserted 10000000 items in 0,0807315 s
    
    
    --- ListTest count = 100000000
    I will use 762,939453125 MiB of RAM
    Inserted 100000000 items in 0,7741546 s
    
    
    --- ListTest count = 1000000000
    I will use 7629,39453125 MiB of RAM
    System.OutOfMemoryException: Array dimensions exceeded supported range.
    
    --- ConcurrentBagTest count = 10000000
    I will use 76,2939453125 MiB of RAM
    Inserted 10000000 items in 1,0744069 s
    
    
    --- ConcurrentBagTest count = 100000000
    I will use 762,939453125 MiB of RAM
    Inserted 100000000 items in 11,3976436 s
    

    使用 CPU:Intel Core i7-2600 @ 3.4 GHz,

    使用内存:16 GB

    还可以查看this answer 了解限制。

    【讨论】:

      【解决方案3】:

      但是,如果您需要删除项目,ConcurrentBag 比 List 快得多

      void Main()
      {
          ConcurrentBag<int> bag = new ConcurrentBag<int>();
          ConcurrentStack<int> stack = new ConcurrentStack<int>();
          ConcurrentQueue<int> q = new ConcurrentQueue<int>();
          List<int> list = new List<int>();
      
          Stopwatch sw = new Stopwatch();
          int count = 100000;
          sw.Start();
          for (int i = 0; i < count; i++)
          {
              bag.Add(i);
          }
          for (int i = 0; i< count; i++)
          {
              bag.TryTake(out _);
          }
          sw.Elapsed.Dump("BAG");
          sw.Start();
          for (int i = 0; i < count; i++)
          {
              stack.Push(i);
          }
          for (int i = 0; i < count; i++)
          {
              stack.TryPop(out _);
          }
          sw.Elapsed.Dump("Stack");
          sw.Start();
          for (int i = 0; i < count; i++)
          {
              q.Enqueue(i);
          }
          for (int i = 0; i < count; i++)
          {
              q.TryDequeue(out _);
          }
          sw.Elapsed.Dump("Q");
          sw.Start();
          for (int i = 0; i < count; i++)
          {
              list.Add(i);
          }
          for (int i = 0; i < count; i++)
          {
              list.RemoveAt(0);
          }
          sw.Elapsed.Dump("list remove at 0");
          sw.Start();
          for (int i = 0; i < count; i++)
          {
              list.Add(i);
          }
          for (int i = 0; i < count; i++)
          {
              list.RemoveAt(list.Count -1);
          }
          sw.Elapsed.Dump("list remove at end");
      }
      

      结果:

      00:00:00.0144421

      堆栈 00:00:00.0341379

      00:00:00.0400114

      在 0 处删除列表 00:00:00.6188329

      在末尾删除列表 00:00:00.6202170

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-10-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多