.NET 4: “Slim”-style performance boost!
- by Vitus
RTM version of .NET 4 and Visual Studio 2010 is available, and now we can do some test with it.
Parallel Extensions is one of the most valuable part of .NET 4.0. It’s a set of good tools for easily consuming multicore hardware power. And it also contains some “upgraded” sync primitives – Slim-version.
For example, it include updated variant of widely known ManualResetEvent. For people, who don’t know about it: you can sync concurrency execution of some pieces of code with this sync primitive. Instance of ManualResetEvent can be in 2 states: signaled and non-signaled. Transition between it possible by Set() and Reset() methods call. Some shortly explanation:
Thread 1
Thread 2
Time
mre.Reset();
mre.WaitOne();
//code execution
0
//wating
//code execution
1
//wating
//code execution
2
//wating
//code execution
3
//wating
mre.Set();
4
//code execution
//…
5
Upgraded version of this primitive is ManualResetEventSlim. The idea in decreasing performance cost in case, when only 1 thread use it. Main concept in the “hybrid sync schema”, which can be done as following:
internal sealed class SimpleHybridLock : IDisposable
{
private Int32 m_waiters = 0;
private AutoResetEvent m_waiterLock = new AutoResetEvent(false);
public void Enter()
{
if (Interlocked.Increment(ref m_waiters) == 1)
return;
m_waiterLock.WaitOne();
}
public void Leave()
{
if (Interlocked.Decrement(ref m_waiters) == 0)
return;
m_waiterLock.Set();
}
public void Dispose()
{
m_waiterLock.Dispose();
}
}
It’s a sample from Jeffry Richter’s book “CLR via C#”, 3rd edition. Primitive SimpleHybridLock have two public methods: Enter() and Leave(). You can put your concurrency-critical code between calls of these methods, and it would executed in only one thread at the moment. Code is really simple: first thread, called Enter(), increase counter. Second thread also increase counter, and suspend while m_waiterLock is not signaled. So, if we don’t have concurrent access to our lock, “heavy” methods WaitOne() and Set() will not called. It’s can give some performance bonus.
ManualResetEvent use the similar idea. Of course, it have more “smart” technics inside, like a checking of recursive calls, and so on. I want to know a real difference between classic ManualResetEvent realization, and new –Slim. I wrote a simple “benchmark”:
class Program
{
static void Main(string[] args)
{
ManualResetEventSlim mres = new ManualResetEventSlim(false);
ManualResetEventSlim mres2 = new ManualResetEventSlim(false);
ManualResetEvent mre = new ManualResetEvent(false);
long total = 0;
int COUNT = 50;
for (int i = 0; i < COUNT; i++)
{
mres2.Reset();
Stopwatch sw = Stopwatch.StartNew();
ThreadPool.QueueUserWorkItem((obj) =>
{
//Method(mres, true);
Method2(mre, true);
mres2.Set();
});
//Method(mres, false);
Method2(mre, false);
mres2.Wait();
sw.Stop();
Console.WriteLine("Pass {0}: {1} ms", i, sw.ElapsedMilliseconds);
total += sw.ElapsedMilliseconds;
}
Console.WriteLine();
Console.WriteLine("===============================");
Console.WriteLine("Done in average=" + total / (double)COUNT);
Console.ReadLine();
}
private static void Method(ManualResetEventSlim mre, bool value)
{
for (int i = 0; i < 9000000; i++)
{
if (value)
{
mre.Set();
}
else
{
mre.Reset();
}
}
}
private static void Method2(ManualResetEvent mre, bool value)
{
for (int i = 0; i < 9000000; i++)
{
if (value)
{
mre.Set();
}
else
{
mre.Reset();
}
}
}
}
I use 2 concurrent thread (the main thread and one from thread pool) for setting and resetting ManualResetEvents, and try to run test COUNT times, and calculate average execution time. Here is the results (I get it on my dual core notebook with T7250 CPU and Windows 7 x64):
ManualResetEvent
ManualResetEventSlim
Difference is obvious and serious – in 10 times!
So, I think preferable way is using ManualResetEventSlim, because not always on calling Set() and Reset() will be called “heavy” methods for working with Windows kernel-mode objects. It’s a small and nice improvement! ;)