runtime: BufferBlock slow?

Been doing some bench-marking and I thought a BufferBlock<T> would be a great underlying collection for an Object Pool. But it just seems to be dramatically slower than a ConcurrentQueue<T>.

Is this simply the nature of the beast? Or isn’t there a way to boost performance?

The reason why I’m not just simply using ConcurrentQueue (because it’s super fast), is that some instances of object construction and recycling can take extra time, and I’d prefer something like a Dataflow pipeline to handle it.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 17 (7 by maintainers)

Commits related to this issue

Most upvoted comments

Just chiming in… if ConcurrentQueue works for your needs, then go ahead and use it. You won’t get much faster than that.

BlockingCollection provides an abstraction over a concurrent collection (defaulting to ConcurrentQueue) that allows producers/consumers to wait for a notfull/notempty signal. AsyncCollection provides (almost) the same abstraction over a concurrent collection (defaulting to ConcurrentQueue) that allows producers/consumers to asynchronously wait for a notfull/notempty signal.

If you don’t need the signals, then you don’t need that abstraction, and can just use ConcurrentQueue directly.

BufferBlock is a much more “batteries included” type of queue. Neither BufferBlock nor BlockingCollection / AsyncCollection attempt to compare performance-wise with ConcurrentQueue, but they bring their own different advantages.

I think he’s referring to the speed of ConcurrentQueue since the default underlying collection of AsyncCollection is … wait for it… a ConcurrentQueue

Yes, but as with any component A that layers functionality over B, A is going to be more expensive, e.g.

using Nito.AsyncEx;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;

class Program
{
    static void Main()
    {
        const int Iters = 1000000;
        var sw = new Stopwatch();

        while (true)
        {
            var ac = new AsyncCollection<int>();
            sw.Restart();
            for (int i = 0; i < Iters; i++)
            {
                ac.Add(i);
                ac.Take();
            }
            sw.Stop();
            Console.WriteLine("AC: " + sw.Elapsed);

            var cq = new ConcurrentQueue<int>();
            sw.Restart();
            for (int i = 0; i < Iters; i++)
            {
                cq.Enqueue(i);
                cq.TryDequeue(out int _);
            }
            sw.Stop();
            Console.WriteLine("CQ: " + sw.Elapsed);

            Console.WriteLine();
        }
    }
}

yields results like:

AC: 00:00:00.8890268
CQ: 00:00:00.0198012

This isn’t a slight against AsyncCollection; anything that layered on such functionality would incur cost. I’m just highlighting why I wrote what I wrote.