Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET 4.0 Concurrent collection performance

I am trying to write a program where i schedule items for removal by putting them in a collection from different threads and cleaning them up in a single thread that iterates of the collection and disposes the items.

Before doing so, I wondered what would yield the optimal performance so I've tried ConcurrentBag, ConcurrentStack and ConcurrentQueue and measured the time required to add 10000000 items.

I used the following program to test this:

class Program
{
    static List<int> list = new List<int>();
    static ConcurrentBag<int> bag = new ConcurrentBag<int>();
    static ConcurrentStack<int> stack = new ConcurrentStack<int>();
    static ConcurrentQueue<int> queue = new ConcurrentQueue<int>();
    static void Main(string[] args)
    {
        run(addList);
        run(addBag);
        run(addStack);
        run(addQueue);
        Console.ReadLine();
    }

    private static void addList(int obj) { lock (list) { list.Add(obj); } }

    private static void addStack(int obj) { stack.Push(obj); }

    private static void addQueue(int obj) { queue.Enqueue(obj); }

    private static void addBag(int obj) { bag.Add(obj); }



    private static void run(Action<int> action)
    {
        Stopwatch stopwatch = Stopwatch.StartNew();
        Parallel.For(0, 10000000, new ParallelOptions() { MaxDegreeOfParallelism = # }, action);
        stopwatch.Stop();
        Console.WriteLine(action.Method.Name + " takes " + stopwatch.Elapsed);
    }
}

where # is the amount of threads used.

but the results are rather confusing:

with 8 threads:

  • addList takes 00:00:00.8166816
  • addBag takes 00:00:01.0368712
  • addStack takes 00:00:01.0902852
  • addQueue takes 00:00:00.6555039

with 1 thread:

  • addList takes 00:00:00.3880958
  • addBag takes 00:00:01.5850249
  • addStack takes 00:00:01.2764924
  • addQueue takes 00:00:00.4409501

so, no matter how many threads, it seems that just locking a plain old list is faster then using any of the concurrent collections, except, perhaps the queue if it needs to handle a lot of writes.

EDIT: after the comments below about Garbage and Debug build: Yes, this influences the benchmark. Debug build influence would be linear, Garbage would be increasing with higher memory usage.

Yet running the same test multiple times gives roughly the same results.

I moved the initialization of the collection to right before the test run and collect the garbage after the run now, like this:

        list = new List<int>();
        run(addList);
        list = null;
        GC.Collect();

with MaxDegreeOfParallelism set to 8 i get the following results:

  • addList takes 00:00:7959546
  • addBag takes 00:00:01.08023823
  • addStack takes 00:00:01.1354566
  • addQueue takes 00:00:00.6597145

with give or take 0.02 seconds deviation each time i run the code.

like image 420
Stijn Tallon Avatar asked Oct 11 '13 19:10

Stijn Tallon


1 Answers

The concurrent collections are not always faster. more of them only see perf gains at higher levels of contention, and the actual workload has an impact as well. Check out this paper from the pfx team :)

http://blogs.msdn.com/b/pfxteam/archive/2010/04/26/9997562.aspx

Beware of premature optimization though. put something together that works and then optimize. especially since the actual workload is important. also, having locks as a perf bottleneck is pretty ware, usually there is some io or other algorithm that takes far longer :)

like image 65
aL3891 Avatar answered Sep 18 '22 00:09

aL3891