简体   繁体   English

C#-ConcurrentBag与列表的性能比较

[英]C# - Performance comparison of ConcurrentBag vs List

Preface: I'm only asking this because I don't have an environment (dataset large enough + computing power) to test it in a reliable fashion. 前言:我之所以问这个问题是因为我没有一个环境(足够大的数据集+计算能力)无法以可靠的方式对其进行测试。

Question: Given a Concurrent Bag , loaded with billions of items, being accessed/used by a single thread, does it perform similar to a List ? 问题:给定一个并发袋 ,其中装有数十亿个项目,并且由单个线程访问/使用,它的性能是否类似于列表 Putting in another words, is the enumeration over a Concurrent Bag any more or less performatic than over a List<T> ? 在另一个词语推杆,是枚举在Concurrent Bag任何或多或少performatic比在List<T>

ConcurrentBag<T> will inevitably be less performant than List<T> . ConcurrentBag<T>性能不可避免地要比List<T> Although you will only be accessing it from a single thread, the structure still needs to have mechanisms in place to protect against the possibility of race hazards should concurrent access arise. 尽管您只能从单个线程访问它,但是该结构仍需要有适当的机制来防止发生并发访问时出现竞争危险的可能性。

If you will be loading the collection from a single thread before starting your enumerations, you can avoid the performance overhead by using the ConcurrentBag(IEnumerable<T>) constructor, rather than adding each item individually through its Add method. 如果要开始枚举之前从单个线程加载集合,则可以通过使用ConcurrentBag(IEnumerable<T>)构造函数来避免性能开销,而不是通过其Add方法单独添加每个项目。

ConcurrentBag<T> provides “moment-in-time snapshot” semantics for enumerations; ConcurrentBag<T>为枚举提供“即时快照”语义; see the remarks for its GetEnumerator method. 有关其GetEnumerator方法的说明,请参见。 When you access ConcurrentBag<T> from a foreach loop, it will first copy its entire contents into a plain List<T> , then enumerate over that. 当您从foreach循环访问ConcurrentBag<T> ,它将首先将其全部内容复制到一个普通的List<T> ,然后对其进行枚举。 This will incur a substantial performance overhead (both computation- and memory-wise) each time you use it in a loop. 每次在循环中使用它时,都会产生相当大的性能开销(在计算和内存方面)。

If your scenario is that your list will be populated by multiple threads, but then only read by one thread, then you should convert it to a List<T> as soon as the writers are done. 如果您的情况是列表将由多个线程填充,但是只能由一个线程读取,则编写器完成后应立即将其转换为List<T>

Billions of items and List or Concurrent bag? 数十亿个项目和清单或并发袋? That is a "no go". 那是“不行”。

As far as performance goes try this to test adding: (feel free to modify this to test other operations) 就性能而言,请尝试执行以下测试来添加:(可以随意修改以测试其他操作)

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();
        }
    }
}

My Output: 我的输出:

--- 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

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

Using RAM: 16 GB 使用RAM:16 GB

Also take a look at this answer for limitations. 还请查看此答案的局限性。

But, if you need to remove items, the ConcurrentBag is SIGNIFICANTLY faster than the List 但是,如果您需要删除项目,则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");
}

Results: 结果:

BAG 00:00:00.0144421 00:00:00.0144421

Stack 00:00:00.0341379 堆栈 00:00:00.0341379

Q 00:00:00.0400114 00:00:00.0400114

list remove at 0 00:00:00.6188329 列表从0 00:00:00.6188329删除

list remove at end 00:00:00.6202170 列表从结尾 00:00:00.6202170删除

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM