When you want to perform a piece of code asynchronously it is tempting to start a dedicated thread to execute this code. However, starting a new thread has a big performance overhead. For simple tasks this overhead cannot be justified (the CPU-time needed to start the thread might be much bigger than the actual processing that will occur in the thread). Microsoft recognizes this and provides the Threadpool which is much better for executing tasks asynchronously.
I decided to write a small benchmark to find out just how bad it is to start a dedicated thread for a simple task. The code of the task is very simple: increment a counter and check the value of this counter. This task has to be executed 1,000,000 times. Of course this is not actual code that you would write; it is just the simplest task that I could think of to execute asynchronously.
Because the counter will be accessed from multiple threads it needs to be protected by a lock. In this particular code it is possible to avoid the lock using the System.Threading.Interlocked class. I have decided not to do this because the use of a normal lock makes the benchmark much more relevant towards real-world scenarios.
Here is the version of the code that uses a dedicated thread for each task (error-handling code and the code that was used to measure the performance has been removed for clarity):
These are the performance measurements (taken on a dual-core processor):
- Total time to execute all the tasks: 18312 ms
- % time in user-mode: 47%
As you can see it takes quite some time to execute all the tasks. Only about half of this time is spent in user mode, the rest is spent in kernel mode.
Now let's rewrite this code using the threadpool. Only minimal changes are needed (they are shown in red):
These are the performance measurements:
- Total time to execute all the tasks: 218 ms.
- % time in user-mode: 98%
So we see that by using the Treadpool we have increased the performance 82 times! Also, almost all time is spent in user-mode.
Conclusion: when executing simple tasks asynchronously, starting a dedicated thread for each task has a very big performance impact.