Performance Comparison of Multithreading in Java
There are different techniques for multithreading in Java. One can parallelize a piece of code in Java either with synchronize keywords, locks or atomic variables. This post will compare performances of using synchronized keyword, ReentrantLock, getAndIncrement() and performing continuous trials of get() and compareAndSet() calls. Different types of Matrix classes are created for performance testing and a plain one also included. For comparison, all cells incremented 100 times for different sizes of matrices, with different types of synchronizations, thread counts and pool sizes at a computer which has Intel Core I7 (has 8 cores – 4 of them are real), Ubuntu 14.04 LTS and Java 1.7.0_60.
This is the plain matrix class of performance test:
/** * Plain matrix without synchronization. */ public class Matrix { private int rows; private int cols; private int[][] array; /** * Matrix constructor. * * @param rows number of rows * @param cols number of columns */ public Matrix(int rows, int cols) { this.rows = rows; this.cols = cols; array = new int[rows][rows]; } /** * Increments all matrix cells. */ public void increment() { for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { array[i][j]++; } } } /** * Returns a string representation of the object which shows row sums of each row. * * @return a string representation of the object. */ @Override public String toString() { StringBuffer s = new StringBuffer(); int rowSum; for (int i = 0; i < rows; i++) { rowSum = 0; for (int j = 0; j < cols; j++) { rowSum += array[i][j]; } s.append(rowSum); s.append(" "); } return s.toString(); } }
For other ones, increment methods of them are listed due to remaining parts are same for each matrix types. Synchronized matrix:
public void increment() { for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { synchronized (this) { array[i][j]++; } } } }
Lock matrix:
public void increment() { for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { lock.lock(); try { array[i][j]++; } finally { lock.unlock(); } } } }
Atomic getAndIncrement matrix:
public void increment() { for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { array[i][j].getAndIncrement(); } } }
Continuous trials of get() and compareAndSet() matrix:
public void increment() { for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { for (; ; ) { int current = array[i][j].get(); int next = current + 1; if (array[i][j].compareAndSet(current, next)) { break; } } } } }
Also worker classes are created for each matrix. Here is the worker class of plain one:
/** * Worker for plain matrix without synchronization. * * @author Furkan KAMACI * @see Matrix */ public class PlainMatrixWorker extends Matrix implements Runnable { private AtomicInteger incrementCount = new AtomicInteger(WorkerDefaults.INCREMENT_COUNT); /** * Worker constructor. * * @param rows number of rows * @param cols number of columns */ public PlainMatrixWorker(int rows, int cols) { super(rows, cols); } /** * Increments matrix up to a maximum number. * * @see WorkerDefaults */ @Override public void run() { while (incrementCount.getAndDecrement() > 0) { increment(); } } }
For a correct comparison, all tests are replied 20 times by default. Average and standard errors calculated for each result. Due to there are many dimensions at test set (matrix type, matrix size, pool size, thread count and elapsed time) some features are shown as aggregated at charts. These are the results: For pool size 2 and thread count 2:
For pool size 4 and thread count 4:
For pool size 6 and thread count 6:
For pool size 8 and thread count 8:
For pool size 10 and thread count 10:
For pool size 12 and thread count 12:
Conclusion
It can be easily seen that plain version is run fastest. However it does not produce correct results as expected. Worse performance is seen with synchronized blocks (when synchronization is done with “this”). Locks are slightly better than synchronized blocks. However, atomic variables are prominently better from all of them. When atomic getAndIncrement and continous trials of get() and compareAndSet() calls compared it’s shown that their performances are same. Reason behind it can easily be understood when source code of Java is checked:
/** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } }
It can be seen that getAndIncrement is implemented with continuous trials of get() and compareAndSet() within Java (version 1.7) source code. On the other hand when other results are checked the effect of pool size can be seen. When a pool size is used which is less than actual thread counts a performance performance issue will occur. So, performance comparison of multithreading in Java shows that when a piece of code is decided to be synchronized and performance is an issue, and if such kind of threads will be used as like in the test, one should try to use Atomic variables. Other choices should be locks or synchronized blocks. Also it does not mean that synchronized blocks are always better than locks due to effect of JIT compiler and running a piece of code several times or not.
- Source code for performance comparison of multithreading in Java can be downloaded from here: https://github.com/kamaci/performance
Reference: | Performance Comparison of Multithreading in Java from our JCG partner Furkan Kamaci at the FURKAN KAMACI blog. |
Hi. Check line 17 of the Matrix class. You are creating the array using only the rows parameter instead of using the cols as well.
In java two dimensional array can be declared as same as one dimensional array