Tuesday, 2 October 2012

Monitor Pulse/Wait, ResetEvent and SpinWait

After some lively discussion with about the merits of Monitor Pulse/Wait as a thread synchronization primitive in C# I decided to test the results for myself.

One argument was that Monitor Pulse / Wait performs better than ManualResetEvent due to it running in User mode without the need for Kernel objects and therefore should be used as the preferred thread synchronization mechanism in C# for performance reasons.

Counter arguments were :-

1. That the code would be less maintainable
2. Pulses can be missed in real world production code.
3. If keen on optimum speed through micro optimizations then maybe 'lock free programming' should be considered.

Its hard to draw real conclusions on a system with only 4 cores as really to determine the effect of massive parallelism you would need a NUMA machine with many more cores.

However I tried to test the theory anyway :-

Here is a run with more iterations but the same amount of concurrency and contention (Small runs are unreliable due to general background multi-tasking on the machine. There is also a CLR/JIT warm up cost but I've also ignored this to keep things simple.).

AMD Processor (4 cores)

ManualResetEvent suffers a lot showing the advantage in performance of MonitorWaitAndPulse.

Kernel objects do become expensive if you use a LOT of them, which can be seen here.

What would make more sense would be to up the concurrency, however I cannot up the true concurrency on my hardware, only the total number of threads which would then be time sliced.

The second run was with 10,000 iterations of 120 (40x3) tasks. In real world use I'd recommend using ManualResetEvent until you find you need more performance. Premature optimization is usually a bad idea.

MonitorWaitAndPulse offers good mid performance as long as you can ensure you don't miss pulses and have low contention.

As you can see from this example with mild contention MonitorWaitAndPulse is not a clear winner over lock free code.

As contention rises the locks in MonitorWaitAndPulse will mean its performance will degrade favoring code that is lock free like MySpinLock or the Flag with MemoryFence and SpinWait.

MySpinLock makes use of 'lock free programming' using CompareAndSwap (CAS).

 #pragma warning disable 0420  
 namespace ThreadCommunication  
   struct MySpinLock  
     volatile int _taken;  
     public void Enter()  
       while(Interlocked.CompareExchange(ref _taken, 1, 0) != 0) Thread.Yield() /*spin*/;  
     public void Exit()  
       _taken = 0;  

This allows us to create a SpinLock without using CLR or Kernel locking primitives. Normally you should only spin for short periods, a more typical use would be to simulate a critical section. When using spin locks for short periods you would not want to yield. In this case without the yield we would effectively waste a threads quantum every time slice until the work is done.

We should be able to do better with SpinWait, SpinWait allows us to wait for a condition variable to be set. It also allows us to use a 'backing off' algorithm in order to yield or sleep for longer periods if our wait is 'too busy'.

Note :- The .NET framework also comes with its own SpinLock class, in production code you should use the .NET framework where possible, the MySpinLock code is provided to demonstrate CAS programming principles only. The framework SpinLock is specifically designed for short running operations and favors thread affinity.

Code can be downloaded here.