Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is lock much slower than Monitor.TryEnter?

Tags:

Results

Lock: 85.3 microseconds

Monitor.TryEnter: 11.0 microseconds

Isn't the lock expanded into the same code?

Edit: Results with 1000 iterations: Lock: 103.3 microseconds Monitor.TryEnter: 20.2 microseconds

Code below. Thanks

    [Test]
    public void Lock_Performance_Test()
    {
        const int lockIterations = 100;

        Stopwatch csLock = Stopwatch.StartNew();
        for (int i = 0; i < lockIterations; )
        {
            lock (object1)
            {
                i++;
            }
        }
        csLock.Stop();

        Stopwatch csMonitor = Stopwatch.StartNew();
        for (int i = 0; i < lockIterations; )
        {
            if (Monitor.TryEnter(object1, TimeSpan.FromSeconds(10)))
            {
                try
                {
                    i++;
                }
                finally
                {
                    Monitor.Exit(object1);
                }
            }
        }
        csMonitor.Stop();

        Console.WriteLine("Lock: {0:f1} microseconds", csLock.Elapsed.Ticks / 10M);
        Console.WriteLine("Monitor.TryEnter: {0:f1} microseconds", csMonitor.Elapsed.Ticks / 10M);;
    }
like image 888
CodingThunder Avatar asked Mar 10 '10 12:03

CodingThunder


2 Answers

I don't actually know the answer, but feel it's important to point out that lock and Monitor.TryEnter are not functionally equivalent. From the MSDN documentation on Monitor.TryEnter:

If successful, this method acquires an exclusive lock on the obj parameter. This method returns immediately, whether or not the lock is available.

The lock statement is analogous to Monitor.Enter, which does potentially block. Granted, in your example code, there shouldn't be any blocking issues; but I would wager that since lock provides blocking, it does a little more work (potentially) than TryEnter does.


For what it's worth, I just tried your code on my machine and got completely different results:

100 iterations:
lock: 4.4 microseconds
Monitor.TryEnter: 16.1 microseconds
Monitor.Enter: 3.9 microseconds

100000 iterations:
lock: 2872.5 microseconds
Monitor.TryEnter: 5226.6 microseconds
Monitor.Enter: 2432.9 microseconds

This seriously undermines my initial guess and shows that, on my system, lock (which performs about the same as Monitor.Enter) actually drastically outperforms Monitor.TryEnter.


Indeed, I attempted this in VS 2010 targeting both .NET 3.5 and .NET 4.0 and, though the results were different, in each case lock did in fact outperform Monitor.TryEnter:

Runtime version: 2.0.50727.3603

Ran 100 times, 100000 iterations each time:
Lock: 279736.4 microseconds
Monitor.TryEnter: 1366751.5 microseconds
Monitor.TryEnter (no timeout): 475107.3 microseconds
Monitor.Enter: 332334.1 microseconds

Runtime version: 4.0.30128.1

Ran 100 times, 100000 iterations each time:
Lock: 334273.7 microseconds
Monitor.TryEnter: 1671363.4 microseconds
Monitor.TryEnter (no timeout): 531451.8 microseconds
Monitor.Enter: 316693.1 microseconds

(Notice I also tested Monitor.TryEnter with no timeout, as I agreed with Marc that calling TimeSpan.FromSeconds was almost certainly slowing down your times for Monitor.TryEnter--and these tests support that--though it's strange, since in your case apparently lock is still significantly slower.)

Based on these results I am strongly inclined to believe that your measured execution times are somehow affected by running this code with the Test attribute. Either that or this code is far more machine-dependent than I would have expected.

like image 56
Dan Tao Avatar answered Sep 28 '22 18:09

Dan Tao


100 is far too few, and running in a test framework may skew things. It is also possibly (see comments) related to any additional cost associated with the first lock against an object; try:

  • locking once outside the loop first
  • doing lots more iterations
  • in a console exe, at the command line, in release mode

Also, note that in 4.0 lock is not Monitor.Enter(object) - so expect different results in 4.0.

But I get:

lock: 3548ms
Monitor.TryEnter: 7008ms
Monitor.TryEnter (2): 2947ms
Monitor.Enter: 2906ms

From the test rig:

using System;
using System.Diagnostics;
using System.Threading;
static class Program {
    static void Main()
    {
        const int lockIterations = 50000000;
        object object1 = new object();
        lock (object1) { Console.WriteLine("First one has to pay an extra toll"); }
        Stopwatch csLock = Stopwatch.StartNew();
        for (int i = 0; i < lockIterations; ) {
            lock (object1) { i++; }
        }
        csLock.Stop();
        Console.WriteLine("lock: " + csLock.ElapsedMilliseconds + "ms");

        Stopwatch csMonitorTryEnter = Stopwatch.StartNew();
        for (int i = 0; i < lockIterations; ) {
            if (Monitor.TryEnter(object1, TimeSpan.FromSeconds(10))) {
                try { i++; } finally { Monitor.Exit(object1); }
            }
        }
        csMonitorTryEnter.Stop();
        Console.WriteLine("Monitor.TryEnter: " + csMonitorTryEnter.ElapsedMilliseconds + "ms");

        csMonitorTryEnter = Stopwatch.StartNew();
        for (int i = 0; i < lockIterations; ) {
            if (Monitor.TryEnter(object1, 10000)) {
                try { i++; } finally { Monitor.Exit(object1); }
            }
        }
        csMonitorTryEnter.Stop();
        Console.WriteLine("Monitor.TryEnter (2): " + csMonitorTryEnter.ElapsedMilliseconds + "ms");

        Stopwatch csMonitorEnter = Stopwatch.StartNew();
        for (int i = 0; i < lockIterations; ) {
            Monitor.Enter(object1);
            try { i++; } finally { Monitor.Exit(object1); }
        }
        csMonitorEnter.Stop();
        Console.WriteLine("Monitor.Enter: " + csMonitorEnter.ElapsedMilliseconds + "ms");
    }
}
like image 24
Marc Gravell Avatar answered Sep 28 '22 17:09

Marc Gravell