Tuesday, November 11, 2014

Java thread synchronization performance

I was surprised that I couldn't find anything useful on this topic from a Google search. There are a lot of questions and discussions about Thread synchronization, which is apt, since the topic is important and not terribly simple.

But there are some questions that get asked over and over but never answered, despite the answer being quite simple.

Here's the one that I couldn't find the answer to: how much overhead is there to entering a synchronization block? Just, flat out, with no other nonsense involved, how much does entering a synchronized block slow down your code?

To discuss it with a lot of people, you'd think that entering a synchronized block starts a chain of events that culminates in a petition sent to congress that could delay processing by weeks. I'll give you my results in a moment, but I suspect that misconception is the result of people watching a poorly designed multi-threaded program essentially become single-threaded because of the overuse of synchronization. That, however, is an entirely different topic.

EDIT: I'm continuing my research into synchronization, see the next installment here.

Let's construct a scenario to illustrate the question: you have a process that needs to run very fast based on the application requirements. Under normal conditions, there is only a single thread running this process, but occasionally (in unusual circumstances) there may be more than one. It's absolutely vital that these threads do not tramp all over each other, so some sort of synchronization is required. However, in the typical case, you don't want thread performance to be slowed by synchronization that isn't necessary. Can you just use synchronized blocks or will they create a performance bottleneck in the single-threaded case, requiring you to contrive some sort of synchronization that turns off and on?

So I wrote this simple test:

public class PerfTest {
  public static void main(String[] argc) {
    PerfTest pt = new PerfTest();
    pt.run();
  }
  
  private void run() {
    final int runs = 10000;
    
    for (int loop = 0; loop < 10; loop++) {
      long start, end;

      start = System.nanoTime();
      for (int i = 0; i < runs; i++) {
        withLock();
      }
      end = System.nanoTime();
      System.out.println("Synchornized run time   " + (end - start));

      start = System.nanoTime();
      for (int i = 0; i < runs; i++) {
        withoutLock();
      }
      end = System.nanoTime();
      System.out.println("Unsynchornized run time " + (end - start));
    }
  }
  
  private synchronized void withLock() {
    common();
  }
  
  private void withoutLock() {
    common();
  }
  
  private void common() {
    float f = 1;
    for (int i = 0; i < 10; i++) {
      f += f;
    }
  }
}

Results:

Synchornized run time   1533107
Unsynchornized run time 801967
Synchornized run time   346320
Unsynchornized run time 304972
Synchornized run time   340985
Unsynchornized run time 305335
Synchornized run time   342456
Unsynchornized run time 307547
Synchornized run time   343720
Unsynchornized run time 307947
Synchornized run time   343797
Unsynchornized run time 305243
Synchornized run time   344508
Unsynchornized run time 306182
Synchornized run time   340217
Unsynchornized run time 307223
Synchornized run time   340816
Unsynchornized run time 304765
Synchornized run time   343846
Unsynchornized run time 305347

Do the math and you come up with the answer that synchronized costs 3 nanoseconds (for those who aren't privy to that unit, that's .000000003 seconds)

Obviously, this is on a specific piece of hardware with a specific JVM installed. Other virtual machines on other hardware will likely produce variations in timing.

But the act alone of entering into a synchronized block probably isn't enough of a performance hit to shy away from it. Lesson being, if you think your code might ever be multi-threaded and operation could be hindered by unsynchronized access, add synchronized blocks. If your performance concerns are great enough that 3 nanoseconds is too long, then you probably shouldn't be writing Java in the first place.

There's obviously a lot more to thread synchronization. Hopefully I'll take some time to write some more in the near future.

No comments:

Post a Comment