Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cost of locking in .NET vs Java

I was playing with Disruptor framework and its port for .NET platform and found an interesting case. May be I completely miss something so I'm looking for help from almighty Community.

        long iterations = 500*1000*1000;         long testValue = 1;          //.NET 4.0. Release build. Mean time - 26 secs;         object lockObject = new object();         Stopwatch sw = Stopwatch.StartNew();         for (int i = 0; i < iterations; i++)         {             lock (lockObject)             {                 testValue++;                 }         }         sw.Stop();          //Java 6.25. Default JVM params. Mean time - 17 secs.         Object lock = new Object();         long start = System.currentTimeMillis();         for (int i = 0; i < iterations; i++)         {                 synchronized (lock)                 {                     testValue++;                 }         }         long stop = System.currentTimeMillis(); 

It seems that acquiring the lock in the scenario with a signle thread in .NET costs just 50% more than in Java. At first I was suspicious at timers but I've ran the same test for a few times with results just around mentioned above mean values. Then I was suspicious at synchronized block of code but it does no more than just monitorenter / monitorexit byte code instructions - the same thing as lock keyword in .NET. Any other ideas why taking a lock is so expensive in .NET vs Java?

like image 460
Andrey Taptunov Avatar asked Aug 27 '11 17:08

Andrey Taptunov


People also ask

Why do we need locks in Java?

Java lock acts as thread synchronization mechanisms that are similar to the synchronized blocks. After some time, a new locking mechanism was introduced. It is very flexible and provides more options in comparison to the Synchronized block.


2 Answers

Yes, it looks like taking an uncontended lock is more expensive in .NET than in Java. (The results on my netbook are slightly more dramatic still.)

There are various aspects to performance which will be faster on one platform than another, sometimes to this extent. The HotSpot JIT and the .NET JIT are pretty radically different in various ways - not least because the .NET JIT only runs once on IL, whereas HotSpot is able to optimize more and more as a particular piece of code is run more and more often.

The important question is whether this is really significant. If your real life application spends really acquires an uncontented lock 500 million times every minute, it probably is significant - and you should probably redesign your app somewhat. If your real life application actually does real work within the lock (or between acquisitions of the lock) then it's unlikely to be a real bottleneck.

I recently found two .NET gotchas (part one; part two) which I'm having to work round as I'm writing a "system level library" and they would have made a significant difference when an application did a lot of date/time parsing - but this sort of micro-optimization is rarely worth doing.

like image 197
Jon Skeet Avatar answered Sep 22 '22 10:09

Jon Skeet


The first thing to remember about micro-benchmarks is that Java is particularly good at identifying and eliminating code which doesn't do anything. I have found that again and again, Java does pointless code faster than any other language. ;)

If Java is surprising fast compared to another language the first question should be; Does the code do anything remotely useful? (or even look like it could be useful)

Java tends to loop unroll more than it used to. It can also combine locks. As your test is uncontested and does do anything your code is like to look something like.

for (int i = 0; i < iterations; i+=8) {     synchronized (lock) {         testValue++;     }     synchronized (lock) {         testValue++;     }     synchronized (lock) {         testValue++;     }     synchronized (lock) {         testValue++;     }     synchronized (lock) {         testValue++;     }     synchronized (lock) {         testValue++;     }     synchronized (lock) {         testValue++;     }     synchronized (lock) {         testValue++;     } } 

which becomes

for (int i = 0; i < iterations; i+=8) {     synchronized (lock) {         testValue++;         testValue++;         testValue++;         testValue++;         testValue++;         testValue++;         testValue++;         testValue++;     } } 

since testValue is not used.

for (int i = 0; i < iterations; i+=8) {     synchronized (lock) {     } } 

and finally

{ } 
like image 39
Peter Lawrey Avatar answered Sep 18 '22 10:09

Peter Lawrey