Delegate.BeginInvoke vs ThreadPool.QueueUserWorkItem

|

Today, I come across Chris Brumme's blog on TransparentProxy, I really enjoy his blog, in particular those dedicated to the inner-working of CLR, one of the surprising sentence I read from his TransparentProxy article is that:

One surprising fact is that this is also why Delegate.BeginInvoke / EndInvoke are so slow compared to equivalent techniques like ThreadPool.QueueUserWorkItem (or UnsafeQueueUserWorkItem if you understand the security implications and want to be really efficient). The codepath for BeginInvoke / EndInvoke quickly turns into the common Message processing code of the general remoting pathway.

   I have to say I get pretty stunned when reading this, so I write a little piece of code to verify his allegation:

using System;
using System.Threading;
using System.Diagnostics;

namespace DelegatePerformance
{
    class Program
    {
        static void Main(string[] args)
        {
            ThreadStart threadStart = new ThreadStart(() => { });
            WaitCallback waitCallback = new WaitCallback(a => { });
            Stopwatch stopWatch = new Stopwatch();

            stopWatch.Start();
            for (int i = 0; i < 10000; i++)
            {
                threadStart.BeginInvoke(null, null);
            }
            stopWatch.Stop();
            Console.WriteLine("Delegate.BeinInvoke(): {0}",stopWatch.ElapsedTicks.ToString());

            stopWatch.Reset();
            stopWatch.Start();
            for (int i = 0; i < 10000; i++)
            {
                System.Threading.ThreadPool.QueueUserWorkItem(waitCallback);
            }
            stopWatch.Stop();
            Console.WriteLine("ThreadPool.QueueUserWorkItem(): {0}", stopWatch.ElapsedTicks.ToString());
        }
    }
}

And the following output is what I get when running above programme:

Delegate.BeinInvoke(): 591441
ThreadPool.QueueUserWorkItem(): 91958

From the output, there is no brainer that Delegate.BeginInvoke is way much slower than the ThreadPool.QueueUserWorkItem call.


 

2 comments:

Anonymous said...

Great post. Thanks for actually profiling instead of solely using rhetorical devices, intuition, rules of thumb, hearsay, etc, to argue performance points, as is extremely popular on Internet forums related to programming.

I profiled QueueUserWorkItem vs. UnsafeQueueUserWorkItem and here are numbers:

ThreadPool.QueueUserWorkItem(): 45881667
ThreadPool.UnsafeQueueUserWorkItem(): 28368351

ThreadPool.QueueUserWorkItem(): 55213866
ThreadPool.UnsafeQueueUserWorkItem(): 31720158

ThreadPool.QueueUserWorkItem(): 47258991
ThreadPool.UnsafeQueueUserWorkItem(): 29558070

ThreadPool.QueueUserWorkItem(): 50587812
ThreadPool.UnsafeQueueUserWorkItem(): 29996415

ThreadPool.QueueUserWorkItem(): 57517047
ThreadPool.UnsafeQueueUserWorkItem(): 26509005

UnsafeQueueUserWorkItem is approximately twice as fast as QueueUserWorkItem, at a cost of call stack security.

Anonymous said...

Geez! That's 6.4 times faster... I'd better start using threadpooling. Thanks for the advise very useful A+++!