I am wondering how a piece of locked code can slow down my code even though the code is never executed. Here is an example below:
public void Test_PerformanceUnit()
{
Stopwatch sw = new Stopwatch();
sw.Start();
Random r = new Random();
for (int i = 0; i < 10000; i++)
{
testRand(r);
}
sw.Stop();
Console.WriteLine(sw.ElapsedTicks);
}
public object testRand(Random r)
{
if (r.Next(1) > 10)
{
lock(this) {
return null;
}
}
return r;
}
This code runs in ~1300ms on my machine. If we remove the lock block (but keep its body), we get 750ms. Almost the double, even though the code is never run!
Of course this code does nothing. I noticed it while adding some lazy initialization in a class where the code checks if the object is initialized and if not initializes it. The problem is that the initialization is locked and slows down everything even after the first call.
My questions are:
Locks (also known as mutexes) have a history of being misjudged. Back in 1986, in a Usenet discussion on multithreading, Matthew Dillon wrote, “Most people have the misconception that locks are slow.” 25 years later, this misconception still seems to pop up once in a while.
No the compiler does not slow down the program, it ignores all the comments and takes the raw code. Show activity on this post. If language is using the compiler then the processing don't effected but if interpreter is working there then the process may slow down due to comments.
While a lock is held, the thread that holds the lock can again acquire and release the lock. Any other thread is blocked from acquiring the lock and waits until the lock is released. Since the code uses a try... finally block, the lock is released even if an exception is thrown within the body of a lock statement.
lock (Monitor. Enter/Exit) is very cheap, cheaper than alternatives like a Waithandle or Mutex.
About why it's happening, it has been discussed in the comments : it's due to the initialization of the try ... finally
generated by the lock
.
And to avoid this slowdown, you can extract the locking feature to a new method, so that the locking mechanism will only be initialized if the method is actually called.
I tried it with this simple code :
public object testRand(Random r)
{
if (r.Next(1) > 10)
{
return LockingFeature();
}
return r;
}
private object LockingFeature()
{
lock (_lock)
{
return null;
}
}
And here are my times (in ticks) :
your code, no lock : ~500
your code, with lock : ~1200
my code : ~500
EDIT : My test code (running a bit slower than the code with no locks) was actually on static methods, it appears that when the code is ran "inside" an object, the timings are the same. I fixed the timings according to that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With