Thursday, November 13, 2014

Java Thread Synchronization performance: round 2

You didn't think I was done, did you?

The previous post attempted to look at the most basic component of Thread synchronization and estimate its cost. The conclusion was that the cost is too low to be considered an issue.

Now I want to add another element to testing. Does the number of threads that might hold the lock significantly impact synchronization performance? I'm still not considering actual contention for the lock (I'll get to that). But how well is the synchronization code optimized to handle many threads, even when there is no contention.

My conclusion here? That the number of threads does impact synchronization performance, however, that impact is minimal to the point of unimportance. It's possible that continuing to increase the number of threads will eventually find a break point where performance degrades very quickly, but the change was fairly constant up to 1000 threads. The increase in time is small enough that it's not worth fussing over. If you're building a massive application that has many thousands of threads, you should probably run your own tests to be sure of the impact, but otherwise I wouldn't worry about it.

The code and results are included at the end of this post, so you're free to draw your own conclusions.

I ran the tests on FreeBSD 10 with OpenJDK 1.7.0_60-b19 (some of that version number may be FreeBSD-specific version information). Feel free to grab the code and try it out on your favorite JVM. Of course, hardware will have a non-trivial effect on overall run time, but the ratio of run times between the various numbers of threads should be fairly consistent within a specific implementation. Whether other JVMs have different performance characteristics is a question for someone else to answer.

Test Code:
public class Test2 {

  public static boolean run;
  
  public static void main(String[] args) {
    Test2 app = new Test2();
    app.go();
  }
  
  private void go() {
    
    int loops = 30000000;
    int[] sizes = new int[] {1, 1, 5, 10, 50, 100, 500, 1000};
    int runsEach = 30;
    
    for (int size : sizes) {
      System.out.println("");
      System.out.println(">> Testing with " + size + " threads");
      long total = 0;
      for (int i = 0; i < runsEach; i++) {
        startThreads(size);
        total += doTest(loops);
        stopThreads();
        java.lang.System.gc();
        try {
          Thread.sleep(900);
        }
        catch (InterruptedException e) {
          System.out.println("!! Main Thread interrupted");
        }
      }
      System.out.println(">> Average time = " + total / runsEach);
      System.out.println(">> Average time per call = " + total / (runsEach * loops));
    }
    
  }
  
  private TestThread[] threads;
    
  private void startThreads(final int num) {
    threads = new TestThread[num];
    run = true;
    for (int i = 0; i < num; i++) {
      threads[i] = new TestThread();
      threads[i].start();
    }
  }

  private long doTest(final int loops) {
    long start, end;
    TestThread test = new TestThread();
    start = System.nanoTime();
    for (int i = 0; i < loops; i++) {
      test.work();
    }
    end = System.nanoTime();
    System.out.println("Run time " + (end - start));
    return end - start;
  }

  private void stopThreads() {
    run = false;
    for (int i = 0; i < threads.length; i++) {
      try {
        threads[i].join();
      }
      catch (InterruptedException e) {
        System.out.println("!! Thread interrupted before successful join");
      }
    }
  }
  
  private class TestThread extends Thread {

    public void run() {
      while (run) {
        try {
          Thread.sleep(10);
        }
        catch (InterruptedException e) {
          // Just continue with the loop
        }
      }
    }
    
    public synchronized void work() {
      float f = 1;
      for (int i = 0; i < 10; i++) {
        f += f;
      }
    }
    
  }
  
}

Results:
>> Testing with 1 threads
Run time 795962042
Run time 786286857
Run time 199196969
Run time 16297397
Run time 17163671
Run time 16824990
Run time 16407745
Run time 17554201
Run time 15890607
Run time 17541246
Run time 16434331
Run time 16932517
Run time 16085874
Run time 15215291
Run time 15619357
Run time 15115641
Run time 15780876
Run time 15205568
Run time 15643645
Run time 15472839
Run time 15974359
Run time 15157174
Run time 15617533
Run time 15438992
Run time 15468485
Run time 15507157
Run time 15244974
Run time 15875522
Run time 15191091
Run time 15783097
>> Average time = 73729668
>> Average time per call = 2

>> Testing with 1 threads
Run time 15386418
Run time 15348036
Run time 15663421
Run time 15212932
Run time 15658743
Run time 15153505
Run time 15835270
Run time 15256209
Run time 15382945
Run time 15965559
Run time 15231225
Run time 15880726
Run time 15271493
Run time 175648894
Run time 178731461
Run time 190533321
Run time 199313983
Run time 199060600
Run time 199415887
Run time 198765814
Run time 199796152
Run time 198772032
Run time 199028302
Run time 199572685
Run time 199027456
Run time 199549007
Run time 198859863
Run time 198762869
Run time 198742303
Run time 198504792
>> Average time = 117777730
>> Average time per call = 3

>> Testing with 5 threads
Run time 199484393
Run time 198697191
Run time 200076825
Run time 199277548
Run time 199980755
Run time 199297630
Run time 199438981
Run time 199254148
Run time 198903016
Run time 199377172
Run time 199109173
Run time 200369934
Run time 199544113
Run time 200267803
Run time 199367557
Run time 200217676
Run time 199541885
Run time 199430942
Run time 199129716
Run time 198968970
Run time 198969677
Run time 199830897
Run time 198897138
Run time 199976410
Run time 199251960
Run time 199445897
Run time 199301249
Run time 198974016
Run time 198842029
Run time 199144413
>> Average time = 199412303
>> Average time per call = 6

>> Testing with 10 threads
Run time 199150382
Run time 199320156
Run time 199138419
Run time 199160883
Run time 199245564
Run time 199021987
Run time 198903988
Run time 199016954
Run time 199358554
Run time 199310135
Run time 200188974
Run time 199290615
Run time 199183679
Run time 199238515
Run time 199217762
Run time 199035672
Run time 199041539
Run time 199471882
Run time 199150993
Run time 199165169
Run time 199301659
Run time 198992886
Run time 199086269
Run time 199257415
Run time 198941664
Run time 199705914
Run time 199222760
Run time 199960150
Run time 199671478
Run time 200217657
>> Average time = 199298989
>> Average time per call = 6

>> Testing with 50 threads
Run time 200530217
Run time 199892436
Run time 197262962
Run time 201208398
Run time 199715748
Run time 199992779
Run time 200659787
Run time 200165258
Run time 199800937
Run time 199635001
Run time 200087977
Run time 200112382
Run time 200363229
Run time 200298460
Run time 199473022
Run time 199927887
Run time 199505967
Run time 200106173
Run time 200292659
Run time 200843640
Run time 199689815
Run time 200025039
Run time 200644594
Run time 199538623
Run time 199061118
Run time 199193134
Run time 200095598
Run time 199744275
Run time 199588208
Run time 198247397
>> Average time = 199856757
>> Average time per call = 6

>> Testing with 100 threads
Run time 200634862
Run time 200379624
Run time 200683595
Run time 200798071
Run time 199676874
Run time 200784278
Run time 200763565
Run time 200921517
Run time 200344951
Run time 200623614
Run time 200946937
Run time 200934182
Run time 200399438
Run time 200897918
Run time 200741948
Run time 200843386
Run time 201104921
Run time 200010681
Run time 200800678
Run time 200658461
Run time 200565042
Run time 200107552
Run time 200443788
Run time 200282941
Run time 200987905
Run time 201173373
Run time 200345061
Run time 200222440
Run time 200900207
Run time 199372010
>> Average time = 200578327
>> Average time per call = 6

>> Testing with 500 threads
Run time 203454398
Run time 205942401
Run time 210786881
Run time 206963343
Run time 202776739
Run time 205442114
Run time 208851151
Run time 207165043
Run time 215251977
Run time 209431195
Run time 205501259
Run time 215273283
Run time 212493174
Run time 212313821
Run time 206062559
Run time 208761225
Run time 209276176
Run time 210185340
Run time 206295473
Run time 208948272
Run time 208813577
Run time 209519561
Run time 211457223
Run time 208937196
Run time 205003769
Run time 211591797
Run time 208652449
Run time 207622638
Run time 210153556
Run time 211430555
>> Average time = 208811938
>> Average time per call = 6

>> Testing with 1000 threads
Run time 221468163
Run time 220505584
Run time 221465052
Run time 221587124
Run time 219965864
Run time 222025735
Run time 229103364
Run time 223751433
Run time 232994489
Run time 232980384
Run time 228709413
Run time 231892879
Run time 234950720
Run time 235469612
Run time 233690739
Run time 234489554
Run time 225850296
Run time 232385003
Run time 232641676
Run time 236887098
Run time 227695483
Run time 228017456
Run time 229519798
Run time 231757513
Run time 231118086
Run time 230468418
Run time 232784509
Run time 232030684
Run time 232966890
Run time 227513589
>> Average time = 229222886
>> Average time per call = 7

No comments:

Post a Comment